Added the clone command in SSM

This commit is contained in:
Leon Mika 2022-04-05 13:39:14 +10:00
parent ee6011bc3e
commit 306640abdb
9 changed files with 102 additions and 10 deletions

View file

@ -2,6 +2,8 @@ package commandctrl
import ( import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/pkg/errors"
"log"
"strings" "strings"
"github.com/lmika/awstools/internal/common/ui/events" "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 { func (c *CommandController) Execute(commandInput string) tea.Cmd {
log.Println("Received input: ", commandInput)
input := strings.TrimSpace(commandInput) input := strings.TrimSpace(commandInput)
if input == "" { if input == "" {
return nil return nil
} }
tokens := shellwords.Split(input) tokens := shellwords.Split(input)
log.Println("Tokens: ", tokens)
command := c.lookupCommand(tokens[0]) command := c.lookupCommand(tokens[0])
if command == nil { 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:]) return command(tokens[1:])
@ -51,6 +56,7 @@ func (c *CommandController) Execute(commandInput string) tea.Cmd {
func (c *CommandController) lookupCommand(name string) Command { func (c *CommandController) lookupCommand(name string) Command {
for ctx := c.commandList; ctx != nil; ctx = ctx.parent { 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 { if cmd, ok := ctx.Commands[name]; ok {
return cmd return cmd
} }

View file

@ -10,6 +10,12 @@ func Error(err error) tea.Msg {
return ErrorMsg(err) return ErrorMsg(err)
} }
func SetError(err error) tea.Cmd {
return func() tea.Msg {
return Error(err)
}
}
func SetStatus(msg string) tea.Cmd { func SetStatus(msg string) tea.Cmd {
return func() tea.Msg { return func() tea.Msg {
return StatusMsg(msg) return StatusMsg(msg)

View file

@ -4,6 +4,7 @@ import (
"context" "context"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/awstools/internal/common/ui/events" "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" "github.com/lmika/awstools/internal/ssm-browse/services/ssmparameters"
"sync" "sync"
) )
@ -12,15 +13,15 @@ type SSMController struct {
service *ssmparameters.Service service *ssmparameters.Service
// state // state
mutex *sync.Mutex mutex *sync.Mutex
prefix string prefix string
} }
func New(service *ssmparameters.Service) *SSMController { func New(service *ssmparameters.Service) *SSMController {
return &SSMController{ return &SSMController{
service: service, service: service,
prefix: "/", prefix: "/",
mutex: new(sync.Mutex), mutex: new(sync.Mutex),
} }
} }
@ -32,7 +33,7 @@ func (c *SSMController) Fetch() tea.Cmd {
} }
return NewParameterListMsg{ return NewParameterListMsg{
Prefix: c.prefix, Prefix: c.prefix,
Parameters: res, Parameters: res,
} }
} }
@ -50,8 +51,29 @@ func (c *SSMController) ChangePrefix(newPrefix string) tea.Cmd {
c.prefix = newPrefix c.prefix = newPrefix
return NewParameterListMsg{ return NewParameterListMsg{
Prefix: c.prefix, Prefix: c.prefix,
Parameters: res, Parameters: res,
} }
} }
} }
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,
}
}
})
}

View file

@ -1,10 +1,13 @@
package models package models
import "github.com/aws/aws-sdk-go-v2/service/ssm/types"
type SSMParameters struct { type SSMParameters struct {
Items []SSMParameter Items []SSMParameter
} }
type SSMParameter struct { type SSMParameter struct {
Name string Name string
Type types.ParameterType
Value string Value string
} }

View file

@ -4,11 +4,14 @@ import (
"context" "context"
"github.com/aws/aws-sdk-go-v2/aws" "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"
"github.com/aws/aws-sdk-go-v2/service/ssm/types"
"github.com/lmika/awstools/internal/ssm-browse/models" "github.com/lmika/awstools/internal/ssm-browse/models"
"github.com/pkg/errors" "github.com/pkg/errors"
"log" "log"
) )
const defaultKMSKeyIDForSecureStrings = "alias/aws/ssm"
type Provider struct { type Provider struct {
client *ssm.Client 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) log.Printf("new prefix: %v", prefix)
pager := ssm.NewGetParametersByPathPaginator(p.client, &ssm.GetParametersByPathInput{ pager := ssm.NewGetParametersByPathPaginator(p.client, &ssm.GetParametersByPathInput{
Path: aws.String(prefix), Path: aws.String(prefix),
Recursive: true, Recursive: true,
WithDecryption: true, WithDecryption: true,
}) })
items := make([]models.SSMParameter, 0) items := make([]models.SSMParameter, 0)
outer: for pager.HasMorePages() { outer:
for pager.HasMorePages() {
out, err := pager.NextPage(ctx) out, err := pager.NextPage(ctx)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "cannot get parameters from path") 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 { for _, p := range out.Parameters {
items = append(items, models.SSMParameter{ items = append(items, models.SSMParameter{
Name: aws.ToString(p.Name), Name: aws.ToString(p.Name),
Type: p.Type,
Value: aws.ToString(p.Value), Value: aws.ToString(p.Value),
}) })
if len(items) >= maxCount { 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 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
}

View file

@ -7,4 +7,5 @@ import (
type SSMProvider interface { type SSMProvider interface {
List(ctx context.Context, prefix string, maxCount int) (*models.SSMParameters, error) List(ctx context.Context, prefix string, maxCount int) (*models.SSMParameters, error)
Put(ctx context.Context, param models.SSMParameter, override bool) error
} }

View file

@ -18,3 +18,12 @@ func NewService(provider SSMProvider) *Service {
func (s *Service) List(ctx context.Context, prefix string) (*models.SSMParameters, error) { func (s *Service) List(ctx context.Context, prefix string) (*models.SSMParameters, error) {
return s.provider.List(ctx, prefix, 100) 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)
}

View file

@ -3,11 +3,13 @@ package ui
import ( import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/awstools/internal/common/ui/commandctrl" "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/layout"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/statusandprompt" "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/controllers"
"github.com/lmika/awstools/internal/ssm-browse/ui/ssmdetails" "github.com/lmika/awstools/internal/ssm-browse/ui/ssmdetails"
"github.com/lmika/awstools/internal/ssm-browse/ui/ssmlist" "github.com/lmika/awstools/internal/ssm-browse/ui/ssmlist"
"github.com/pkg/errors"
) )
type Model struct { type Model struct {
@ -27,6 +29,17 @@ func NewModel(controller *controllers.SSMController, cmdController *commandctrl.
layout.NewVBox(layout.LastChildFixedAt(17), ssmList, ssmdDetails), 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) root := layout.FullScreen(statusAndPrompt)
return Model{ return Model{

View file

@ -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 { func (m *Model) View() string {
return lipgloss.JoinVertical(lipgloss.Top, m.frameTitle.View(), m.table.View()) return lipgloss.JoinVertical(lipgloss.Top, m.frameTitle.View(), m.table.View())
} }