Skip to content

Commit

Permalink
feature/inquirer (#91)
Browse files Browse the repository at this point in the history
* Add bubble tea library

* Add first pass at namespace form, added new --generate flag so no args/flags renders said form

* Add a more robust prompt solution to generate-config

* Move all prompts logic to own module

* add some documentation

* Update namespace placeholder, add config set prompts

* Generate Config form: swap order of templates, prepopulate filename with default.
  • Loading branch information
Jonesy committed Dec 7, 2023
1 parent a0ec60c commit e2e7519
Show file tree
Hide file tree
Showing 8 changed files with 629 additions and 31 deletions.
54 changes: 50 additions & 4 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (

"github.com/MakeNowJust/heredoc/v2"
"github.com/bcgov/gwa-cli/pkg"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand Down Expand Up @@ -49,7 +51,7 @@ func NewConfigGetCmd(_ *pkg.AppContext) *cobra.Command {
return configGetCmd
}

func NewConfigSetCmd(_ *pkg.AppContext) *cobra.Command {
func NewConfigSetCmd(ctx *pkg.AppContext) *cobra.Command {
var configSetCmd = &cobra.Command{
Use: "set [key] [value]",
Short: "Write a specific global setting",
Expand All @@ -67,8 +69,9 @@ Exposes some specific config values that can be defined by the user.
$ gwa config set namespace ns-sampler
$ gwa config set --namespace ns-sampler
`),
RunE: func(_ *cobra.Command, args []string) error {
if len(args) > 1 {
RunE: func(cmd *cobra.Command, args []string) error {
totalArgs := len(args)
if totalArgs > 1 {
switch args[0] {
case "token":
viper.Set("api_key", args[1])
Expand All @@ -83,10 +86,18 @@ $ gwa config set --namespace ns-sampler
}
}

if len(args) == 1 {
if totalArgs == 1 {
return fmt.Errorf("No value was set for %s", args)
}

if totalArgs == 0 && !cmd.HasFlags() {
model := initialSetModel(ctx)
if _, err := tea.NewProgram(model).Run(); err != nil {
return err
}
return nil
}

err := viper.WriteConfig()
if err != nil {
return err
Expand All @@ -107,3 +118,38 @@ $ gwa config set --namespace ns-sampler

return configSetCmd
}

const (
key = iota
value
)

func initialSetModel(ctx *pkg.AppContext) pkg.GenerateModel {
var prompts = make([]pkg.PromptField, 2)

prompts[key] = pkg.NewList("Select a config key to set", []string{"host", "namespace", "scheme", "token"})
prompts[value] = pkg.NewTextInput("Value", "", true)

m := pkg.GenerateModel{
Action: setConfigValue,
Ctx: ctx,
Prompts: prompts,
Spinner: spinner.New(),
}

return m
}

func setConfigValue(m pkg.GenerateModel) tea.Cmd {
return func() tea.Msg {
key := m.Prompts[key].Value
value := m.Prompts[value].TextInput.Value()

viper.Set(key, value)
err := viper.WriteConfig()
if err != nil {
return pkg.PromptOutputErr{Err: err}
}
return pkg.PromptCompleteEvent("")
}
}
95 changes: 90 additions & 5 deletions cmd/generateConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (
"net/url"
"os"
"path"
"strings"

"github.com/MakeNowJust/heredoc/v2"
"github.com/bcgov/gwa-cli/pkg"
tea "github.com/charmbracelet/bubbletea"
"github.com/spf13/cobra"
)

Expand All @@ -27,6 +29,10 @@ type GenerateConfigOptions struct {
Out string
}

func (o *GenerateConfigOptions) IsEmpty() bool {
return o.Template == "" && o.Service == "" && o.Upstream == ""
}

func (o *GenerateConfigOptions) ValidateTemplate() error {
if o.Template == "kong-httpbin" || o.Template == "client-credentials-shared-idp" {
return nil
Expand Down Expand Up @@ -64,6 +70,19 @@ func (o *GenerateConfigOptions) ParseUpstream() error {
return nil
}

func (o *GenerateConfigOptions) ImportFromForm(m pkg.GenerateModel) tea.Cmd {
return func() tea.Msg {
o.Service = m.Prompts[service].Value
o.Template = m.Prompts[template].Value
o.Upstream = m.Prompts[upstream].Value
o.Organization = m.Prompts[organization].Value
o.OrganizationUnit = m.Prompts[orgUnit].Value
o.Out = m.Prompts[outfile].Value
return pkg.PromptCompleteEvent("")
}

}

func NewGenerateConfigCmd(ctx *pkg.AppContext) *cobra.Command {
opts := &GenerateConfigOptions{}
var generateConfigCmd = &cobra.Command{
Expand All @@ -79,8 +98,31 @@ $ gwa generate-config --template client-credentials-shared-idp \
--service my-service \
--upstream https://httpbin.org
`),
PreRun: func(cmd *cobra.Command, _ []string) {
if !opts.IsEmpty() {
cmd.MarkFlagRequired("template")
cmd.MarkFlagRequired("service")
cmd.MarkFlagRequired("upstream")
}
},
RunE: func(_ *cobra.Command, _ []string) error {
if ctx.Namespace == "" {
fmt.Println(heredoc.Doc(`
A namespace must be set via the config command
Example:
$ gwa config set namespace YOUR_NAMESPACE_NAME
`),
)
return fmt.Errorf("No namespace has been set")
}
opts.Namespace = ctx.Namespace
if opts.IsEmpty() {
model := initGenerateModel(ctx, opts)
if _, err := tea.NewProgram(model).Run(); err != nil {
return err
}
}
err := opts.Exec()
if err != nil {
return err
Expand All @@ -91,8 +133,8 @@ $ gwa generate-config --template client-credentials-shared-idp \
return err
}

output := fmt.Sprintf("File %s created", opts.Out)
fmt.Println(pkg.PrintSuccess(output))
output := fmt.Sprintf("\n%s File %s created", pkg.Checkmark(), opts.Out)
fmt.Println(output)

return nil
},
Expand All @@ -104,9 +146,6 @@ $ gwa generate-config --template client-credentials-shared-idp \
generateConfigCmd.Flags().StringVar(&opts.Organization, "org", "ministry-of-citizens-services", "Set the organization")
generateConfigCmd.Flags().StringVar(&opts.OrganizationUnit, "org-unit", "databc", "Set the organization unit")
generateConfigCmd.Flags().StringVarP(&opts.Out, "out", "o", "gw-config.yml", "The file to output the generate config to")
generateConfigCmd.MarkFlagRequired("template")
generateConfigCmd.MarkFlagRequired("service")
generateConfigCmd.MarkFlagRequired("upstream")

return generateConfigCmd
}
Expand Down Expand Up @@ -137,3 +176,49 @@ func GenerateConfig(ctx *pkg.AppContext, opts *GenerateConfigOptions) error {
}
return nil
}

// Prompt Code
const (
service = iota
template
upstream
organization
orgUnit
outfile
)

func initGenerateModel(ctx *pkg.AppContext, opts *GenerateConfigOptions) pkg.GenerateModel {
var prompts = make([]pkg.PromptField, 6)

prompts[service] = pkg.NewTextInput("Service", "", true)
prompts[service].TextInput.Focus()

prompts[template] = pkg.NewList("Template", []string{
"client-credentials-shared-idp",
"kong-httpbin",
})

prompts[upstream] = pkg.NewTextInput("Upstream (URL)", "", true)
prompts[upstream].Validator = func(input string) error {
_, err := url.ParseRequestURI(input)
return err
}

prompts[organization] = pkg.NewTextInput("Organization", "", false)
prompts[orgUnit] = pkg.NewTextInput("Org Unit", "", false)
prompts[outfile] = pkg.NewTextInput("Filename", "Must be a YAML file", true)
prompts[outfile].TextInput.SetValue("gw-config.yml")
prompts[outfile].Validator = func(input string) error {
if strings.HasSuffix(input, ".yml") || strings.HasSuffix(input, ".yaml") {
return nil
}
return fmt.Errorf("Filename %s is invalid. Only YAML files are accepted.", pkg.BoldStyle.Underline(true).Render(input))
}

model := pkg.GenerateModel{
Action: opts.ImportFromForm,
Ctx: ctx,
Prompts: prompts,
}
return model
}

0 comments on commit e2e7519

Please sign in to comment.