diff --git a/cmd/component/root.go b/cmd/component/root.go index cbf721a..8a75266 100644 --- a/cmd/component/root.go +++ b/cmd/component/root.go @@ -2,6 +2,7 @@ package component import ( "bunnyshell.com/cli/cmd/component/action" + "bunnyshell.com/cli/cmd/component/variable" "bunnyshell.com/cli/pkg/config" "bunnyshell.com/cli/pkg/util" "github.com/spf13/cobra" @@ -33,6 +34,17 @@ func init() { }, action.GetMainCommand().Commands(), ) + + util.AddGroupedCommands( + mainCmd, + cobra.Group{ + ID: "variables", + Title: "Commands for Component Variables:", + }, + []*cobra.Command{variable.GetMainCommand()}, + ) + + config.MainManager.CommandWithGlobalOptions(mainCmd) } func GetMainCommand() *cobra.Command { diff --git a/cmd/component/variable/action/create.go b/cmd/component/variable/action/create.go new file mode 100644 index 0000000..43d8227 --- /dev/null +++ b/cmd/component/variable/action/create.go @@ -0,0 +1,107 @@ +package action + +import ( + "fmt" + "io" + "os" + + "bunnyshell.com/cli/pkg/api/component_variable" + "bunnyshell.com/cli/pkg/config" + "bunnyshell.com/cli/pkg/lib" + "bunnyshell.com/cli/pkg/util" + "bunnyshell.com/sdk" + "github.com/spf13/cobra" +) + +func init() { + settings := config.GetSettings() + + createOptions := component_variable.NewCreateOptions() + var componentName string + + command := &cobra.Command{ + Use: "create", + + ValidArgsFunction: cobra.NoFileCompletions, + + PreRunE: func(cmd *cobra.Command, args []string) error { + hasStdin, err := util.IsStdinPresent() + if err != nil { + return err + } + + flags := cmd.Flags() + if !flags.Changed("value") && !hasStdin { + return errMissingValue + } + + if flags.Changed("value") && hasStdin { + return errMultipleValueInputs + } + + if !flags.Changed("component-name") { + cmd.MarkFlagRequired("component") + } + + return nil + }, + + RunE: func(cmd *cobra.Command, args []string) error { + createOptions.ServiceComponent = settings.Profile.Context.ServiceComponent + + if componentName != "" { + component, err := findComponentByName(componentName, &settings.Profile) + if err != nil { + return err + } + createOptions.ServiceComponent = component.GetId() + } + + hasStdin, err := util.IsStdinPresent() + if err != nil { + return err + } + + if hasStdin { + buf, err := io.ReadAll(os.Stdin) + if err != nil { + return err + } + + createOptions.Value = string(buf) + } + + model, err := component_variable.Create(createOptions) + if err != nil { + return lib.FormatCommandError(cmd, err) + } + + return lib.FormatCommandData(cmd, model) + }, + } + + flags := command.Flags() + + updateComponentIdentifierFlags(command, &componentName) + + createOptions.UpdateFlagSet(flags) + + mainCmd.AddCommand(command) +} + +func findComponentByName(componentName string, profile *config.Profile) (*sdk.ComponentCollection, error) { + matchedComponents, err := findComponentsByName(componentName, profile) + if err != nil { + return nil, fmt.Errorf("failed while searching for the component: %w", err) + } + + if matchedComponents.GetTotalItems() == 0 { + return nil, fmt.Errorf("component '%s' not found", componentName) + } + + if matchedComponents.GetTotalItems() > 1 { + return nil, fmt.Errorf("multiple components with the name '%s' were found", componentName) + } + + return &matchedComponents.GetEmbedded().Item[0], nil +} diff --git a/cmd/component/variable/action/delete.go b/cmd/component/variable/action/delete.go new file mode 100644 index 0000000..1b9d6a6 --- /dev/null +++ b/cmd/component/variable/action/delete.go @@ -0,0 +1,53 @@ +package action + +import ( + "bunnyshell.com/cli/pkg/api/component_variable" + "bunnyshell.com/cli/pkg/config" + "bunnyshell.com/cli/pkg/lib" + "github.com/spf13/cobra" +) + +func init() { + var componentName string + var componentVariableName string + + settings := config.GetSettings() + + deleteOptions := component_variable.NewDeleteOptions() + + command := &cobra.Command{ + Use: "delete", + + ValidArgsFunction: cobra.NoFileCompletions, + + RunE: func(cmd *cobra.Command, args []string) error { + if componentVariableName != "" { + componentVariable, err := findComponentVariableByName(componentVariableName, componentName, &settings.Profile) + if err != nil { + return err + } + deleteOptions.ID = componentVariable.GetId() + } + + err := component_variable.Delete(deleteOptions) + if err != nil { + return lib.FormatCommandError(cmd, err) + } + + cmd.Printf("\nComponent variable %s successfully deleted\n", deleteOptions.ID) + + return nil + }, + } + + flags := command.Flags() + + flags.AddFlag(GetIDOption(&deleteOptions.ID).GetFlag("id")) + flags.StringVar(&componentVariableName, "name", componentVariableName, "Component Variable Name") + command.MarkFlagsMutuallyExclusive("name", "id") + + updateComponentIdentifierFlags(command, &componentName) + command.MarkFlagsMutuallyExclusive("component-name", "id", "component") + + mainCmd.AddCommand(command) +} diff --git a/cmd/component/variable/action/edit.go b/cmd/component/variable/action/edit.go new file mode 100644 index 0000000..00a239f --- /dev/null +++ b/cmd/component/variable/action/edit.go @@ -0,0 +1,94 @@ +package action + +import ( + "io" + "os" + + "bunnyshell.com/cli/pkg/api/component_variable" + "bunnyshell.com/cli/pkg/config" + "bunnyshell.com/cli/pkg/lib" + "bunnyshell.com/cli/pkg/util" + "github.com/spf13/cobra" +) + +func init() { + var componentName string + var componentVariableName string + + settings := config.GetSettings() + + editOptions := component_variable.NewEditOptions("") + + command := &cobra.Command{ + Use: "edit", + + ValidArgsFunction: cobra.NoFileCompletions, + + PreRunE: func(cmd *cobra.Command, args []string) error { + hasStdin, err := util.IsStdinPresent() + if err != nil { + return err + } + + flags := cmd.Flags() + if flags.Changed("value") && hasStdin { + return errMultipleValueInputs + } + + if flags.Changed("name") && !flags.Changed("component-name") && settings.Profile.Context.ServiceComponent == "" { + return errComponentRequired + } + + return nil + }, + + RunE: func(cmd *cobra.Command, args []string) error { + flags := cmd.Flags() + if flags.Changed("value") { + editOptions.ServiceComponentVariableEditAction.SetValue(flags.Lookup("value").Value.String()) + } + + if componentVariableName != "" { + componentVariable, err := findComponentVariableByName(componentVariableName, componentName, &settings.Profile) + if err != nil { + return err + } + editOptions.ID = componentVariable.GetId() + } + + hasStdin, err := util.IsStdinPresent() + if err != nil { + return err + } + + if hasStdin { + buf, err := io.ReadAll(os.Stdin) + if err != nil { + return err + } + + editOptions.ServiceComponentVariableEditAction.SetValue(string(buf)) + } + + model, err := component_variable.Edit(editOptions) + if err != nil { + return lib.FormatCommandError(cmd, err) + } + + return lib.FormatCommandData(cmd, model) + }, + } + + flags := command.Flags() + + flags.AddFlag(GetIDOption(&editOptions.ID).GetFlag("id")) + flags.StringVar(&componentVariableName, "name", componentVariableName, "Component Variable Name") + command.MarkFlagsMutuallyExclusive("name", "id") + + updateComponentIdentifierFlags(command, &componentName) + command.MarkFlagsMutuallyExclusive("component-name", "id", "component") + + editOptions.UpdateFlagSet(flags) + + mainCmd.AddCommand(command) +} diff --git a/cmd/component/variable/action/root.go b/cmd/component/variable/action/root.go new file mode 100644 index 0000000..e1755d2 --- /dev/null +++ b/cmd/component/variable/action/root.go @@ -0,0 +1,112 @@ +package action + +import ( + "errors" + "fmt" + + "bunnyshell.com/cli/pkg/api/component" + "bunnyshell.com/cli/pkg/api/component_variable" + "bunnyshell.com/cli/pkg/build" + "bunnyshell.com/cli/pkg/config" + "bunnyshell.com/cli/pkg/config/option" + "bunnyshell.com/sdk" + "github.com/spf13/cobra" +) + +var ( + errMissingValue = errors.New("the plain value must be provided") + errMultipleValueInputs = errors.New("the value must be provided either by argument or by stdin, not both") + errComponentRequired = errors.New("either the 'component' or the 'component-name' arguments are required") +) + +var mainCmd = &cobra.Command{} + +func GetMainCommand() *cobra.Command { + return mainCmd +} + +func GetIDOption(value *string) *option.String { + help := fmt.Sprintf( + `Find available component variables with "%s variables list"`, + build.Name, + ) + + idOption := option.NewStringOption(value) + + idOption.AddFlagWithExtraHelp("id", "Component Variable Id", help) + + return idOption +} + +func updateComponentIdentifierFlags(cmd *cobra.Command, componentName *string) { + options := config.GetOptions() + + flags := cmd.Flags() + + flags.AddFlag(options.ServiceComponent.AddFlagWithExtraHelp( + "component", + "Component for the variable", + "Components contain multiple variables", + )) + + flags.StringVar(componentName, "component-name", *componentName, "Component Name") + cmd.MarkFlagsMutuallyExclusive("component-name", "component") + + flags.AddFlag(options.Organization.GetFlag("organization")) + flags.AddFlag(options.Project.GetFlag("project")) + flags.AddFlag(options.Environment.GetFlag("environment")) +} + +func findComponentsByName(componentName string, profile *config.Profile) (*sdk.PaginatedComponentCollection, error) { + listOptions := component.NewListOptions() + + listOptions.Organization = profile.Context.Organization + listOptions.Project = profile.Context.Project + listOptions.Environment = profile.Context.Environment + listOptions.Name = componentName + + components, error := component.List(listOptions) + if error != nil { + return nil, error + } + + return components, nil +} + +func findComponentVariablesByName(componentVariableName string, componentName string, profile *config.Profile) (*sdk.PaginatedServiceComponentVariableCollection, error) { + listOptions := component_variable.NewListOptions() + + listOptions.Organization = profile.Context.Organization + listOptions.Project = profile.Context.Project + listOptions.Environment = profile.Context.Environment + listOptions.Component = profile.Context.ServiceComponent + listOptions.Name = componentVariableName + + if componentName != "" { + listOptions.ComponentName = componentName + } + + component_variables, error := component_variable.List(listOptions) + if error != nil { + return nil, error + } + + return component_variables, nil +} + +func findComponentVariableByName(componentVariableName string, componentName string, profile *config.Profile) (*sdk.ServiceComponentVariableCollection, error) { + matchedComponentVariables, err := findComponentVariablesByName(componentVariableName, componentName, profile) + if err != nil { + return nil, fmt.Errorf("failed while searching for the component variable: %w", err) + } + + if matchedComponentVariables.GetTotalItems() == 0 { + return nil, fmt.Errorf("component variable '%s' not found", componentVariableName) + } + + if matchedComponentVariables.GetTotalItems() > 1 { + return nil, fmt.Errorf("multiple component variables '%s' were found", componentVariableName) + } + + return &matchedComponentVariables.GetEmbedded().Item[0], nil +} diff --git a/cmd/component/variable/list.go b/cmd/component/variable/list.go new file mode 100644 index 0000000..d2859a2 --- /dev/null +++ b/cmd/component/variable/list.go @@ -0,0 +1,44 @@ +package variable + +import ( + "bunnyshell.com/cli/pkg/api/component_variable" + "bunnyshell.com/cli/pkg/config" + "bunnyshell.com/cli/pkg/lib" + "github.com/spf13/cobra" +) + +func init() { + options := config.GetOptions() + settings := config.GetSettings() + + listOptions := component_variable.NewListOptions() + + command := &cobra.Command{ + Use: "list", + GroupID: mainGroup.ID, + + ValidArgsFunction: cobra.NoFileCompletions, + + RunE: func(cmd *cobra.Command, args []string) error { + listOptions.Organization = settings.Profile.Context.Organization + listOptions.Project = settings.Profile.Context.Project + listOptions.Environment = settings.Profile.Context.Environment + listOptions.Component = settings.Profile.Context.ServiceComponent + + return lib.ShowCollection(cmd, listOptions, func() (lib.ModelWithPagination, error) { + return component_variable.List(listOptions) + }) + }, + } + + flags := command.Flags() + + flags.AddFlag(options.Organization.GetFlag("organization")) + flags.AddFlag(options.Project.GetFlag("project")) + flags.AddFlag(options.Environment.GetFlag("environment")) + flags.AddFlag(options.ServiceComponent.GetFlag("component")) + + listOptions.UpdateFlagSet(flags) + + mainCmd.AddCommand(command) +} diff --git a/cmd/component/variable/root.go b/cmd/component/variable/root.go new file mode 100644 index 0000000..413d2c8 --- /dev/null +++ b/cmd/component/variable/root.go @@ -0,0 +1,39 @@ +package variable + +import ( + "bunnyshell.com/cli/cmd/component/variable/action" + "bunnyshell.com/cli/pkg/config" + "bunnyshell.com/cli/pkg/util" + "github.com/spf13/cobra" +) + +var mainCmd = &cobra.Command{ + Use: "variables", + Aliases: []string{"vars"}, + + Short: "Component Variables", +} + +var mainGroup = &cobra.Group{ + ID: "variables", + Title: "Commands for Component Variables:", +} + +func init() { + config.MainManager.CommandWithAPI(mainCmd) + + mainCmd.AddGroup(mainGroup) + + util.AddGroupedCommands( + mainCmd, + cobra.Group{ + ID: "actions", + Title: "Commands for Component variables Actions:", + }, + action.GetMainCommand().Commands(), + ) +} + +func GetMainCommand() *cobra.Command { + return mainCmd +} diff --git a/cmd/component/variable/show.go b/cmd/component/variable/show.go new file mode 100644 index 0000000..b3fe8d5 --- /dev/null +++ b/cmd/component/variable/show.go @@ -0,0 +1,39 @@ +package variable + +import ( + "bunnyshell.com/cli/pkg/api/component_variable" + "bunnyshell.com/cli/pkg/config" + "bunnyshell.com/cli/pkg/lib" + "github.com/spf13/cobra" +) + +func init() { + options := config.GetOptions() + settings := config.GetSettings() + + itemOptions := component_variable.NewItemOptions("") + + command := &cobra.Command{ + Use: "show", + GroupID: mainGroup.ID, + + ValidArgsFunction: cobra.NoFileCompletions, + + RunE: func(cmd *cobra.Command, args []string) error { + itemOptions.ID = settings.Profile.Context.ServiceComponent + + model, err := component_variable.Get(itemOptions) + if err != nil { + return lib.FormatCommandError(cmd, err) + } + + return lib.FormatCommandData(cmd, model) + }, + } + + flags := command.Flags() + + flags.AddFlag(options.ServiceComponent.GetRequiredFlag("id")) + + mainCmd.AddCommand(command) +} diff --git a/cmd/project_variable/action/create.go b/cmd/project_variable/action/create.go index 7fe8887..0868fde 100644 --- a/cmd/project_variable/action/create.go +++ b/cmd/project_variable/action/create.go @@ -1,6 +1,9 @@ package action import ( + "io" + "os" + "bunnyshell.com/cli/pkg/api/project_variable" "bunnyshell.com/cli/pkg/config" "bunnyshell.com/cli/pkg/lib" @@ -19,9 +22,41 @@ func init() { ValidArgsFunction: cobra.NoFileCompletions, + PreRunE: func(cmd *cobra.Command, args []string) error { + hasStdin, err := util.IsStdinPresent() + if err != nil { + return err + } + + flags := cmd.Flags() + if !flags.Changed("value") && !hasStdin { + return errMissingValue + } + + if flags.Changed("value") && hasStdin { + return errMultipleValueInputs + } + + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { createOptions.Project = settings.Profile.Context.Project + hasStdin, err := util.IsStdinPresent() + if err != nil { + return err + } + + if hasStdin { + buf, err := io.ReadAll(os.Stdin) + if err != nil { + return err + } + + createOptions.Value = string(buf) + } + model, err := project_variable.Create(createOptions) if err != nil { return lib.FormatCommandError(cmd, err) diff --git a/cmd/project_variable/action/delete.go b/cmd/project_variable/action/delete.go index 5634346..786432d 100644 --- a/cmd/project_variable/action/delete.go +++ b/cmd/project_variable/action/delete.go @@ -20,7 +20,7 @@ func init() { return lib.FormatCommandError(cmd, err) } - cmd.Printf("\nProject %s successfully deleted\n", deleteOptions.ID) + cmd.Printf("\nProject variable %s successfully deleted\n", deleteOptions.ID) return nil }, diff --git a/cmd/project_variable/action/edit.go b/cmd/project_variable/action/edit.go index fbd542b..f5181ad 100644 --- a/cmd/project_variable/action/edit.go +++ b/cmd/project_variable/action/edit.go @@ -1,8 +1,12 @@ package action import ( + "io" + "os" + "bunnyshell.com/cli/pkg/api/project_variable" "bunnyshell.com/cli/pkg/lib" + "bunnyshell.com/cli/pkg/util" "github.com/spf13/cobra" ) @@ -14,12 +18,40 @@ func init() { ValidArgsFunction: cobra.NoFileCompletions, + PreRunE: func(cmd *cobra.Command, args []string) error { + hasStdin, err := util.IsStdinPresent() + if err != nil { + return err + } + + flags := cmd.Flags() + if flags.Changed("value") && hasStdin { + return errMultipleValueInputs + } + + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { flags := cmd.Flags() if flags.Changed("value") { editOptions.ProjectVariableEditAction.SetValue(flags.Lookup("value").Value.String()) } + hasStdin, err := util.IsStdinPresent() + if err != nil { + return err + } + + if hasStdin { + buf, err := io.ReadAll(os.Stdin) + if err != nil { + return err + } + + editOptions.ProjectVariableEditAction.SetValue(string(buf)) + } + model, err := project_variable.Edit(editOptions) if err != nil { return lib.FormatCommandError(cmd, err) diff --git a/cmd/project_variable/action/root.go b/cmd/project_variable/action/root.go index 11a09a5..ade2fe3 100644 --- a/cmd/project_variable/action/root.go +++ b/cmd/project_variable/action/root.go @@ -1,6 +1,7 @@ package action import ( + "errors" "fmt" "bunnyshell.com/cli/pkg/build" @@ -8,6 +9,11 @@ import ( "github.com/spf13/cobra" ) +var ( + errMissingValue = errors.New("the plain value must be provided") + errMultipleValueInputs = errors.New("the value must be provided either by argument or by stdin, not both") +) + var mainCmd = &cobra.Command{} func GetMainCommand() *cobra.Command { diff --git a/cmd/secret/decrypt.go b/cmd/secret/decrypt.go index 78ef5d0..a4fb550 100644 --- a/cmd/secret/decrypt.go +++ b/cmd/secret/decrypt.go @@ -9,6 +9,7 @@ import ( "bunnyshell.com/cli/pkg/build" "bunnyshell.com/cli/pkg/config" "bunnyshell.com/cli/pkg/lib" + "bunnyshell.com/cli/pkg/util" "github.com/MakeNowJust/heredoc" "github.com/spf13/cobra" ) @@ -39,7 +40,7 @@ func init() { ValidArgsFunction: cobra.NoFileCompletions, PreRunE: func(cmd *cobra.Command, args []string) error { - hasStdin, err := isStdinPresent() + hasStdin, err := util.IsStdinPresent() if err != nil { return err } diff --git a/cmd/secret/definition.go b/cmd/secret/definition.go index 91625a3..d206214 100644 --- a/cmd/secret/definition.go +++ b/cmd/secret/definition.go @@ -43,7 +43,7 @@ func executeTranscriptConfiguration(options *secret.TranscriptConfigurationOptio } func validateDefinitionCommand(options *secret.TranscriptConfigurationOptions) error { - hasStdin, err := isStdinPresent() + hasStdin, err := util.IsStdinPresent() if err != nil { return err } diff --git a/cmd/secret/encrypt.go b/cmd/secret/encrypt.go index 9d8e3fe..1ac5589 100644 --- a/cmd/secret/encrypt.go +++ b/cmd/secret/encrypt.go @@ -9,6 +9,7 @@ import ( "bunnyshell.com/cli/pkg/build" "bunnyshell.com/cli/pkg/config" "bunnyshell.com/cli/pkg/lib" + "bunnyshell.com/cli/pkg/util" "github.com/MakeNowJust/heredoc" "github.com/spf13/cobra" ) @@ -39,7 +40,7 @@ func init() { ValidArgsFunction: cobra.NoFileCompletions, PreRunE: func(cmd *cobra.Command, args []string) error { - hasStdin, err := isStdinPresent() + hasStdin, err := util.IsStdinPresent() if err != nil { return err } diff --git a/cmd/secret/root.go b/cmd/secret/root.go index 9c2a7e9..1770b3d 100644 --- a/cmd/secret/root.go +++ b/cmd/secret/root.go @@ -1,8 +1,6 @@ package secret import ( - "os" - "bunnyshell.com/cli/pkg/config" "github.com/spf13/cobra" ) @@ -22,12 +20,3 @@ func init() { func GetMainCommand() *cobra.Command { return mainCmd } - -func isStdinPresent() (bool, error) { - fi, err := os.Stdin.Stat() - if err != nil { - return false, err - } - - return (fi.Mode() & os.ModeCharDevice) == 0, nil -} diff --git a/cmd/variable/action/create.go b/cmd/variable/action/create.go index 0a9adca..d4b67e5 100644 --- a/cmd/variable/action/create.go +++ b/cmd/variable/action/create.go @@ -1,6 +1,9 @@ package action import ( + "io" + "os" + "bunnyshell.com/cli/pkg/api/variable" "bunnyshell.com/cli/pkg/config" "bunnyshell.com/cli/pkg/lib" @@ -19,9 +22,41 @@ func init() { ValidArgsFunction: cobra.NoFileCompletions, + PreRunE: func(cmd *cobra.Command, args []string) error { + hasStdin, err := util.IsStdinPresent() + if err != nil { + return err + } + + flags := cmd.Flags() + if !flags.Changed("value") && !hasStdin { + return errMissingValue + } + + if flags.Changed("value") && hasStdin { + return errMultipleValueInputs + } + + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { createOptions.Environment = settings.Profile.Context.Environment + hasStdin, err := util.IsStdinPresent() + if err != nil { + return err + } + + if hasStdin { + buf, err := io.ReadAll(os.Stdin) + if err != nil { + return err + } + + createOptions.Value = string(buf) + } + model, err := variable.Create(createOptions) if err != nil { return lib.FormatCommandError(cmd, err) diff --git a/cmd/variable/action/edit.go b/cmd/variable/action/edit.go index 40aef9c..e12483a 100644 --- a/cmd/variable/action/edit.go +++ b/cmd/variable/action/edit.go @@ -1,8 +1,12 @@ package action import ( + "io" + "os" + "bunnyshell.com/cli/pkg/api/variable" "bunnyshell.com/cli/pkg/lib" + "bunnyshell.com/cli/pkg/util" "github.com/spf13/cobra" ) @@ -14,12 +18,40 @@ func init() { ValidArgsFunction: cobra.NoFileCompletions, + PreRunE: func(cmd *cobra.Command, args []string) error { + hasStdin, err := util.IsStdinPresent() + if err != nil { + return err + } + + flags := cmd.Flags() + if flags.Changed("value") && hasStdin { + return errMultipleValueInputs + } + + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { flags := cmd.Flags() if flags.Changed("value") { editOptions.EnvironmentVariableEditAction.SetValue(flags.Lookup("value").Value.String()) } + hasStdin, err := util.IsStdinPresent() + if err != nil { + return err + } + + if hasStdin { + buf, err := io.ReadAll(os.Stdin) + if err != nil { + return err + } + + editOptions.EnvironmentVariableEditAction.SetValue(string(buf)) + } + model, err := variable.Edit(editOptions) if err != nil { return lib.FormatCommandError(cmd, err) diff --git a/cmd/variable/action/root.go b/cmd/variable/action/root.go index 69f7f6f..9dd1a47 100644 --- a/cmd/variable/action/root.go +++ b/cmd/variable/action/root.go @@ -1,6 +1,7 @@ package action import ( + "errors" "fmt" "bunnyshell.com/cli/pkg/build" @@ -8,6 +9,11 @@ import ( "github.com/spf13/cobra" ) +var ( + errMissingValue = errors.New("the plain value must be provided") + errMultipleValueInputs = errors.New("the value must be provided either by argument or by stdin, not both") +) + var mainCmd = &cobra.Command{} func GetMainCommand() *cobra.Command { diff --git a/go.mod b/go.mod index a417265..c4c47d5 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,9 @@ replace github.com/imdario/mergo => github.com/imdario/mergo v0.3.16 require ( bunnyshell.com/dev v0.5.6 - bunnyshell.com/sdk v0.15.3 + bunnyshell.com/sdk v0.15.4 github.com/AlecAivazis/survey/v2 v2.3.7 + github.com/MakeNowJust/heredoc v1.0.0 github.com/avast/retry-go/v4 v4.5.1 github.com/briandowns/spinner v1.23.0 github.com/fatih/color v1.15.0 @@ -25,7 +26,6 @@ require ( require ( github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect diff --git a/go.sum b/go.sum index 03b4847..7ce3b7d 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ bunnyshell.com/dev v0.5.6 h1:C8vsLfjAqGW1pHOC8BBCDzu1/sPz/+x5ewccyyGmwF0= bunnyshell.com/dev v0.5.6/go.mod h1:KlqPdOh60vqAfnuGUw9AKc0RVhXpNzWg46QKJSP/jog= -bunnyshell.com/sdk v0.15.3 h1:wH5RJqXGACphawbThyD8PMYzZB8fQGK/NwgW44lSu8U= -bunnyshell.com/sdk v0.15.3/go.mod h1:RfgfUzZ4WHZGCkToUfu2/hoQS6XsQc8IdPTVAlpS138= +bunnyshell.com/sdk v0.15.4 h1:4kZP2A+UjnRsnpS4weGqAbJUVEHcCPDD+xkfc+ioJMk= +bunnyshell.com/sdk v0.15.4/go.mod h1:RfgfUzZ4WHZGCkToUfu2/hoQS6XsQc8IdPTVAlpS138= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= diff --git a/pkg/api/component/list.go b/pkg/api/component/list.go index c2317a2..afa70f8 100644 --- a/pkg/api/component/list.go +++ b/pkg/api/component/list.go @@ -17,6 +17,7 @@ type ListOptions struct { Project string Environment string + Name string ClusterStatus string OperationStatus string } @@ -30,6 +31,7 @@ func NewListOptions() *ListOptions { func (lo *ListOptions) UpdateFlagSet(flags *pflag.FlagSet) { flags.StringVar(&lo.ClusterStatus, "clusterStatus", lo.ClusterStatus, "Filter by Cluster Status") flags.StringVar(&lo.OperationStatus, "operationStatus", lo.OperationStatus, "Filter by Operation Status") + flags.StringVar(&lo.Name, "componentName", lo.OperationStatus, "Filter by Name") lo.ListOptions.UpdateFlagSet(flags) } @@ -83,5 +85,9 @@ func applyOptions(request sdk.ApiComponentListRequest, options *ListOptions) sdk request = request.OperationStatus(options.OperationStatus) } + if options.Name != "" { + request = request.Name(options.Name) + } + return request } diff --git a/pkg/api/component_variable/action_create.go b/pkg/api/component_variable/action_create.go new file mode 100644 index 0000000..137d0f8 --- /dev/null +++ b/pkg/api/component_variable/action_create.go @@ -0,0 +1,77 @@ +package component_variable + +import ( + "net/http" + + "bunnyshell.com/cli/pkg/api" + "bunnyshell.com/cli/pkg/api/common" + "bunnyshell.com/cli/pkg/config/enum" + "bunnyshell.com/cli/pkg/lib" + "bunnyshell.com/cli/pkg/util" + "bunnyshell.com/sdk" + "github.com/spf13/pflag" +) + +type CreateOptions struct { + common.Options + + sdk.ServiceComponentVariableCreateAction + + Value string + IsSecret enum.Bool +} + +func NewCreateOptions() *CreateOptions { + componentVariableCreateOptions := sdk.NewServiceComponentVariableCreateAction("", "", "") + + return &CreateOptions{ + ServiceComponentVariableCreateAction: *componentVariableCreateOptions, + + IsSecret: enum.BoolNone, + } +} + +func (co *CreateOptions) UpdateFlagSet(flags *pflag.FlagSet) { + flags.StringVar(&co.Name, "name", co.Name, "Unique name for the component variable") + util.MarkFlagRequiredWithHelp(flags.Lookup("name"), "A unique name within the component for the new variable") + + flags.StringVar(&co.Value, "value", co.Value, "The value of the component variable") + util.AppendFlagHelp(flags.Lookup("value"), "A value for this component variable") + util.MarkFlag(flags.Lookup("value"), util.FlagAllowBlank) + + isSecretFlag := enum.BoolFlag( + &co.IsSecret, + "secret", + "Whether the component variable is secret or not", + ) + flags.AddFlag(isSecretFlag) + isSecretFlag.NoOptDefVal = "true" +} + +func Create(options *CreateOptions) (*sdk.ServiceComponentVariableItem, error) { + options.ServiceComponentVariableCreateAction.SetValue(options.Value) + + if options.IsSecret == enum.BoolTrue { + options.ServiceComponentVariableCreateAction.SetIsSecret(true) + } + + model, resp, err := CreateRaw(options) + if err != nil { + return nil, api.ParseError(resp, err) + } + + return model, nil +} + +func CreateRaw(options *CreateOptions) (*sdk.ServiceComponentVariableItem, *http.Response, error) { + profile := options.GetProfile() + + ctx, cancel := lib.GetContextFromProfile(profile) + defer cancel() + + request := lib.GetAPIFromProfile(profile). + ServiceComponentVariableAPI.ServiceComponentVariableCreate(ctx). + ServiceComponentVariableCreateAction(options.ServiceComponentVariableCreateAction) + + return request.Execute() +} diff --git a/pkg/api/component_variable/action_delete.go b/pkg/api/component_variable/action_delete.go new file mode 100644 index 0000000..b3eb3a7 --- /dev/null +++ b/pkg/api/component_variable/action_delete.go @@ -0,0 +1,38 @@ +package component_variable + +import ( + "net/http" + + "bunnyshell.com/cli/pkg/api" + "bunnyshell.com/cli/pkg/api/common" + "bunnyshell.com/cli/pkg/lib" +) + +type DeleteOptions struct { + common.ItemOptions +} + +func NewDeleteOptions() *DeleteOptions { + return &DeleteOptions{} +} + +func Delete(options *DeleteOptions) error { + resp, err := DeleteRaw(options) + if err != nil { + return api.ParseError(resp, err) + } + + return nil +} + +func DeleteRaw(options *DeleteOptions) (*http.Response, error) { + profile := options.GetProfile() + + ctx, cancel := lib.GetContextFromProfile(profile) + defer cancel() + + request := lib.GetAPIFromProfile(profile). + ServiceComponentVariableAPI.ServiceComponentVariableDelete(ctx, options.ID) + + return request.Execute() +} diff --git a/pkg/api/component_variable/action_edit.go b/pkg/api/component_variable/action_edit.go new file mode 100644 index 0000000..9383ac5 --- /dev/null +++ b/pkg/api/component_variable/action_edit.go @@ -0,0 +1,82 @@ +package component_variable + +import ( + "net/http" + + "bunnyshell.com/cli/pkg/api" + "bunnyshell.com/cli/pkg/api/common" + "bunnyshell.com/cli/pkg/config/enum" + "bunnyshell.com/cli/pkg/lib" + "bunnyshell.com/sdk" + "github.com/spf13/pflag" +) + +type EditOptions struct { + common.ItemOptions + + sdk.ServiceComponentVariableEditAction + + EditData +} + +type EditData struct { + Value string + IsSecret enum.Bool +} + +func NewEditOptions(id string) *EditOptions { + return &EditOptions{ + ItemOptions: *common.NewItemOptions(id), + + EditData: EditData{}, + + ServiceComponentVariableEditAction: *sdk.NewServiceComponentVariableEditActionWithDefaults(), + } +} + +func (eso *EditOptions) UpdateFlagSet(flags *pflag.FlagSet) { + data := &eso.EditData + + flags.StringVar(&data.Value, "value", data.Value, "Update the component variable value") + + isSecretFlag := enum.BoolFlag( + &eso.EditData.IsSecret, + "secret", + "Whether the component variable is secret or not", + ) + flags.AddFlag(isSecretFlag) + isSecretFlag.NoOptDefVal = "true" +} + +func Edit(options *EditOptions) (*sdk.ServiceComponentVariableItem, error) { + model, resp, err := EditRaw(options) + if err != nil { + return nil, api.ParseError(resp, err) + } + + return model, nil +} + +func EditRaw(options *EditOptions) (*sdk.ServiceComponentVariableItem, *http.Response, error) { + profile := options.GetProfile() + + ctx, cancel := lib.GetContextFromProfile(profile) + defer cancel() + + request := lib.GetAPIFromProfile(profile).ServiceComponentVariableAPI.ServiceComponentVariableEdit(ctx, options.ID) + + return applyEditOptions(request, options).Execute() +} + +func applyEditOptions( + request sdk.ApiServiceComponentVariableEditRequest, + options *EditOptions, +) sdk.ApiServiceComponentVariableEditRequest { + if options.EditData.IsSecret != enum.BoolNone { + options.ServiceComponentVariableEditAction.SetIsSecret(options.EditData.IsSecret == enum.BoolTrue) + } + + request = request.ServiceComponentVariableEditAction(options.ServiceComponentVariableEditAction) + + return request +} diff --git a/pkg/api/component_variable/item.go b/pkg/api/component_variable/item.go new file mode 100644 index 0000000..b2c66aa --- /dev/null +++ b/pkg/api/component_variable/item.go @@ -0,0 +1,34 @@ +package component_variable + +import ( + "net/http" + + "bunnyshell.com/cli/pkg/api" + "bunnyshell.com/cli/pkg/api/common" + "bunnyshell.com/cli/pkg/lib" + "bunnyshell.com/sdk" +) + +func NewItemOptions(id string) *common.ItemOptions { + return common.NewItemOptions(id) +} + +func Get(options *common.ItemOptions) (*sdk.ServiceComponentVariableItem, error) { + model, resp, err := GetRaw(options) + if err != nil { + return nil, api.ParseError(resp, err) + } + + return model, nil +} + +func GetRaw(options *common.ItemOptions) (*sdk.ServiceComponentVariableItem, *http.Response, error) { + profile := options.GetProfile() + + ctx, cancel := lib.GetContextFromProfile(profile) + defer cancel() + + request := lib.GetAPIFromProfile(profile).ServiceComponentVariableAPI.ServiceComponentVariableView(ctx, options.ID) + + return request.Execute() +} diff --git a/pkg/api/component_variable/list.go b/pkg/api/component_variable/list.go new file mode 100644 index 0000000..d34b71e --- /dev/null +++ b/pkg/api/component_variable/list.go @@ -0,0 +1,88 @@ +package component_variable + +import ( + "net/http" + + "bunnyshell.com/cli/pkg/api" + "bunnyshell.com/cli/pkg/api/common" + "bunnyshell.com/cli/pkg/lib" + "bunnyshell.com/sdk" + "github.com/spf13/pflag" +) + +type ListOptions struct { + common.ListOptions + + Organization string + Project string + Environment string + + Name string + Component string + ComponentName string +} + +func NewListOptions() *ListOptions { + return &ListOptions{ + ListOptions: *common.NewListOptions(), + } +} + +func (lo *ListOptions) UpdateFlagSet(flags *pflag.FlagSet) { + flags.StringVar(&lo.Name, "name", lo.Name, "Filter by Name") + flags.StringVar(&lo.ComponentName, "component-name", lo.ComponentName, "Filter by Component Name") + + lo.ListOptions.UpdateFlagSet(flags) +} + +func List(options *ListOptions) (*sdk.PaginatedServiceComponentVariableCollection, error) { + model, resp, err := ListRaw(options) + if err != nil { + return nil, api.ParseError(resp, err) + } + + return model, nil +} + +func ListRaw(options *ListOptions) (*sdk.PaginatedServiceComponentVariableCollection, *http.Response, error) { + profile := options.GetProfile() + + ctx, cancel := lib.GetContextFromProfile(profile) + defer cancel() + + request := lib.GetAPIFromProfile(profile).ServiceComponentVariableAPI.ServiceComponentVariableList(ctx) + + return applyOptions(request, options).Execute() +} + +func applyOptions(request sdk.ApiServiceComponentVariableListRequest, options *ListOptions) sdk.ApiServiceComponentVariableListRequest { + if options == nil { + return request + } + + if options.Page > 1 { + request = request.Page(options.Page) + } + + if options.Organization != "" { + request = request.Organization(options.Organization) + } + + if options.Environment != "" { + request = request.Environment(options.Environment) + } + + if options.Name != "" { + request = request.Name(options.Name) + } + + if options.Component != "" { + request = request.ServiceComponent(options.Component) + } + + if options.ComponentName != "" { + request = request.ServiceComponentName(options.ComponentName) + } + + return request +} diff --git a/pkg/api/project_variable/action_create.go b/pkg/api/project_variable/action_create.go index b0573b3..9bbafb3 100644 --- a/pkg/api/project_variable/action_create.go +++ b/pkg/api/project_variable/action_create.go @@ -36,7 +36,7 @@ func (co *CreateOptions) UpdateFlagSet(flags *pflag.FlagSet) { util.MarkFlagRequiredWithHelp(flags.Lookup("name"), "A unique name within the project for the new project variable") flags.StringVar(&co.Value, "value", co.Value, "The value of the project variable") - util.MarkFlagRequiredWithHelp(flags.Lookup("value"), "A value for this project variable") + util.AppendFlagHelp(flags.Lookup("value"), "A value for this project variable") util.MarkFlag(flags.Lookup("value"), util.FlagAllowBlank) isSecretFlag := enum.BoolFlag( diff --git a/pkg/api/variable/action_create.go b/pkg/api/variable/action_create.go index 277d623..05477ee 100644 --- a/pkg/api/variable/action_create.go +++ b/pkg/api/variable/action_create.go @@ -34,7 +34,7 @@ func (co *CreateOptions) UpdateFlagSet(flags *pflag.FlagSet) { util.MarkFlagRequiredWithHelp(flags.Lookup("name"), "A unique name within the environment for the new environment variable") flags.StringVar(&co.Value, "value", co.Value, "The value of the environment variable") - util.MarkFlagRequiredWithHelp(flags.Lookup("value"), "A value for this environment variable") + util.AppendFlagHelp(flags.Lookup("value"), "A value for this environment variable") util.MarkFlag(flags.Lookup("value"), util.FlagAllowBlank) isSecretFlag := enum.BoolFlag( diff --git a/pkg/formatter/stylish.go b/pkg/formatter/stylish.go index 1ef0cb7..1ab430c 100644 --- a/pkg/formatter/stylish.go +++ b/pkg/formatter/stylish.go @@ -46,6 +46,8 @@ func stylish(data interface{}) ([]byte, error) { tabulateTemplatesRepositoryCollection(writer, dataType) case *sdk.PaginatedRegistryIntegrationCollection: tabulateRegistryIntegrationsCollection(writer, dataType) + case *sdk.PaginatedServiceComponentVariableCollection: + tabulateServiceComponentVariableCollection(writer, dataType) case []sdk.ComponentEndpointCollection: tabulateAggregateEndpoint(writer, dataType) case *sdk.OrganizationItem: @@ -62,6 +64,8 @@ func stylish(data interface{}) ([]byte, error) { tabulateEnvironmentVariableItem(writer, dataType) case *sdk.ProjectVariableItem: tabulateProjectVariableItem(writer, dataType) + case *sdk.ServiceComponentVariableItem: + tabulateServiceComponentVariableItem(writer, dataType) case *sdk.KubernetesIntegrationItem: tabulateKubernetesItem(writer, dataType) case *sdk.RegistryIntegrationItem: @@ -241,6 +245,26 @@ func tabulateComponentItem(w *tabwriter.Writer, item *sdk.ComponentItem) { } } +func tabulateServiceComponentVariableCollection(w *tabwriter.Writer, data *sdk.PaginatedServiceComponentVariableCollection) { + fmt.Fprintf(w, "%v\t %v\t %v\t %v\t %v\n", "ComponentVarID", "ComponentID", "EnvironmentId", "ProjectID", "Name") + + if data.Embedded != nil { + for _, item := range data.Embedded.Item { + fmt.Fprintf(w, "%v\t %v\t %v\t %v\t %v\n", item.GetId(), item.GetServiceComponent(), item.GetEnvironment(), item.GetProject(), item.GetName()) + } + } +} + +func tabulateServiceComponentVariableItem(w *tabwriter.Writer, item *sdk.ServiceComponentVariableItem) { + fmt.Fprintf(w, "%v\t %v\n", "ComponentVariableID", item.GetId()) + fmt.Fprintf(w, "%v\t %v\n", "ComponentID", item.GetServiceComponent()) + fmt.Fprintf(w, "%v\t %v\n", "EnvironmentID", item.GetEnvironment()) + fmt.Fprintf(w, "%v\t %v\n", "ProjectID", item.GetProject()) + fmt.Fprintf(w, "%v\t %v\n", "Name", item.GetName()) + fmt.Fprintf(w, "%v\t %v\n", "Value", item.GetValue()) + fmt.Fprintf(w, "%v\t %v\n", "Secret", item.GetSecret()) +} + func tabulateEnvironmentVariableCollection(w *tabwriter.Writer, data *sdk.PaginatedEnvironmentVariableCollection) { fmt.Fprintf(w, "%v\t %v\t %v\t %v\n", "EnvVarID", "EnvironmentID", "OrganizationID", "Name") diff --git a/pkg/util/os.go b/pkg/util/os.go index bf97acc..9743a3d 100644 --- a/pkg/util/os.go +++ b/pkg/util/os.go @@ -14,3 +14,12 @@ func FileExists(path string) (bool, error) { return false, err } + +func IsStdinPresent() (bool, error) { + fi, err := os.Stdin.Stat() + if err != nil { + return false, err + } + + return (fi.Mode() & os.ModeCharDevice) == 0, nil +}