github.com/tooolbox/migrate/v4@v4.6.2-0.20200325001913-461b03b92064/internal/cli/main.go (about)

     1  package cli
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"os"
     7  	"os/signal"
     8  	"strconv"
     9  	"strings"
    10  	"syscall"
    11  	"time"
    12  
    13  	"github.com/tooolbox/migrate/v4"
    14  	"github.com/tooolbox/migrate/v4/database"
    15  	"github.com/tooolbox/migrate/v4/source"
    16  )
    17  
    18  const defaultTimeFormat = "20060102150405"
    19  
    20  // set main log
    21  var log = &Log{}
    22  
    23  // Main function of a cli application. It is public for backwards compatibility with `cli` package
    24  func Main(version string) {
    25  	helpPtr := flag.Bool("help", false, "")
    26  	versionPtr := flag.Bool("version", false, "")
    27  	verbosePtr := flag.Bool("verbose", false, "")
    28  	prefetchPtr := flag.Uint("prefetch", 10, "")
    29  	lockTimeoutPtr := flag.Uint("lock-timeout", 15, "")
    30  	pathPtr := flag.String("path", "", "")
    31  	databasePtr := flag.String("database", "", "")
    32  	sourcePtr := flag.String("source", "", "")
    33  
    34  	flag.Usage = func() {
    35  		fmt.Fprint(os.Stderr,
    36  			`Usage: migrate OPTIONS COMMAND [arg...]
    37         migrate [ -version | -help ]
    38  
    39  Options:
    40    -source          Location of the migrations (driver://url)
    41    -path            Shorthand for -source=file://path
    42    -database        Run migrations against this database (driver://url)
    43    -prefetch N      Number of migrations to load in advance before executing (default 10)
    44    -lock-timeout N  Allow N seconds to acquire database lock (default 15)
    45    -verbose         Print verbose logging
    46    -version         Print version
    47    -help            Print usage
    48  
    49  Commands:
    50    create [-ext E] [-dir D] [-seq] [-digits N] [-format] NAME
    51  			   Create a set of timestamped up/down migrations titled NAME, in directory D with extension E.
    52  			   Use -seq option to generate sequential up/down migrations with N digits.
    53  			   Use -format option to specify a Go time format string. Note: migrations with the same time cause "duplicate migration version" error. 
    54    goto V       Migrate to version V
    55    up [N]       Apply all or N up migrations
    56    down [N]     Apply all or N down migrations
    57    drop         Drop everything inside database
    58    force V      Set version V but don't run migration (ignores dirty state)
    59    version      Print current migration version
    60  
    61  Source drivers: `+strings.Join(source.List(), ", ")+`
    62  Database drivers: `+strings.Join(database.List(), ", ")+"\n")
    63  	}
    64  
    65  	flag.Parse()
    66  
    67  	// initialize logger
    68  	log.verbose = *verbosePtr
    69  
    70  	// show cli version
    71  	if *versionPtr {
    72  		fmt.Fprintln(os.Stderr, version)
    73  		os.Exit(0)
    74  	}
    75  
    76  	// show help
    77  	if *helpPtr {
    78  		flag.Usage()
    79  		os.Exit(0)
    80  	}
    81  
    82  	// translate -path into -source if given
    83  	if *sourcePtr == "" && *pathPtr != "" {
    84  		*sourcePtr = fmt.Sprintf("file://%v", *pathPtr)
    85  	}
    86  
    87  	// initialize migrate
    88  	// don't catch migraterErr here and let each command decide
    89  	// how it wants to handle the error
    90  	migrater, migraterErr := migrate.New(*sourcePtr, *databasePtr)
    91  	defer func() {
    92  		if migraterErr == nil {
    93  			if _, err := migrater.Close(); err != nil {
    94  				log.Println(err)
    95  			}
    96  		}
    97  	}()
    98  	if migraterErr == nil {
    99  		migrater.Log = log
   100  		migrater.PrefetchMigrations = *prefetchPtr
   101  		migrater.LockTimeout = time.Duration(int64(*lockTimeoutPtr)) * time.Second
   102  
   103  		// handle Ctrl+c
   104  		signals := make(chan os.Signal, 1)
   105  		signal.Notify(signals, syscall.SIGINT)
   106  		go func() {
   107  			for range signals {
   108  				log.Println("Stopping after this running migration ...")
   109  				migrater.GracefulStop <- true
   110  				return
   111  			}
   112  		}()
   113  	}
   114  
   115  	startTime := time.Now()
   116  
   117  	switch flag.Arg(0) {
   118  	case "create":
   119  		args := flag.Args()[1:]
   120  		seq := false
   121  		seqDigits := 6
   122  
   123  		createFlagSet := flag.NewFlagSet("create", flag.ExitOnError)
   124  		extPtr := createFlagSet.String("ext", "", "File extension")
   125  		dirPtr := createFlagSet.String("dir", "", "Directory to place file in (default: current working directory)")
   126  		formatPtr := createFlagSet.String("format", defaultTimeFormat, `The Go time format string to use. If the string "unix" or "unixNano" is specified, then the seconds or nanoseconds since January 1, 1970 UTC respectively will be used. Caution, due to the behavior of time.Time.Format(), invalid format strings will not error`)
   127  		createFlagSet.BoolVar(&seq, "seq", seq, "Use sequential numbers instead of timestamps (default: false)")
   128  		createFlagSet.IntVar(&seqDigits, "digits", seqDigits, "The number of digits to use in sequences (default: 6)")
   129  		if err := createFlagSet.Parse(args); err != nil {
   130  			log.Println(err)
   131  		}
   132  
   133  		if createFlagSet.NArg() == 0 {
   134  			log.fatal("error: please specify name")
   135  		}
   136  		name := createFlagSet.Arg(0)
   137  
   138  		if *extPtr == "" {
   139  			log.fatal("error: -ext flag must be specified")
   140  		}
   141  
   142  		if err := createCmd(*dirPtr, startTime, *formatPtr, name, *extPtr, seq, seqDigits, true); err != nil {
   143  			log.fatalErr(err)
   144  		}
   145  
   146  	case "goto":
   147  		if migraterErr != nil {
   148  			log.fatalErr(migraterErr)
   149  		}
   150  
   151  		if flag.Arg(1) == "" {
   152  			log.fatal("error: please specify version argument V")
   153  		}
   154  
   155  		v, err := strconv.ParseUint(flag.Arg(1), 10, 64)
   156  		if err != nil {
   157  			log.fatal("error: can't read version argument V")
   158  		}
   159  
   160  		if err := gotoCmd(migrater, uint(v)); err != nil {
   161  			log.fatalErr(err)
   162  		}
   163  
   164  		if log.verbose {
   165  			log.Println("Finished after", time.Since(startTime))
   166  		}
   167  
   168  	case "up":
   169  		if migraterErr != nil {
   170  			log.fatalErr(migraterErr)
   171  		}
   172  
   173  		limit := -1
   174  		if flag.Arg(1) != "" {
   175  			n, err := strconv.ParseUint(flag.Arg(1), 10, 64)
   176  			if err != nil {
   177  				log.fatal("error: can't read limit argument N")
   178  			}
   179  			limit = int(n)
   180  		}
   181  
   182  		if err := upCmd(migrater, limit); err != nil {
   183  			log.fatalErr(err)
   184  		}
   185  
   186  		if log.verbose {
   187  			log.Println("Finished after", time.Since(startTime))
   188  		}
   189  
   190  	case "down":
   191  		if migraterErr != nil {
   192  			log.fatalErr(migraterErr)
   193  		}
   194  
   195  		downFlagSet := flag.NewFlagSet("down", flag.ExitOnError)
   196  		applyAll := downFlagSet.Bool("all", false, "Apply all down migrations")
   197  
   198  		args := flag.Args()[1:]
   199  		if err := downFlagSet.Parse(args); err != nil {
   200  			log.fatalErr(err)
   201  		}
   202  
   203  		downArgs := downFlagSet.Args()
   204  		num, needsConfirm, err := numDownMigrationsFromArgs(*applyAll, downArgs)
   205  		if err != nil {
   206  			log.fatalErr(err)
   207  		}
   208  		if needsConfirm {
   209  			log.Println("Are you sure you want to apply all down migrations? [y/N]")
   210  			var response string
   211  			fmt.Scanln(&response)
   212  			response = strings.ToLower(strings.TrimSpace(response))
   213  
   214  			if response == "y" {
   215  				log.Println("Applying all down migrations")
   216  			} else {
   217  				log.fatal("Not applying all down migrations")
   218  			}
   219  		}
   220  
   221  		if err := downCmd(migrater, num); err != nil {
   222  			log.fatalErr(err)
   223  		}
   224  
   225  		if log.verbose {
   226  			log.Println("Finished after", time.Since(startTime))
   227  		}
   228  
   229  	case "drop":
   230  		log.Println("Are you sure you want to drop the entire database schema? [y/N]")
   231  		var response string
   232  		fmt.Scanln(&response)
   233  		response = strings.ToLower(strings.TrimSpace(response))
   234  
   235  		if response == "y" {
   236  			log.Println("Dropping the entire database schema")
   237  		} else {
   238  			log.fatal("Aborted dropping the entire database schema")
   239  		}
   240  
   241  		if migraterErr != nil {
   242  			log.fatalErr(migraterErr)
   243  		}
   244  
   245  		if err := dropCmd(migrater); err != nil {
   246  			log.fatalErr(err)
   247  		}
   248  
   249  		if log.verbose {
   250  			log.Println("Finished after", time.Since(startTime))
   251  		}
   252  
   253  	case "force":
   254  		if migraterErr != nil {
   255  			log.fatalErr(migraterErr)
   256  		}
   257  
   258  		if flag.Arg(1) == "" {
   259  			log.fatal("error: please specify version argument V")
   260  		}
   261  
   262  		v, err := strconv.ParseInt(flag.Arg(1), 10, 64)
   263  		if err != nil {
   264  			log.fatal("error: can't read version argument V")
   265  		}
   266  
   267  		if v < -1 {
   268  			log.fatal("error: argument V must be >= -1")
   269  		}
   270  
   271  		if err := forceCmd(migrater, int(v)); err != nil {
   272  			log.fatalErr(err)
   273  		}
   274  
   275  		if log.verbose {
   276  			log.Println("Finished after", time.Since(startTime))
   277  		}
   278  
   279  	case "version":
   280  		if migraterErr != nil {
   281  			log.fatalErr(migraterErr)
   282  		}
   283  
   284  		if err := versionCmd(migrater); err != nil {
   285  			log.fatalErr(err)
   286  		}
   287  
   288  	default:
   289  		flag.Usage()
   290  
   291  		// If a command is not found we exit with a status 2 to match the behavior
   292  		// of flag.Parse() with flag.ExitOnError when parsing an invalid flag.
   293  		os.Exit(2)
   294  	}
   295  }