2018-06-24 16:40:48 +03:00
package commands
import (
2019-03-18 19:40:04 +03:00
"io/ioutil"
"os"
2018-06-24 16:40:48 +03:00
"time"
"github.com/hasura/graphql-engine/cli"
2020-02-24 19:14:46 +03:00
"github.com/hasura/graphql-engine/cli/metadata"
metadataTypes "github.com/hasura/graphql-engine/cli/metadata/types"
2019-04-30 11:34:08 +03:00
"github.com/hasura/graphql-engine/cli/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
mig "github.com/hasura/graphql-engine/cli/migrate/cmd"
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
# Take pg_dump from server and save it as a migration and specify the schemas to include
hasura migrate create init -- sql - from - server -- schema myschema1 , myschema2 `
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" ,
Long : "Create sql and yaml 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 ) ,
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..." )
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
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" )
2019-04-30 11:34:08 +03:00
f . StringVar ( & opts . sqlFile , "sql-from-file" , "" , "path to an sql file which contains the SQL statements" )
2020-04-09 12:05:05 +03:00
f . BoolVar ( & opts . sqlServer , "sql-from-server" , false , "take pg_dump from server (default: public) and save it as a migration" )
f . StringSliceVar ( & opts . schemaNames , "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" )
2019-04-30 11:34:08 +03:00
2019-03-18 19:40:04 +03:00
migrateCreateCmd . MarkFlagFilename ( "sql-from-file" )
migrateCreateCmd . MarkFlagFilename ( "metadata-from-file" )
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
2019-04-30 11:34:08 +03:00
schemaNames [ ] string
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 ( )
2018-07-20 13:31:33 +03:00
createOptions := mig . New ( timestamp , o . name , o . EC . MigrationDir )
2019-03-18 19:40:04 +03:00
2019-04-30 11:34:08 +03:00
if o . fromServer {
o . sqlServer = true
2020-02-25 09:46:11 +03:00
if o . EC . Config . Version == cli . V1 {
o . metaDataServer = true
}
}
if o . flags . Changed ( "metadata-from-file" ) && o . EC . Config . Version != cli . V1 {
return 0 , errors . New ( "metadata-from-file flag can be set only with config version 1" )
}
if o . flags . Changed ( "metadata-from-server" ) && o . EC . Config . Version != cli . V1 {
return 0 , errors . New ( "metadata-from-server flag can be set only with config version 1" )
2019-04-30 11:34:08 +03:00
}
if o . flags . Changed ( "metadata-from-file" ) && o . sqlServer {
return 0 , errors . New ( "only one sql type can be set" )
}
if o . flags . Changed ( "metadata-from-file" ) && o . metaDataServer {
return 0 , errors . New ( "only one metadata type can be set" )
}
var migrateDrv * migrate . Migrate
if o . sqlServer || o . metaDataServer {
2020-04-07 12:23:20 +03:00
migrateDrv , err = migrate . NewMigrate ( o . EC , true )
2019-04-30 11:34:08 +03:00
if err != nil {
return 0 , errors . Wrap ( err , "cannot create migrate instance" )
}
}
2019-03-18 19:40:04 +03:00
if o . flags . Changed ( "sql-from-file" ) {
// sql-file flag is set
err := createOptions . SetSQLUpFromFile ( o . sqlFile )
if err != nil {
2019-04-03 14:29:58 +03:00
return 0 , errors . Wrap ( err , "cannot set sql file" )
2019-03-18 19:40:04 +03:00
}
}
2019-04-30 11:34:08 +03:00
if o . sqlServer {
data , err := migrateDrv . ExportSchemaDump ( o . schemaNames )
if err != nil {
return 0 , errors . Wrap ( err , "cannot fetch schema dump" )
}
createOptions . SetSQLUp ( string ( data ) )
2019-03-18 19:40:04 +03:00
}
if o . flags . Changed ( "metadata-from-file" ) {
// metadata-file flag is set
err := createOptions . SetMetaUpFromFile ( o . metaDataFile )
if err != nil {
2019-04-03 14:29:58 +03:00
return 0 , errors . Wrap ( err , "cannot set metadata file" )
2019-03-18 19:40:04 +03:00
}
}
2020-02-24 19:14:46 +03:00
// Create metadata migrations only if config version is V1
2020-02-25 09:46:11 +03:00
if o . metaDataServer {
2020-02-24 19:14:46 +03:00
// To create metadata.yaml, set metadata plugin
tmpDirName , err := ioutil . TempDir ( "" , "*" )
if err != nil {
return 0 , errors . Wrap ( err , "cannot create temp directory to fetch metadata" )
}
defer os . RemoveAll ( tmpDirName )
plugins := make ( metadataTypes . MetadataPlugins , 0 )
plugins = append ( plugins , metadata . New ( o . EC , tmpDirName ) )
migrateDrv . SetMetadataPlugins ( plugins )
2019-03-18 19:40:04 +03:00
// fetch metadata from server
2020-02-24 19:14:46 +03:00
files , err := migrateDrv . ExportMetadata ( )
2019-03-18 19:40:04 +03:00
if err != nil {
2019-04-03 14:29:58 +03:00
return 0 , errors . Wrap ( err , "cannot fetch metadata from server" )
2019-03-18 19:40:04 +03:00
}
2020-02-24 19:14:46 +03:00
err = migrateDrv . WriteMetadata ( files )
2019-03-18 19:40:04 +03:00
if err != nil {
2020-02-24 19:14:46 +03:00
return 0 , errors . Wrap ( err , "cannot write to tmp file" )
2019-03-18 19:40:04 +03:00
}
2020-02-24 19:14:46 +03:00
for name := range files {
err = createOptions . SetMetaUpFromFile ( name )
if err != nil {
return 0 , errors . Wrap ( err , "cannot parse metadata from the server" )
}
2019-03-18 19:40:04 +03:00
}
}
2020-02-25 09:46:11 +03:00
if ! o . flags . Changed ( "sql-from-file" ) && ! o . flags . Changed ( "metadata-from-file" ) && ! o . metaDataServer && ! o . sqlServer && o . EC . Config . Version == cli . V1 {
2019-03-18 19:40:04 +03:00
// Set empty data for [up|down].yaml
createOptions . MetaUp = [ ] byte ( ` [] ` )
createOptions . MetaDown = [ ] byte ( ` [] ` )
}
2020-02-25 09:46:11 +03:00
if ! o . flags . Changed ( "sql-from-file" ) && ! o . flags . Changed ( "metadata-from-file" ) && ! o . metaDataServer && ! o . sqlServer && o . EC . Config . Version != cli . V1 {
// Set empty data for [up|down].sql
createOptions . SQLUp = [ ] byte ( ` ` )
createOptions . SQLDown = [ ] byte ( ` ` )
}
2019-03-18 19:40:04 +03:00
defer func ( ) {
if err != nil {
createOptions . Delete ( )
}
} ( )
err = createOptions . Create ( )
2018-06-24 16:40:48 +03:00
if err != nil {
2019-04-03 14:29:58 +03:00
return 0 , errors . Wrap ( err , "error creating migration files" )
2018-06-24 16:40:48 +03:00
}
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 )
}