2018-06-24 16:40:48 +03:00
package commands
import (
2021-04-01 08:13:24 +03:00
"fmt"
2021-01-18 20:11:05 +03:00
"path/filepath"
2018-06-24 16:40:48 +03:00
"time"
2021-06-16 14:44:15 +03:00
"github.com/hasura/graphql-engine/cli/v2"
2021-07-23 12:49:44 +03:00
"github.com/hasura/graphql-engine/cli/v2/internal/hasura"
2021-06-16 14:44:15 +03:00
"github.com/hasura/graphql-engine/cli/v2/migrate"
2018-06-24 16:40:48 +03:00
"github.com/pkg/errors"
"github.com/spf13/cobra"
2019-03-18 19:40:04 +03:00
"github.com/spf13/pflag"
2019-04-30 11:34:08 +03:00
2021-06-16 14:44:15 +03:00
mig "github.com/hasura/graphql-engine/cli/v2/migrate/cmd"
2019-04-30 11:34:08 +03:00
log "github.com/sirupsen/logrus"
2018-06-24 16:40:48 +03:00
)
2019-04-30 11:34:08 +03:00
const migrateCreateCmdExamples = ` # Setup migration files for the first time by introspecting a server :
2019-12-12 08:16:36 +03:00
hasura migrate create "init" -- from - server
# Use with admin secret :
hasura migrate create -- admin - secret "<admin-secret>"
# Setup migration files from an instance mentioned by the flag :
2020-04-09 12:05:05 +03:00
hasura migrate create init -- from - server -- endpoint "<endpoint>"
# Take pg_dump of schema and hasura metadata from server while specifying the schemas to include
hasura migrate create init -- from - server -- schema myschema1 , myschema2
2020-06-16 09:00:20 +03:00
# Create up and down SQL migrations , providing contents as flags
hasura migrate create migration - name -- up - sql "CREATE TABLE article(id serial NOT NULL, title text NOT NULL, content text NOT NULL);" -- down - sql "DROP TABLE article;"
`
2019-04-30 11:34:08 +03:00
2018-06-28 11:36:25 +03:00
func newMigrateCreateCmd ( ec * cli . ExecutionContext ) * cobra . Command {
2018-06-24 16:40:48 +03:00
opts := & migrateCreateOptions {
EC : ec ,
}
migrateCreateCmd := & cobra . Command {
Use : "create [migration-name]" ,
Short : "Create files required for a migration" ,
2022-01-12 19:15:52 +03:00
Long : "Create ``sql`` files required for a migration" ,
2019-04-30 11:34:08 +03:00
Example : migrateCreateCmdExamples ,
2018-06-24 16:40:48 +03:00
SilenceUsage : true ,
Args : cobra . ExactArgs ( 1 ) ,
2021-04-01 08:13:24 +03:00
PreRunE : func ( cmd * cobra . Command , args [ ] string ) error {
2021-10-07 17:23:19 +03:00
if cmd . Flags ( ) . Changed ( "up-sql" ) {
opts . upSQLChanged = true
}
if cmd . Flags ( ) . Changed ( "down-sql" ) {
opts . downSQLChanged = true
}
2021-04-01 08:13:24 +03:00
if cmd . Flags ( ) . Changed ( "metadata-from-server" ) {
2021-09-10 07:54:48 +03:00
return fmt . Errorf ( "metadata-from-server flag is deprecated" )
2021-04-01 08:13:24 +03:00
}
if cmd . Flags ( ) . Changed ( "metadata-from-file" ) {
2021-09-10 07:54:48 +03:00
return fmt . Errorf ( "metadata-from-file flag is deprecated" )
2021-04-01 08:13:24 +03:00
}
2021-06-21 16:54:08 +03:00
if err := validateConfigV3Flags ( cmd , ec ) ; err != nil {
if errors . Is ( err , errDatabaseNotFound ) {
// this means provided database is not yet connected to hasura
// this can be ignored for `migrate create`
// we can allow users to create migration files for databases
// which are not connected
ec . Logger . Warnf ( "database %s is not connected to hasura" , ec . Source . Name )
ec . Source . Kind = hasura . SourceKindPG // the default kind is postgres
return nil
}
return err
}
2021-10-07 17:23:19 +03:00
2022-01-12 19:15:52 +03:00
if opts . upSQLChanged && ! opts . downSQLChanged {
2021-10-07 17:23:19 +03:00
ec . Logger . Warn ( "you are creating an up migration without a down migration" )
}
2022-01-12 19:15:52 +03:00
if opts . downSQLChanged && ! opts . upSQLChanged {
2021-10-07 17:23:19 +03:00
ec . Logger . Warn ( "you are creating a down migration without an up migration" )
}
2021-06-21 16:54:08 +03:00
return nil
2021-04-01 08:13:24 +03:00
} ,
2018-06-24 16:40:48 +03:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
opts . name = args [ 0 ]
2019-04-03 14:29:58 +03:00
opts . EC . Spin ( "Creating migration files..." )
2021-03-08 14:59:35 +03:00
opts . Source = ec . Source
2019-04-03 14:29:58 +03:00
version , err := opts . run ( )
opts . EC . Spinner . Stop ( )
if err != nil {
return err
}
opts . EC . Logger . WithFields ( log . Fields {
"version" : version ,
"name" : opts . name ,
} ) . Info ( "Migrations files created" )
return nil
2018-06-24 16:40:48 +03:00
} ,
}
2019-03-18 19:40:04 +03:00
f := migrateCreateCmd . Flags ( )
opts . flags = f
2022-01-12 19:15:52 +03:00
f . SortFlags = false
2020-04-09 12:05:05 +03:00
f . BoolVar ( & opts . fromServer , "from-server" , false , "take pg_dump of schema (default: public) and Hasura metadata from the server" )
2021-03-15 18:40:52 +03:00
f . StringVar ( & opts . sqlFile , "sql-from-file" , "" , "path to an SQL file which contains the SQL statements" )
f . BoolVar ( & opts . sqlServer , "sql-from-server" , false , "take pg_dump from the server (default: public) and save it as a migration" )
2022-04-25 16:05:26 +03:00
f . StringSliceVar ( & opts . includeSchemas , "schema" , [ ] string { "public" } , "name of Postgres schema to export as a migration. provide multiple schemas with a comma separated list e.g. --schema public,user" )
2019-03-18 19:40:04 +03:00
f . StringVar ( & opts . metaDataFile , "metadata-from-file" , "" , "path to a hasura metadata file to be used for up actions" )
f . BoolVar ( & opts . metaDataServer , "metadata-from-server" , false , "take metadata from the server and write it as an up migration file" )
2020-06-16 09:00:20 +03:00
f . StringVar ( & opts . upSQL , "up-sql" , "" , "sql string/query that is to be used to create an up migration" )
f . StringVar ( & opts . downSQL , "down-sql" , "" , "sql string/query that is to be used to create a down migration" )
2019-04-30 11:34:08 +03:00
2022-01-12 19:15:52 +03:00
if err := f . MarkDeprecated ( "sql-from-server" , "use --from-server instead" ) ; err != nil {
ec . Logger . Debugf ( "marking flag --sql-from-server as depricatef failed: %v" , err )
}
2021-10-13 17:38:07 +03:00
if err := migrateCreateCmd . MarkFlagFilename ( "sql-from-file" , "sql" ) ; err != nil {
ec . Logger . WithError ( err ) . Errorf ( "error while using a dependency library" )
}
if err := migrateCreateCmd . MarkFlagFilename ( "metadata-from-file" , "json" ) ; err != nil {
ec . Logger . WithError ( err ) . Errorf ( "error while using a dependency library" )
}
2019-03-18 19:40:04 +03:00
2018-06-24 16:40:48 +03:00
return migrateCreateCmd
}
type migrateCreateOptions struct {
EC * cli . ExecutionContext
2019-03-18 19:40:04 +03:00
name string
flags * pflag . FlagSet
// Flags
2019-04-30 11:34:08 +03:00
fromServer bool
2019-03-18 19:40:04 +03:00
sqlFile string
2019-04-30 11:34:08 +03:00
sqlServer bool
2019-03-18 19:40:04 +03:00
metaDataFile string
metaDataServer bool
2022-04-25 16:05:26 +03:00
includeSchemas [ ] string
excludeSchemas [ ] string
2020-06-16 09:00:20 +03:00
upSQL string
2021-10-07 17:23:19 +03:00
upSQLChanged bool
downSQLChanged bool
2020-06-16 09:00:20 +03:00
downSQL string
2021-03-08 14:59:35 +03:00
Source cli . Source
2018-06-24 16:40:48 +03:00
}
2019-04-03 14:29:58 +03:00
func ( o * migrateCreateOptions ) run ( ) ( version int64 , err error ) {
2018-06-24 16:40:48 +03:00
timestamp := getTime ( )
2021-03-08 14:59:35 +03:00
createOptions := mig . New ( timestamp , o . name , filepath . Join ( o . EC . MigrationDir , o . Source . Name ) )
2019-03-18 19:40:04 +03:00
2019-04-30 11:34:08 +03:00
if o . fromServer {
o . sqlServer = true
2020-06-16 09:00:20 +03:00
}
2019-04-30 11:34:08 +03:00
var migrateDrv * migrate . Migrate
2021-06-21 16:54:08 +03:00
// disabling auto state migrations for migrate create command
o . EC . DisableAutoStateMigration = true
2021-10-07 17:23:19 +03:00
if o . sqlServer || o . upSQLChanged || o . downSQLChanged {
2021-03-08 14:59:35 +03:00
migrateDrv , err = migrate . NewMigrate ( o . EC , true , o . Source . Name , o . Source . Kind )
2019-04-30 11:34:08 +03:00
if err != nil {
2021-10-13 17:38:07 +03:00
return 0 , fmt . Errorf ( "cannot create migrate instance: %w" , err )
2019-04-30 11:34:08 +03:00
}
}
2021-10-07 17:23:19 +03:00
if o . sqlFile != "" {
2019-03-18 19:40:04 +03:00
// sql-file flag is set
err := createOptions . SetSQLUpFromFile ( o . sqlFile )
if err != nil {
2021-10-13 17:38:07 +03:00
return 0 , fmt . Errorf ( "cannot set sql file: %w" , err )
2019-03-18 19:40:04 +03:00
}
}
2019-04-30 11:34:08 +03:00
if o . sqlServer {
2022-04-25 16:05:26 +03:00
data , err := migrateDrv . ExportSchemaDump ( o . includeSchemas , o . excludeSchemas , o . Source . Name , o . Source . Kind )
2019-04-30 11:34:08 +03:00
if err != nil {
2021-10-13 17:38:07 +03:00
return 0 , fmt . Errorf ( "cannot fetch schema dump: %w" , err )
}
err = createOptions . SetSQLUp ( string ( data ) )
if err != nil {
return 0 , fmt . Errorf ( "while writing data from server into the up.sql file: %w" , err )
2019-04-30 11:34:08 +03:00
}
2019-03-18 19:40:04 +03:00
}
2020-06-16 09:00:20 +03:00
// create pure sql based migrations here
2021-10-07 17:23:19 +03:00
if o . upSQLChanged {
2020-06-16 09:00:20 +03:00
err = createOptions . SetSQLUp ( o . upSQL )
if err != nil {
2021-10-13 17:38:07 +03:00
return 0 , fmt . Errorf ( "up migration with SQL string could not be created: %w" , err )
2020-06-16 09:00:20 +03:00
}
}
2021-10-07 17:23:19 +03:00
if o . downSQLChanged {
2020-06-16 09:00:20 +03:00
err = createOptions . SetSQLDown ( o . downSQL )
if err != nil {
2021-10-13 17:38:07 +03:00
return 0 , fmt . Errorf ( "down migration with SQL string could not be created: %w" , err )
2020-06-16 09:00:20 +03:00
}
}
2022-01-12 19:15:52 +03:00
if o . sqlFile == "" && ! o . sqlServer && o . EC . Config . Version != cli . V1 {
2020-02-25 09:46:11 +03:00
// Set empty data for [up|down].sql
2022-01-12 19:15:52 +03:00
if ! o . upSQLChanged {
createOptions . SQLUp = [ ] byte ( ` ` )
}
if ! o . downSQLChanged {
createOptions . SQLDown = [ ] byte ( ` ` )
}
2020-02-25 09:46:11 +03:00
}
2019-03-18 19:40:04 +03:00
defer func ( ) {
if err != nil {
2021-10-13 17:38:07 +03:00
if err := createOptions . Delete ( ) ; err != nil {
o . EC . Logger . Warnf ( "cannot delete dangling migrations: %v" , err )
}
2019-03-18 19:40:04 +03:00
}
} ( )
err = createOptions . Create ( )
2018-06-24 16:40:48 +03:00
if err != nil {
2021-10-13 17:38:07 +03:00
return 0 , fmt . Errorf ( "error creating migration files: %w" , err )
2018-06-24 16:40:48 +03:00
}
2021-10-13 17:38:07 +03:00
o . EC . Spinner . Stop ( )
2021-10-07 17:23:19 +03:00
o . EC . Logger . Infof ( "Created Migrations" )
2021-09-22 14:47:37 +03:00
if o . fromServer {
opts := & MigrateApplyOptions {
EC : o . EC ,
SkipExecution : true ,
VersionMigration : fmt . Sprintf ( "%d" , timestamp ) ,
Source : o . Source ,
}
err := opts . Run ( )
if err != nil {
o . EC . Logger . Warnf ( "cannot mark created migration %d as applied: %v" , timestamp , err )
o . EC . Logger . Warnf ( "manually mark it as applied using command: hasura migrate apply --skip-execution --version %d" , timestamp )
}
}
2019-04-30 11:34:08 +03:00
return timestamp , nil
2018-06-24 16:40:48 +03:00
}
func getTime ( ) int64 {
startTime := time . Now ( )
return startTime . UnixNano ( ) / int64 ( time . Millisecond )
}