From 306640abdb938798bf706d16ea5a2497011282fc Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Tue, 5 Apr 2022 13:39:14 +1000 Subject: [PATCH] Added the clone command in SSM --- internal/common/ui/commandctrl/commandctrl.go | 8 ++++- internal/common/ui/events/commands.go | 6 ++++ .../ssm-browse/controllers/ssmcontroller.go | 34 +++++++++++++++---- internal/ssm-browse/models/models.go | 3 ++ .../ssm-browse/providers/awsssm/provider.go | 30 ++++++++++++++-- .../services/ssmparameters/iface.go | 1 + .../services/ssmparameters/service.go | 9 +++++ internal/ssm-browse/ui/model.go | 13 +++++++ internal/ssm-browse/ui/ssmlist/ssmlist.go | 8 +++++ 9 files changed, 102 insertions(+), 10 deletions(-) diff --git a/internal/common/ui/commandctrl/commandctrl.go b/internal/common/ui/commandctrl/commandctrl.go index 4332023..a71352a 100644 --- a/internal/common/ui/commandctrl/commandctrl.go +++ b/internal/common/ui/commandctrl/commandctrl.go @@ -2,6 +2,8 @@ package commandctrl import ( tea "github.com/charmbracelet/bubbletea" + "github.com/pkg/errors" + "log" "strings" "github.com/lmika/awstools/internal/common/ui/events" @@ -35,15 +37,18 @@ func (c *CommandController) Prompt() tea.Cmd { } func (c *CommandController) Execute(commandInput string) tea.Cmd { + log.Println("Received input: ", commandInput) input := strings.TrimSpace(commandInput) if input == "" { return nil } tokens := shellwords.Split(input) + log.Println("Tokens: ", tokens) command := c.lookupCommand(tokens[0]) if command == nil { - return events.SetStatus("no such command: " + tokens[0]) + log.Println("No such command: ", tokens) + return events.SetError(errors.New("no such command: " + tokens[0])) } return command(tokens[1:]) @@ -51,6 +56,7 @@ func (c *CommandController) Execute(commandInput string) tea.Cmd { func (c *CommandController) lookupCommand(name string) Command { for ctx := c.commandList; ctx != nil; ctx = ctx.parent { + log.Printf("Looking in command list: %v", c.commandList) if cmd, ok := ctx.Commands[name]; ok { return cmd } diff --git a/internal/common/ui/events/commands.go b/internal/common/ui/events/commands.go index 6a679de..99f70a7 100644 --- a/internal/common/ui/events/commands.go +++ b/internal/common/ui/events/commands.go @@ -10,6 +10,12 @@ func Error(err error) tea.Msg { return ErrorMsg(err) } +func SetError(err error) tea.Cmd { + return func() tea.Msg { + return Error(err) + } +} + func SetStatus(msg string) tea.Cmd { return func() tea.Msg { return StatusMsg(msg) diff --git a/internal/ssm-browse/controllers/ssmcontroller.go b/internal/ssm-browse/controllers/ssmcontroller.go index 2ee83b6..4af7335 100644 --- a/internal/ssm-browse/controllers/ssmcontroller.go +++ b/internal/ssm-browse/controllers/ssmcontroller.go @@ -4,6 +4,7 @@ import ( "context" tea "github.com/charmbracelet/bubbletea" "github.com/lmika/awstools/internal/common/ui/events" + "github.com/lmika/awstools/internal/ssm-browse/models" "github.com/lmika/awstools/internal/ssm-browse/services/ssmparameters" "sync" ) @@ -12,15 +13,15 @@ type SSMController struct { service *ssmparameters.Service // state - mutex *sync.Mutex + mutex *sync.Mutex prefix string } func New(service *ssmparameters.Service) *SSMController { return &SSMController{ service: service, - prefix: "/", - mutex: new(sync.Mutex), + prefix: "/", + mutex: new(sync.Mutex), } } @@ -32,7 +33,7 @@ func (c *SSMController) Fetch() tea.Cmd { } return NewParameterListMsg{ - Prefix: c.prefix, + Prefix: c.prefix, Parameters: res, } } @@ -50,8 +51,29 @@ func (c *SSMController) ChangePrefix(newPrefix string) tea.Cmd { c.prefix = newPrefix return NewParameterListMsg{ - Prefix: c.prefix, + Prefix: c.prefix, Parameters: res, } } -} \ No newline at end of file +} + +func (c *SSMController) Clone(param models.SSMParameter) tea.Cmd { + return events.PromptForInput("New key: ", func(value string) tea.Cmd { + return func() tea.Msg { + ctx := context.Background() + if err := c.service.Clone(ctx, param, value); err != nil { + return events.Error(err) + } + + res, err := c.service.List(context.Background(), c.prefix) + if err != nil { + return events.Error(err) + } + + return NewParameterListMsg{ + Prefix: c.prefix, + Parameters: res, + } + } + }) +} diff --git a/internal/ssm-browse/models/models.go b/internal/ssm-browse/models/models.go index 777e6c8..74a9b7d 100644 --- a/internal/ssm-browse/models/models.go +++ b/internal/ssm-browse/models/models.go @@ -1,10 +1,13 @@ package models +import "github.com/aws/aws-sdk-go-v2/service/ssm/types" + type SSMParameters struct { Items []SSMParameter } type SSMParameter struct { Name string + Type types.ParameterType Value string } diff --git a/internal/ssm-browse/providers/awsssm/provider.go b/internal/ssm-browse/providers/awsssm/provider.go index 07786de..c7e72bb 100644 --- a/internal/ssm-browse/providers/awsssm/provider.go +++ b/internal/ssm-browse/providers/awsssm/provider.go @@ -4,11 +4,14 @@ import ( "context" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ssm" + "github.com/aws/aws-sdk-go-v2/service/ssm/types" "github.com/lmika/awstools/internal/ssm-browse/models" "github.com/pkg/errors" "log" ) +const defaultKMSKeyIDForSecureStrings = "alias/aws/ssm" + type Provider struct { client *ssm.Client } @@ -23,13 +26,14 @@ func (p *Provider) List(ctx context.Context, prefix string, maxCount int) (*mode log.Printf("new prefix: %v", prefix) pager := ssm.NewGetParametersByPathPaginator(p.client, &ssm.GetParametersByPathInput{ - Path: aws.String(prefix), - Recursive: true, + Path: aws.String(prefix), + Recursive: true, WithDecryption: true, }) items := make([]models.SSMParameter, 0) - outer: for pager.HasMorePages() { +outer: + for pager.HasMorePages() { out, err := pager.NextPage(ctx) if err != nil { return nil, errors.Wrap(err, "cannot get parameters from path") @@ -38,6 +42,7 @@ func (p *Provider) List(ctx context.Context, prefix string, maxCount int) (*mode for _, p := range out.Parameters { items = append(items, models.SSMParameter{ Name: aws.ToString(p.Name), + Type: p.Type, Value: aws.ToString(p.Value), }) if len(items) >= maxCount { @@ -48,3 +53,22 @@ func (p *Provider) List(ctx context.Context, prefix string, maxCount int) (*mode return &models.SSMParameters{Items: items}, nil } + +func (p *Provider) Put(ctx context.Context, param models.SSMParameter, override bool) error { + in := &ssm.PutParameterInput{ + Name: aws.String(param.Name), + Type: param.Type, + Value: aws.String(param.Value), + Overwrite: override, + } + if param.Type == types.ParameterTypeSecureString { + in.KeyId = aws.String(defaultKMSKeyIDForSecureStrings) + } + + _, err := p.client.PutParameter(ctx, in) + if err != nil { + return errors.Wrap(err, "unable to put new SSM parameter") + } + + return nil +} \ No newline at end of file diff --git a/internal/ssm-browse/services/ssmparameters/iface.go b/internal/ssm-browse/services/ssmparameters/iface.go index cc23f54..406d733 100644 --- a/internal/ssm-browse/services/ssmparameters/iface.go +++ b/internal/ssm-browse/services/ssmparameters/iface.go @@ -7,4 +7,5 @@ import ( type SSMProvider interface { List(ctx context.Context, prefix string, maxCount int) (*models.SSMParameters, error) + Put(ctx context.Context, param models.SSMParameter, override bool) error } diff --git a/internal/ssm-browse/services/ssmparameters/service.go b/internal/ssm-browse/services/ssmparameters/service.go index 7706801..40f2042 100644 --- a/internal/ssm-browse/services/ssmparameters/service.go +++ b/internal/ssm-browse/services/ssmparameters/service.go @@ -17,4 +17,13 @@ func NewService(provider SSMProvider) *Service { func (s *Service) List(ctx context.Context, prefix string) (*models.SSMParameters, error) { return s.provider.List(ctx, prefix, 100) +} + +func (s *Service) Clone(ctx context.Context, param models.SSMParameter, newName string) error { + newParam := models.SSMParameter{ + Name: newName, + Type: param.Type, + Value: param.Value, + } + return s.provider.Put(ctx, newParam, false) } \ No newline at end of file diff --git a/internal/ssm-browse/ui/model.go b/internal/ssm-browse/ui/model.go index ae625e9..e01fa2d 100644 --- a/internal/ssm-browse/ui/model.go +++ b/internal/ssm-browse/ui/model.go @@ -3,11 +3,13 @@ package ui import ( tea "github.com/charmbracelet/bubbletea" "github.com/lmika/awstools/internal/common/ui/commandctrl" + "github.com/lmika/awstools/internal/common/ui/events" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/statusandprompt" "github.com/lmika/awstools/internal/ssm-browse/controllers" "github.com/lmika/awstools/internal/ssm-browse/ui/ssmdetails" "github.com/lmika/awstools/internal/ssm-browse/ui/ssmlist" + "github.com/pkg/errors" ) type Model struct { @@ -27,6 +29,17 @@ func NewModel(controller *controllers.SSMController, cmdController *commandctrl. layout.NewVBox(layout.LastChildFixedAt(17), ssmList, ssmdDetails), "") + cmdController.AddCommands(&commandctrl.CommandContext{ + Commands: map[string]commandctrl.Command{ + "clone": func(args []string) tea.Cmd { + if currentParam := ssmList.CurrentParameter(); currentParam != nil { + return controller.Clone(*currentParam) + } + return events.SetError(errors.New("no parameter selected")) + }, + }, + }) + root := layout.FullScreen(statusAndPrompt) return Model{ diff --git a/internal/ssm-browse/ui/ssmlist/ssmlist.go b/internal/ssm-browse/ui/ssmlist/ssmlist.go index 9d90964..e304e66 100644 --- a/internal/ssm-browse/ui/ssmlist/ssmlist.go +++ b/internal/ssm-browse/ui/ssmlist/ssmlist.go @@ -85,6 +85,14 @@ func (m *Model) emitNewSelectedParameter() tea.Cmd { } } +func (m *Model) CurrentParameter() *models.SSMParameter { + if row, ok := m.table.SelectedRow().(itemTableRow); ok { + return &(row.item) + } + + return nil +} + func (m *Model) View() string { return lipgloss.JoinVertical(lipgloss.Top, m.frameTitle.View(), m.table.View()) }