graphql-engine/cli/commands/actions_use_codegen.go
Kali Vara Purushotham Santhati b75ab7233e cli: fix data-race warnings
#### Issue: https://github.com/hasura/graphql-engine-mono/issues/2179

### Problem:

1. There are few warnings when tests are executed with cli if it is built with `race` flag
It is because of a race condition between stdin in migrate_delete for the prompt and stdout for other commands.

2. The integration test of the console used to behave as follows:
    ```
     - Initially there won't be any wg.adds in wait-group so the second go-routine created in console-test (integration-test)
     - It will send the interrupt signal to the channel before the server has been started
     - So practically the server won't start
    ```
3. The read and write for consoleopts.WG has been happening in different go-routines without control or lock system(reading and writing into same memory location without lock system). So there is a chance of data-race(warning for data-race while integration tests are executed)
4. Data-race errors from `promptui` package

### Solution:

1. Use `--force` flag to avoid getting the input from the prompt in `migrate_delete_test.go`
2. Introduced two fields in server and console opts to let know other go-routines whether the server has been started or not
3. To avoid data-race above which are shared b/w go-routines to know the status of servers has been operated atomically.
4. using new package https://github.com/AlecAivazis/survey

https://github.com/hasura/graphql-engine-mono/pull/2231

GitOrigin-RevId: 387eb1be74f24dda34bb3588314b5a909adac227
2021-09-07 13:34:54 +00:00

173 lines
5.2 KiB
Go

package commands
import (
"fmt"
"path/filepath"
"sort"
"strconv"
"github.com/hasura/graphql-engine/cli/v2"
"github.com/hasura/graphql-engine/cli/v2/util"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
type codegenFramework struct {
Name string `json:"name"`
HasStarterKit bool `json:"hasStarterKit"`
}
func newActionsUseCodegenCmd(ec *cli.ExecutionContext) *cobra.Command {
opts := &actionsUseCodegenOptions{
EC: ec,
}
actionsUseCodegenCmd := &cobra.Command{
Use: "use-codegen",
Short: "Use the codegen to generate code for Hasura actions",
Example: ` # Use codegen by providing framework
hasura actions use-codegen --framework nodejs-express
# Use codegen from framework list
hasura actions use-codegen
# Set output directory
hasura actions use-codegen --output-dir codegen
# Use a codegen with a starter kit
hasura actions use-codegen --with-starter-kit true`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
return opts.run()
},
}
f := actionsUseCodegenCmd.Flags()
f.StringVar(&opts.framework, "framework", "", "framework to be used by codegen")
f.StringVar(&opts.outputDir, "output-dir", "", "directory to create the codegen files")
f.BoolVar(&opts.withStarterKit, "with-starter-kit", false, "clone starter kit for a framework")
return actionsUseCodegenCmd
}
type actionsUseCodegenOptions struct {
EC *cli.ExecutionContext
framework string
outputDir string
withStarterKit bool
}
func (o *actionsUseCodegenOptions) run() error {
o.EC.Spin("Ensuring codegen-assets repo is updated...")
defer o.EC.Spinner.Stop()
// ensure the the actions-codegen repo is updated
err := o.EC.CodegenAssetsRepo.EnsureUpdated()
if err != nil {
o.EC.Logger.Warnf("unable to update codegen-assets repo, got %v", err)
}
newCodegenExecutionConfig := o.EC.Config.ActionConfig.Codegen
newCodegenExecutionConfig.Framework = ""
o.EC.Spin("Fetching frameworks...")
allFrameworks, err := getCodegenFrameworks()
if err != nil {
return errors.Wrap(err, "error in fetching codegen frameworks")
}
o.EC.Spinner.Stop()
if o.framework == "" {
// if framework flag is not provided, display a list and allow them to choose
var frameworkList []string
for _, f := range allFrameworks {
frameworkList = append(frameworkList, f.Name)
}
sort.Strings(frameworkList)
newCodegenExecutionConfig.Framework, err = util.GetSelectPrompt("Choose a codegen framework to use", frameworkList)
if err != nil {
return errors.Wrap(err, "error in selecting framework")
}
} else {
for _, f := range allFrameworks {
if o.framework == f.Name {
newCodegenExecutionConfig.Framework = o.framework
}
}
if newCodegenExecutionConfig.Framework == "" {
return fmt.Errorf("framework %s is not found", o.framework)
}
}
hasStarterKit := false
for _, f := range allFrameworks {
if f.Name == newCodegenExecutionConfig.Framework && f.HasStarterKit {
hasStarterKit = true
}
}
// if with-starter-kit flag is set and the same is not available for the framework, return error
if o.withStarterKit && !hasStarterKit {
return fmt.Errorf("starter kit is not available for framework %s", newCodegenExecutionConfig.Framework)
}
// if with-starter-kit flag is not provided, give an option to clone a starterkit
if !o.withStarterKit && hasStarterKit {
shouldCloneStarterKit, err := util.GetYesNoPrompt("Do you also want to clone a starter kit for " + newCodegenExecutionConfig.Framework + "?")
if err != nil {
return errors.Wrap(err, "error in getting input from user")
}
o.withStarterKit = shouldCloneStarterKit
}
// clone the starter kit
o.EC.Spin("Clonning the starter kit...")
if o.withStarterKit && hasStarterKit {
// get a directory name to clone the starter kit in
starterKitDirname := newCodegenExecutionConfig.Framework
err = util.FSCheckIfDirPathExists(
filepath.Join(o.EC.ExecutionDirectory, starterKitDirname),
)
suffix := 2
for err == nil {
starterKitDirname = newCodegenExecutionConfig.Framework + "-" + strconv.Itoa(suffix)
suffix++
err = util.FSCheckIfDirPathExists(starterKitDirname)
}
err = nil
// copy the starter kit
destinationDir := filepath.Join(o.EC.ExecutionDirectory, starterKitDirname)
err = util.FSCopyDir(
filepath.Join(o.EC.GlobalConfigDir, util.ActionsCodegenDirName, newCodegenExecutionConfig.Framework, "starter-kit"),
destinationDir,
)
if err != nil {
return errors.Wrap(err, "error in copying starter kit")
}
o.EC.Logger.Info("Starter kit cloned at " + destinationDir)
}
o.EC.Spinner.Stop()
// if output directory is not provided, make them enter it
if o.outputDir == "" {
outputDir, err := util.GetFSPathPrompt("Where do you want to place the codegen files?", o.EC.Config.ActionConfig.Codegen.OutputDir)
if err != nil {
return errors.Wrap(err, "error in getting output directory input")
}
newCodegenExecutionConfig.OutputDir = outputDir
} else {
newCodegenExecutionConfig.OutputDir = o.outputDir
}
newConfig := o.EC.Config
newConfig.ActionConfig.Codegen = newCodegenExecutionConfig
err = o.EC.WriteConfig(newConfig)
if err != nil {
return errors.Wrap(err, "error in writing config")
}
o.EC.Spinner.Stop()
o.EC.Logger.Info("Codegen configuration updated in config.yaml")
return nil
}