From 19add8d1daa0c60a510d29697996f74660233a2d Mon Sep 17 00:00:00 2001 From: Danny Olson Date: Fri, 22 Mar 2024 10:35:27 -0700 Subject: [PATCH] feat: Add flags create command (#56) Add flags create command --- cmd/flags/create.go | 91 +++++++++++++++++++++++++++++++++++ cmd/flags/flags.go | 15 ++++++ cmd/projects/create.go | 14 ++---- cmd/projects/list.go | 1 + cmd/projects/projects.go | 4 +- cmd/root.go | 5 +- internal/flags/flags.go | 55 +++++++++++++++++++++ internal/setup/flag_toggle.go | 2 +- internal/setup/flags.go | 4 +- 9 files changed, 172 insertions(+), 19 deletions(-) create mode 100644 cmd/flags/create.go create mode 100644 cmd/flags/flags.go create mode 100644 internal/flags/flags.go diff --git a/cmd/flags/create.go b/cmd/flags/create.go new file mode 100644 index 00000000..5b2c5493 --- /dev/null +++ b/cmd/flags/create.go @@ -0,0 +1,91 @@ +package flags + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "ld-cli/internal/errors" + "ld-cli/internal/flags" +) + +func NewCreateCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create", + Short: "Create a new flag", + Long: "Create a new flag", + PreRunE: validate, + RunE: runCreate, + } + + cmd.Flags().StringP("data", "d", "", "Input data in JSON") + err := cmd.MarkFlagRequired("data") + if err != nil { + panic(err) + } + err = viper.BindPFlag("data", cmd.Flags().Lookup("data")) + if err != nil { + panic(err) + } + + cmd.Flags().String("projKey", "", "Project key") + err = cmd.MarkFlagRequired("projKey") + if err != nil { + panic(err) + } + err = viper.BindPFlag("projKey", cmd.Flags().Lookup("projKey")) + if err != nil { + panic(err) + } + + return cmd +} + +type inputData struct { + Name string `json:"name"` + Key string `json:"key"` +} + +func runCreate(cmd *cobra.Command, args []string) error { + client := flags.NewClient( + viper.GetString("accessToken"), + viper.GetString("baseUri"), + ) + + var data inputData + err := json.Unmarshal([]byte(cmd.Flags().Lookup("data").Value.String()), &data) + // err := json.Unmarshal([]byte(viper.GetString("data")), &data) + if err != nil { + return err + } + projKey := viper.GetString("projKey") + + response, err := client.Create( + context.Background(), + data.Name, + data.Key, + projKey, + ) + if err != nil { + return err + } + + fmt.Fprintf(cmd.OutOrStdout(), string(response)+"\n") + + return nil +} + +// validate ensures the flags are valid before using them. +// TODO: refactor with projects validate(). +func validate(cmd *cobra.Command, args []string) error { + _, err := url.ParseRequestURI(viper.GetString("baseUri")) + if err != nil { + return errors.ErrInvalidBaseURI + } + + return nil +} diff --git a/cmd/flags/flags.go b/cmd/flags/flags.go new file mode 100644 index 00000000..544faf7d --- /dev/null +++ b/cmd/flags/flags.go @@ -0,0 +1,15 @@ +package flags + +import "github.com/spf13/cobra" + +func NewFlagsCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "flags", + Short: "Make requests (list, create, etc.) on flags", + Long: "Make requests (list, create, etc.) on flags", + } + + cmd.AddCommand(NewCreateCmd()) + + return cmd +} diff --git a/cmd/projects/create.go b/cmd/projects/create.go index 3174cf4c..2a36867e 100644 --- a/cmd/projects/create.go +++ b/cmd/projects/create.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/spf13/cobra" "github.com/spf13/viper" @@ -19,14 +20,7 @@ func NewCreateCmd() *cobra.Command { RunE: runCreate, } - var data string - cmd.Flags().StringVarP( - &data, - "data", - "d", - "", - "Input data in JSON", - ) + cmd.Flags().StringP("data", "d", "", "Input data in JSON") err := cmd.MarkFlagRequired("data") if err != nil { panic(err) @@ -50,10 +44,8 @@ func runCreate(cmd *cobra.Command, args []string) error { viper.GetString("baseUri"), ) - dataStr := viper.GetString("data") - var data inputData - err := json.Unmarshal([]byte(dataStr), &data) + err := json.Unmarshal([]byte(viper.GetString("data")), &data) if err != nil { return err } diff --git a/cmd/projects/list.go b/cmd/projects/list.go index cfb9fc12..5fbd3f70 100644 --- a/cmd/projects/list.go +++ b/cmd/projects/list.go @@ -25,6 +25,7 @@ func NewListCmd() *cobra.Command { } // validate ensures the flags are valid before using them. +// TODO: refactor with flags validate(). func validate(cmd *cobra.Command, args []string) error { _, err := url.ParseRequestURI(viper.GetString("baseUri")) if err != nil { diff --git a/cmd/projects/projects.go b/cmd/projects/projects.go index 55d970ae..7fd0b523 100644 --- a/cmd/projects/projects.go +++ b/cmd/projects/projects.go @@ -1,8 +1,6 @@ package projects -import ( - "github.com/spf13/cobra" -) +import "github.com/spf13/cobra" func NewProjectsCmd() *cobra.Command { cmd := &cobra.Command{ diff --git a/cmd/root.go b/cmd/root.go index aefa2e9a..8bf40165 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,12 +5,12 @@ import ( "fmt" "os" - errs "ld-cli/internal/errors" - "github.com/spf13/cobra" "github.com/spf13/viper" + "ld-cli/cmd/flags" "ld-cli/cmd/projects" + errs "ld-cli/internal/errors" ) func newRootCommand() *cobra.Command { @@ -60,6 +60,7 @@ func newRootCommand() *cobra.Command { panic(err) } + cmd.AddCommand(flags.NewFlagsCmd()) cmd.AddCommand(projects.NewProjectsCmd()) cmd.AddCommand(setupCmd) diff --git a/internal/flags/flags.go b/internal/flags/flags.go new file mode 100644 index 00000000..2af80d26 --- /dev/null +++ b/internal/flags/flags.go @@ -0,0 +1,55 @@ +package flags + +import ( + "context" + "encoding/json" + + ldapi "github.com/launchdarkly/api-client-go/v14" + + "ld-cli/internal/errors" +) + +type Client interface { + Create(ctx context.Context, name string, key string, projectKey string) ([]byte, error) +} + +type FlagsClient struct { + client *ldapi.APIClient +} + +func NewClient(accessToken string, baseURI string) FlagsClient { + config := ldapi.NewConfiguration() + config.AddDefaultHeader("Authorization", accessToken) + config.Servers[0].URL = baseURI + client := ldapi.NewAPIClient(config) + + return FlagsClient{ + client: client, + } +} + +func (c FlagsClient) Create( + ctx context.Context, + name string, + key string, + projectKey string, +) ([]byte, error) { + post := ldapi.NewFeatureFlagBody(name, key) + flag, _, err := c.client.FeatureFlagsApi.PostFeatureFlag(ctx, projectKey).FeatureFlagBody(*post).Execute() + if err != nil { + switch err.Error() { + case "401 Unauthorized": + return nil, errors.ErrUnauthorized + case "403 Forbidden": + return nil, errors.ErrForbidden + default: + return nil, err + } + } + responseJSON, err := json.Marshal(flag) + if err != nil { + return nil, err + } + + return responseJSON, nil +} diff --git a/internal/setup/flag_toggle.go b/internal/setup/flag_toggle.go index 3d9ba8ad..cc2f4243 100644 --- a/internal/setup/flag_toggle.go +++ b/internal/setup/flag_toggle.go @@ -62,7 +62,7 @@ func (m flagToggleModel) View() string { return title + "\n\n" + toggleStyle.Render(toggle) + m.flagKey + furtherInstructions } -func (m flagToggleModel) toggleFlag() error { +func (m flagToggleModel) toggleFlag() error { //nolint:unused url := fmt.Sprintf("http://localhost/api/v2/flags/default/%s", m.flagKey) c := &http.Client{ Timeout: 10 * time.Second, diff --git a/internal/setup/flags.go b/internal/setup/flags.go index 193f8a1f..0b8de841 100644 --- a/internal/setup/flags.go +++ b/internal/setup/flags.go @@ -82,9 +82,9 @@ func (m flagModel) View() string { ) + "\n" } -const apiToken = "" +const apiToken = "" //nolint:unused -func (m flagModel) createFlag() error { +func (m flagModel) createFlag() error { //nolint:unused url := "http://localhost/api/v2/flags/default" c := &http.Client{ Timeout: 10 * time.Second,