github.com/fr-nvriep/migrate/v4@v4.3.2/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/fr-nvriep/migrate/v4" 14 "github.com/fr-nvriep/migrate/v4/database" 15 "github.com/fr-nvriep/migrate/v4/source" 16 ) 17 18 const defaultTimeFormat = "20060102150405" 19 20 // set main log 21 var log = &Log{} 22 23 func Main(version string) { 24 helpPtr := flag.Bool("help", false, "") 25 versionPtr := flag.Bool("version", false, "") 26 verbosePtr := flag.Bool("verbose", false, "") 27 prefetchPtr := flag.Uint("prefetch", 10, "") 28 lockTimeoutPtr := flag.Uint("lock-timeout", 15, "") 29 pathPtr := flag.String("path", "", "") 30 databasePtr := flag.String("database", "", "") 31 sourcePtr := flag.String("source", "", "") 32 33 flag.Usage = func() { 34 fmt.Fprint(os.Stderr, 35 `Usage: migrate OPTIONS COMMAND [arg...] 36 migrate [ -version | -help ] 37 38 Options: 39 -source Location of the migrations (driver://url) 40 -path Shorthand for -source=file://path 41 -database Run migrations against this database (driver://url) 42 -prefetch N Number of migrations to load in advance before executing (default 10) 43 -lock-timeout N Allow N seconds to acquire database lock (default 15) 44 -verbose Print verbose logging 45 -version Print version 46 -help Print usage 47 48 Commands: 49 create [-ext E] [-dir D] [-seq] [-digits N] [-format] NAME 50 Create a set of timestamped up/down migrations titled NAME, in directory D with extension E. 51 Use -seq option to generate sequential up/down migrations with N digits. 52 Use -format option to specify a Go time format string. 53 goto V Migrate to version V 54 up [N] Apply all or N up migrations 55 down [N] Apply all or N down migrations 56 drop Drop everything inside database 57 force V Set version V but don't run migration (ignores dirty state) 58 version Print current migration version 59 60 Source drivers: `+strings.Join(source.List(), ", ")+` 61 Database drivers: `+strings.Join(database.List(), ", ")+"\n") 62 } 63 64 flag.Parse() 65 66 // initialize logger 67 log.verbose = *verbosePtr 68 69 // show cli version 70 if *versionPtr { 71 fmt.Fprintln(os.Stderr, version) 72 os.Exit(0) 73 } 74 75 // show help 76 if *helpPtr { 77 flag.Usage() 78 os.Exit(0) 79 } 80 81 // translate -path into -source if given 82 if *sourcePtr == "" && *pathPtr != "" { 83 *sourcePtr = fmt.Sprintf("file://%v", *pathPtr) 84 } 85 86 // initialize migrate 87 // don't catch migraterErr here and let each command decide 88 // how it wants to handle the error 89 migrater, migraterErr := migrate.New(*sourcePtr, *databasePtr) 90 defer func() { 91 if migraterErr == nil { 92 if _, err := migrater.Close(); err != nil { 93 log.Println(err) 94 } 95 } 96 }() 97 if migraterErr == nil { 98 migrater.Log = log 99 migrater.PrefetchMigrations = *prefetchPtr 100 migrater.LockTimeout = time.Duration(int64(*lockTimeoutPtr)) * time.Second 101 102 // handle Ctrl+c 103 signals := make(chan os.Signal, 1) 104 signal.Notify(signals, syscall.SIGINT) 105 go func() { 106 for range signals { 107 log.Println("Stopping after this running migration ...") 108 migrater.GracefulStop <- true 109 return 110 } 111 }() 112 } 113 114 startTime := time.Now() 115 116 switch flag.Arg(0) { 117 case "create": 118 args := flag.Args()[1:] 119 seq := false 120 seqDigits := 6 121 122 createFlagSet := flag.NewFlagSet("create", flag.ExitOnError) 123 extPtr := createFlagSet.String("ext", "", "File extension") 124 dirPtr := createFlagSet.String("dir", "", "Directory to place file in (default: current working directory)") 125 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`) 126 createFlagSet.BoolVar(&seq, "seq", seq, "Use sequential numbers instead of timestamps (default: false)") 127 createFlagSet.IntVar(&seqDigits, "digits", seqDigits, "The number of digits to use in sequences (default: 6)") 128 if err := createFlagSet.Parse(args); err != nil { 129 log.Println(err) 130 } 131 132 if createFlagSet.NArg() == 0 { 133 log.fatal("error: please specify name") 134 } 135 name := createFlagSet.Arg(0) 136 137 if *extPtr == "" { 138 log.fatal("error: -ext flag must be specified") 139 } 140 *extPtr = "." + strings.TrimPrefix(*extPtr, ".") 141 142 if *dirPtr != "" { 143 *dirPtr = strings.Trim(*dirPtr, "/") + "/" 144 } 145 146 createCmd(*dirPtr, startTime, *formatPtr, name, *extPtr, seq, seqDigits) 147 148 case "goto": 149 if migraterErr != nil { 150 log.fatalErr(migraterErr) 151 } 152 153 if flag.Arg(1) == "" { 154 log.fatal("error: please specify version argument V") 155 } 156 157 v, err := strconv.ParseUint(flag.Arg(1), 10, 64) 158 if err != nil { 159 log.fatal("error: can't read version argument V") 160 } 161 162 gotoCmd(migrater, uint(v)) 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 upCmd(migrater, limit) 183 184 if log.verbose { 185 log.Println("Finished after", time.Since(startTime)) 186 } 187 188 case "down": 189 if migraterErr != nil { 190 log.fatalErr(migraterErr) 191 } 192 193 limit := -1 194 if flag.Arg(1) != "" { 195 n, err := strconv.ParseUint(flag.Arg(1), 10, 64) 196 if err != nil { 197 log.fatal("error: can't read limit argument N") 198 } 199 limit = int(n) 200 } 201 202 downCmd(migrater, limit) 203 204 if log.verbose { 205 log.Println("Finished after", time.Since(startTime)) 206 } 207 208 case "drop": 209 if migraterErr != nil { 210 log.fatalErr(migraterErr) 211 } 212 213 dropCmd(migrater) 214 215 if log.verbose { 216 log.Println("Finished after", time.Since(startTime)) 217 } 218 219 case "force": 220 if migraterErr != nil { 221 log.fatalErr(migraterErr) 222 } 223 224 if flag.Arg(1) == "" { 225 log.fatal("error: please specify version argument V") 226 } 227 228 v, err := strconv.ParseInt(flag.Arg(1), 10, 64) 229 if err != nil { 230 log.fatal("error: can't read version argument V") 231 } 232 233 if v < -1 { 234 log.fatal("error: argument V must be >= -1") 235 } 236 237 forceCmd(migrater, int(v)) 238 239 if log.verbose { 240 log.Println("Finished after", time.Since(startTime)) 241 } 242 243 case "version": 244 if migraterErr != nil { 245 log.fatalErr(migraterErr) 246 } 247 248 versionCmd(migrater) 249 250 default: 251 flag.Usage() 252 os.Exit(0) 253 } 254 }