-
Notifications
You must be signed in to change notification settings - Fork 2.7k
/
actions_create.go
166 lines (140 loc) · 5.42 KB
/
actions_create.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package commands
import (
"fmt"
"strings"
"github.com/hasura/graphql-engine/cli/v2/internal/hasura"
"github.com/hasura/graphql-engine/cli/v2"
"github.com/hasura/graphql-engine/cli/v2/internal/errors"
"github.com/hasura/graphql-engine/cli/v2/internal/metadataobject/actions"
"github.com/hasura/graphql-engine/cli/v2/internal/metadataobject/actions/types"
"github.com/hasura/graphql-engine/cli/v2/util"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func newActionsCreateCmd(ec *cli.ExecutionContext, v *viper.Viper) *cobra.Command {
opts := &actionsCreateOptions{
EC: ec,
}
actionsCreateCmd := &cobra.Command{
Use: "create [action-name]",
Short: "Create a Hasura Action",
Long: `This command allows you to create an Action to extend Hasura's schema with custom business logic using queries and mutations. Optional flags can be used to derive the Action from an existing GraphQL query or mutation. Additionally, codegen can be bundled with the creation of the Action to provide you ready-to-use boilerplate with your framework of choice.
Further Reading:
- https://hasura.io/docs/latest/actions/create/
`,
Example: ` # Create a Hasura Action
hasura actions create [action-name]
# Create a Hasura Action with codegen
hasura actions create [action-name] --with-codegen
# Create a Hasura Action by deriving from a Hasura operation
hasura actions create [action-name] --derive-from ''
# Create a Hasura Action with a different kind or webhook
hasura actions create [action-name] --kind [synchronous|asynchronous] --webhook [http://localhost:3000]`,
SilenceUsage: true,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
op := genOpName(cmd, "RunE")
opts.name = args[0]
if err := opts.run(); err != nil {
return errors.E(op, err)
}
return nil
},
}
f := actionsCreateCmd.Flags()
f.StringVar(&opts.deriveFrom, "derive-from", "", "derive action from a Hasura operation")
f.BoolVar(&opts.withCodegen, "with-codegen", false, "create Action along with codegen")
f.String("kind", "", "kind to use in Action")
f.String("webhook", "", "webhook to use in Action")
// bind to viper
util.BindPFlag(v, "actions.kind", f.Lookup("kind"))
util.BindPFlag(v, "actions.handler_webhook_baseurl", f.Lookup("webhook"))
return actionsCreateCmd
}
type actionsCreateOptions struct {
EC *cli.ExecutionContext
name string
deriveFrom string
withCodegen bool
}
func (o *actionsCreateOptions) run() error {
var op errors.Op = "commands.actionsCreateOptions.run"
var introSchema hasura.IntrospectionSchema
var err error
if o.deriveFrom != "" {
o.deriveFrom = strings.TrimSpace(o.deriveFrom)
o.EC.Spin("Deriving a Hasura operation...")
introSchema, err = o.EC.APIClient.V1Graphql.GetIntrospectionSchema()
if err != nil {
return errors.E(op, fmt.Errorf("error in fetching introspection schema: %w", err))
}
o.EC.Spinner.Stop()
}
// create new action
o.EC.Spin("Creating the action...")
actionCfg := actions.New(o.EC, o.EC.MetadataDir)
o.EC.Spinner.Stop()
err = actionCfg.Create(o.name, introSchema, o.deriveFrom)
if err != nil {
return errors.E(op, fmt.Errorf("error in creating action: %w", err))
}
opts := &MetadataApplyOptions{
EC: o.EC,
}
err = opts.Run()
if err != nil {
return errors.E(op, fmt.Errorf("error in applying metadata: %w", err))
}
o.EC.Logger.WithField("name", o.name).Infoln("action created")
// if codegen config not present, skip codegen
if o.EC.Config.ActionConfig.Codegen.Framework == "" {
if o.withCodegen {
return errors.E(op, fmt.Errorf(`could not find codegen config. For adding codegen config, run:
hasura actions use-codegen`))
}
return nil
}
// if with-codegen flag not present, ask them if they want to codegen
var confirmation bool
if !o.withCodegen {
confirmation, err = util.GetYesNoPrompt("Do you want to generate " + o.EC.Config.ActionConfig.Codegen.Framework + " code for this action and the custom types?")
if err != nil {
return errors.E(op, fmt.Errorf("error in getting user input: %w", err))
}
}
if !confirmation {
infoMsg := fmt.Sprintf(`You skipped codegen. For getting codegen for this action, run:
hasura actions codegen %s
`, o.name)
o.EC.Logger.Info(infoMsg)
return nil
}
if err := o.EC.SetupCodegenAssetsRepo(); err != nil {
o.EC.Logger.Errorf("failed generating code: setting up codegen-assets repo failed (this is required for automatically generating actions code): %v", err)
o.EC.Logger.Errorf("retry operation with: 'hasura actions codegen %s'", o.name)
return nil
}
// ensure codegen-assets repo exists
if err := ec.CodegenAssetsRepo.EnsureCloned(); err != nil {
o.EC.Logger.Errorf("failed generating code: pulling latest actions codegen files from internet failed: %v", err)
o.EC.Logger.Errorf("retry operation with: 'hasura actions codegen %s'", o.name)
return nil
}
// construct derive payload to send to codegenerator
derivePayload := types.DerivePayload{
IntrospectionSchema: introSchema,
Operation: o.deriveFrom,
ActionName: o.name,
}
// Run codegen
o.EC.Spin(fmt.Sprintf(`Running "hasura actions codegen %s"...`, o.name))
err = actionCfg.Codegen(o.name, derivePayload)
if err != nil {
o.EC.Spinner.Stop()
o.EC.Logger.Warn("codegen failed, retry with `hasura actions codegen`")
return errors.E(op, err)
}
o.EC.Spinner.Stop()
o.EC.Logger.Info("Codegen files generated at " + o.EC.Config.ActionConfig.Codegen.OutputDir)
return nil
}