mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 17:31:56 +03:00
f0bef2b482
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/2509 Co-authored-by: Aravind K P <8335904+scriptonist@users.noreply.github.com> GitOrigin-RevId: 03ee878072b58c6dc7f43af9d26d30a5594d9a50
390 lines
11 KiB
Go
390 lines
11 KiB
Go
package commands
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/hasura/graphql-engine/cli/v2"
|
|
"github.com/hasura/graphql-engine/cli/v2/internal/fsm"
|
|
"github.com/hasura/graphql-engine/cli/v2/util"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
func NewDeployCmd(ec *cli.ExecutionContext) *cobra.Command {
|
|
v := viper.New()
|
|
opts := &DeployOptions{
|
|
EC: ec,
|
|
}
|
|
deployCmd := &cobra.Command{
|
|
Use: "deploy",
|
|
Short: "(PREVIEW) Utility command to apply metadata & database migrations to graphql-engine",
|
|
Example: `
|
|
# Apply metadata and migrations on Hasura GraphQL engine
|
|
hasura deploy
|
|
|
|
# Use with admin secret:
|
|
hasura deploy --admin-secret "<admin-secret>"
|
|
|
|
# Use with endpoint:
|
|
hasura deploy --endpoint "<endpoint>"`,
|
|
SilenceUsage: false,
|
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
|
cmd.Root().PersistentPreRun(cmd, args)
|
|
ec.Viper = v
|
|
err := ec.Prepare()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := ec.Validate(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
},
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return opts.Run()
|
|
},
|
|
}
|
|
|
|
f := deployCmd.Flags()
|
|
f.String("endpoint", "", "http(s) endpoint for Hasura GraphQL engine")
|
|
f.String("admin-secret", "", "admin secret for Hasura GraphQL engine")
|
|
f.String("access-key", "", "access key for Hasura GraphQL engine")
|
|
if err := f.MarkDeprecated("access-key", "use --admin-secret instead"); err != nil {
|
|
ec.Logger.WithError(err).Errorf("error while using a dependency library")
|
|
}
|
|
f.Bool("insecure-skip-tls-verify", false, "skip TLS verification and disable cert checking (default: false)")
|
|
f.String("certificate-authority", "", "path to a cert file for the certificate authority")
|
|
f.Bool("disable-interactive", false, "disables interactive prompts (default: false)")
|
|
|
|
util.BindPFlag(v, "endpoint", f.Lookup("endpoint"))
|
|
util.BindPFlag(v, "admin_secret", f.Lookup("admin-secret"))
|
|
util.BindPFlag(v, "access_key", f.Lookup("access-key"))
|
|
util.BindPFlag(v, "insecure_skip_tls_verify", f.Lookup("insecure-skip-tls-verify"))
|
|
util.BindPFlag(v, "certificate_authority", f.Lookup("certificate-authority"))
|
|
util.BindPFlag(v, "disable_interactive", f.Lookup("disable-interactive"))
|
|
|
|
f.BoolVar(&ec.DisableAutoStateMigration, "disable-auto-state-migration", false, "after a config v3 update, disable automatically moving state from hdb_catalog.schema_migrations to catalog state")
|
|
if err := f.MarkHidden("disable-auto-state-migration"); err != nil {
|
|
ec.Logger.WithError(err).Errorf("error while using a dependency library")
|
|
}
|
|
|
|
return deployCmd
|
|
}
|
|
|
|
type DeployOptions struct {
|
|
EC *cli.ExecutionContext
|
|
}
|
|
|
|
func (opts *DeployOptions) Run() error {
|
|
opts.EC.Config.DisableInteractive = true
|
|
|
|
context := &deployCtx{
|
|
ec: opts.EC,
|
|
logger: opts.EC.Logger,
|
|
err: nil,
|
|
}
|
|
|
|
if opts.EC.Config.Version <= cli.V2 {
|
|
configV2FSM := newConfigV2DeployFSM()
|
|
if err := configV2FSM.SendEvent(applyMigrations, context); err != nil {
|
|
return err
|
|
}
|
|
if configV2FSM.Current == failedOperation {
|
|
return fmt.Errorf("operation failed: %w", context.err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
configV3FSM := newConfigV3DeployFSM()
|
|
if err := configV3FSM.SendEvent(applyInitialMetadata, context); err != nil {
|
|
return err
|
|
}
|
|
if configV3FSM.Current == failedOperation {
|
|
return fmt.Errorf("operation failed: %w", context.err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type stateType = fsm.StateType
|
|
|
|
const (
|
|
applyingInitialMetadata stateType = "Applying Initial Metadata"
|
|
applyingInitialMetadataFailed stateType = "Applying Initial Metadata Failed"
|
|
applyingMetadata stateType = "Applying Metadata"
|
|
applyingMetadataFailed stateType = "Applying Metadata Failed"
|
|
applyingMigrations stateType = "Applying Migrations"
|
|
applyingMigrationsFailed stateType = "Applying Migrations Failed"
|
|
reloadingMetadata stateType = "Reloading Metadata"
|
|
reloadingMetadataFailed stateType = "Reloading Metadata Failed"
|
|
failedOperation stateType = "Operation Failed"
|
|
)
|
|
|
|
type eventType = fsm.EventType
|
|
|
|
const (
|
|
applyInitialMetadata eventType = "Apply Initial Metadata"
|
|
applyInitialMetadataFailed eventType = "Apply Initial Metadata Failed"
|
|
applyMetadata eventType = "Apply Metadata"
|
|
applyMetadataFailed eventType = "Apply Metadata Failed"
|
|
applyMigrations eventType = "Apply Migrations"
|
|
applyMigrationsFailed eventType = "Apply Migrations Failed"
|
|
reloadMetadata eventType = "Reload Metadata"
|
|
reloadMetadataFailed eventType = "Reload Metadata Failed"
|
|
failOperation eventType = "Operation Failed"
|
|
)
|
|
|
|
type deployCtx struct {
|
|
ec *cli.ExecutionContext
|
|
logger *logrus.Logger
|
|
err error
|
|
}
|
|
|
|
type applyingInitialMetadataAction struct{}
|
|
|
|
func (a *applyingInitialMetadataAction) Execute(ctx fsm.EventContext) eventType {
|
|
context := ctx.(*deployCtx)
|
|
opts := MetadataApplyOptions{
|
|
EC: context.ec,
|
|
}
|
|
context.logger.Debug(applyingInitialMetadata)
|
|
if err := opts.Run(); err != nil {
|
|
context.err = err
|
|
return applyInitialMetadataFailed
|
|
}
|
|
return applyMigrations
|
|
}
|
|
|
|
type applyingInitialMetadataFailedAction struct{}
|
|
|
|
func (a *applyingInitialMetadataFailedAction) Execute(ctx fsm.EventContext) eventType {
|
|
context := ctx.(*deployCtx)
|
|
context.logger.Debug(applyingInitialMetadataFailed)
|
|
if context.err != nil {
|
|
context.logger.Errorf("applying metadata failed")
|
|
context.logger.Info("This can happen when metadata in your project metadata directory is malformed")
|
|
}
|
|
return failOperation
|
|
}
|
|
|
|
type applyingMigrationsAction struct{}
|
|
|
|
func (a *applyingMigrationsAction) Execute(ctx fsm.EventContext) eventType {
|
|
context := ctx.(*deployCtx)
|
|
context.logger.Debug(applyingMigrations)
|
|
disableInteractive := context.ec.Config.DisableInteractive
|
|
defer func() { context.ec.Config.DisableInteractive = disableInteractive }()
|
|
|
|
context.ec.Config.DisableInteractive = true
|
|
opts := MigrateApplyOptions{
|
|
EC: context.ec,
|
|
}
|
|
opts.EC.AllDatabases = true
|
|
if err := opts.Run(); err != nil {
|
|
context.err = err
|
|
return applyMigrationsFailed
|
|
}
|
|
return applyMetadata
|
|
}
|
|
|
|
type applyingMigrationsFailedAction struct{}
|
|
|
|
func (a *applyingMigrationsFailedAction) Execute(ctx fsm.EventContext) eventType {
|
|
context := ctx.(*deployCtx)
|
|
context.logger.Debug(applyingMigrationsFailed)
|
|
if context.err != nil {
|
|
context.logger.Errorf("applying migrations failed")
|
|
}
|
|
return failOperation
|
|
}
|
|
|
|
type applyingMetadataAction struct{}
|
|
|
|
func (a *applyingMetadataAction) Execute(ctx fsm.EventContext) eventType {
|
|
context := ctx.(*deployCtx)
|
|
context.logger.Debug(applyingMetadata)
|
|
opts := MetadataApplyOptions{
|
|
EC: context.ec,
|
|
}
|
|
opts.EC.Spin("Applying metadata...")
|
|
if err := opts.Run(); err != nil {
|
|
opts.EC.Spinner.Stop()
|
|
context.err = err
|
|
return applyMetadataFailed
|
|
}
|
|
opts.EC.Spinner.Stop()
|
|
opts.EC.Logger.Info("Metadata applied")
|
|
return reloadMetadata
|
|
}
|
|
|
|
type applyingMetadataFailedAction struct{}
|
|
|
|
func (a *applyingMetadataFailedAction) Execute(ctx fsm.EventContext) eventType {
|
|
context := ctx.(*deployCtx)
|
|
context.logger.Debug(applyingMetadataFailed)
|
|
if context.err != nil {
|
|
context.logger.Errorf("applying metadata failed")
|
|
}
|
|
return failOperation
|
|
}
|
|
|
|
type reloadingMetadataAction struct{}
|
|
|
|
func (a *reloadingMetadataAction) Execute(ctx fsm.EventContext) eventType {
|
|
context := ctx.(*deployCtx)
|
|
context.logger.Debug(reloadingMetadata)
|
|
opts := MetadataReloadOptions{
|
|
EC: context.ec,
|
|
}
|
|
if err := opts.runWithInfo(); err != nil {
|
|
context.err = err
|
|
return reloadMetadataFailed
|
|
}
|
|
return fsm.NoOp
|
|
}
|
|
|
|
type reloadingMetadataFailedAction struct{}
|
|
|
|
func (a *reloadingMetadataFailedAction) Execute(ctx fsm.EventContext) eventType {
|
|
context := ctx.(*deployCtx)
|
|
context.logger.Debug(reloadingMetadataFailed)
|
|
if context.err != nil {
|
|
context.logger.Errorf("reloading metadata failed")
|
|
}
|
|
return failOperation
|
|
}
|
|
|
|
type failedOperationAction struct{}
|
|
|
|
func (a *failedOperationAction) Execute(ctx fsm.EventContext) eventType {
|
|
context := ctx.(*deployCtx)
|
|
context.logger.Debug(failedOperation)
|
|
return fsm.NoOp
|
|
}
|
|
|
|
func newConfigV3DeployFSM() *fsm.StateMachine {
|
|
type State = fsm.State
|
|
type States = fsm.States
|
|
type Events = fsm.Events
|
|
return &fsm.StateMachine{
|
|
States: States{
|
|
fsm.Default: State{
|
|
Events: Events{
|
|
applyInitialMetadata: applyingInitialMetadata,
|
|
},
|
|
},
|
|
applyingInitialMetadata: State{
|
|
Action: &applyingInitialMetadataAction{},
|
|
Events: Events{
|
|
applyInitialMetadataFailed: applyingInitialMetadataFailed,
|
|
applyMigrations: applyingMigrations,
|
|
},
|
|
},
|
|
applyingInitialMetadataFailed: State{
|
|
Action: &applyingInitialMetadataFailedAction{},
|
|
Events: Events{
|
|
failOperation: failedOperation,
|
|
},
|
|
},
|
|
applyingMigrations: State{
|
|
Action: &applyingMigrationsAction{},
|
|
Events: Events{
|
|
applyMigrationsFailed: applyingMigrationsFailed,
|
|
applyMetadata: applyingMetadata,
|
|
},
|
|
},
|
|
applyingMigrationsFailed: State{
|
|
Action: &applyingMigrationsFailedAction{},
|
|
Events: Events{
|
|
failOperation: failedOperation,
|
|
},
|
|
},
|
|
applyingMetadata: State{
|
|
Action: &applyingMetadataAction{},
|
|
Events: Events{
|
|
applyMetadataFailed: applyingMetadataFailed,
|
|
reloadMetadata: reloadingMetadata,
|
|
},
|
|
},
|
|
applyingMetadataFailed: State{
|
|
Action: &applyingMetadataFailedAction{},
|
|
Events: Events{
|
|
failOperation: failedOperation,
|
|
},
|
|
},
|
|
reloadingMetadata: State{
|
|
Action: &reloadingMetadataAction{},
|
|
Events: Events{
|
|
reloadMetadataFailed: reloadingMetadataFailed,
|
|
},
|
|
},
|
|
reloadingMetadataFailed: State{
|
|
Action: &reloadingMetadataFailedAction{},
|
|
Events: Events{
|
|
failOperation: failedOperation,
|
|
},
|
|
},
|
|
failedOperation: State{
|
|
Action: &failedOperationAction{},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func newConfigV2DeployFSM() *fsm.StateMachine {
|
|
type State = fsm.State
|
|
type States = fsm.States
|
|
type Events = fsm.Events
|
|
return &fsm.StateMachine{
|
|
States: States{
|
|
fsm.Default: State{
|
|
Events: Events{
|
|
applyMigrations: applyingMigrations,
|
|
failOperation: failedOperation,
|
|
},
|
|
},
|
|
applyingMigrations: State{
|
|
Action: &applyingMigrationsAction{},
|
|
Events: Events{
|
|
applyMigrationsFailed: applyingMigrationsFailed,
|
|
applyMetadata: applyingMetadata,
|
|
},
|
|
},
|
|
applyingMigrationsFailed: State{
|
|
Action: &applyingMigrationsFailedAction{},
|
|
Events: Events{
|
|
failOperation: failedOperation,
|
|
},
|
|
},
|
|
applyingMetadata: State{
|
|
Action: &applyingMetadataAction{},
|
|
Events: Events{
|
|
applyMetadataFailed: applyingMetadataFailed,
|
|
reloadMetadata: reloadingMetadata,
|
|
},
|
|
},
|
|
applyingMetadataFailed: State{
|
|
Action: &applyingMetadataFailedAction{},
|
|
Events: Events{
|
|
failOperation: failedOperation,
|
|
},
|
|
},
|
|
reloadingMetadata: State{
|
|
Action: &reloadingMetadataAction{},
|
|
Events: Events{
|
|
reloadMetadataFailed: reloadingMetadataFailed,
|
|
},
|
|
},
|
|
reloadingMetadataFailed: State{
|
|
Action: &reloadingMetadataFailedAction{},
|
|
Events: Events{
|
|
failOperation: failedOperation,
|
|
},
|
|
},
|
|
failedOperation: State{
|
|
Action: &failedOperationAction{},
|
|
},
|
|
},
|
|
}
|
|
}
|