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 }