Skip to content

Commit

Permalink
initial cli
Browse files Browse the repository at this point in the history
  • Loading branch information
Miles Maddox committed Aug 27, 2018
1 parent 3ef350e commit e1df239
Show file tree
Hide file tree
Showing 12 changed files with 508 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
scratch
19 changes: 19 additions & 0 deletions cmd/diff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package cmd

import (
"github.com/justmiles/ssm-parameter-store/lib"
"github.com/spf13/cobra"
)

func init() {
rootCmd.AddCommand(diffCmd)
}

// process the list command
var diffCmd = &cobra.Command{
Use: "diff",
Short: "diff SSM Parameters with those on disk",
Run: func(cmd *cobra.Command, args []string) {
ssmParameterStore.CMDDiff(paths, format, directory)
},
}
21 changes: 21 additions & 0 deletions cmd/pull.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package cmd

import (
"github.com/justmiles/ssm-parameter-store/lib"
"github.com/spf13/cobra"
)

var ()

func init() {
rootCmd.AddCommand(pullCmd)
}

// process the list command
var pullCmd = &cobra.Command{
Use: "pull",
Short: "pull SSM Parameters",
Run: func(cmd *cobra.Command, args []string) {
ssmParameterStore.CMDPull(paths, format, directory)
},
}
21 changes: 21 additions & 0 deletions cmd/push.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package cmd

import (
"github.com/justmiles/ssm-parameter-store/lib"
"github.com/spf13/cobra"
)

var ()

func init() {
rootCmd.AddCommand(pushCmd)
}

// process the list command
var pushCmd = &cobra.Command{
Use: "push",
Short: "push SSM Parameters",
Run: func(cmd *cobra.Command, args []string) {
ssmParameterStore.CMDPush(paths, format, directory)
},
}
47 changes: 47 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package cmd

import (
"fmt"
"os"

"github.com/spf13/cobra"
)

var (
directory, format string
paths []string
)

func init() {
dir, err := os.Getwd()
check(err)
rootCmd.PersistentFlags().StringVarP(&format, "format", "f", "yaml", "format type")
rootCmd.PersistentFlags().StringVarP(&directory, "directory", "d", dir, "output directory")
rootCmd.PersistentFlags().StringSliceVarP(&paths, "path", "p", []string{"/"}, "path")
}

// Configure the root command
var rootCmd = &cobra.Command{
Use: "ssm-parameter-store",
Short: "Sync SSM Parameter Store",
Version: "0.0.1",
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}

// Execute validates input the Cobra CLI
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

// Log errors if exist and exit
func check(err error) {
if err != nil {
fmt.Printf("ERROR\t%s", err.Error())
os.Exit(1)
}
}
78 changes: 78 additions & 0 deletions lib/Diff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package ssmParameterStore

import (
"fmt"
"strings"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ssm"
"github.com/fatih/color"
)

// Diff represents data to be added and removed
type Diff struct {
additions []*ssm.PutParameterInput
deletes []*ssm.DeleteParametersInput
asVisual []string
}

func (diff Diff) String() string {
return strings.Join(diff.asVisual, "\n") + "\n"
}

func (diff Diff) commit() error {
for _, addition := range diff.additions {
_, err := svc.PutParameter(addition)
if err != nil {
return err
}
}

for _, deleteInput := range diff.deletes {
_, err := svc.DeleteParameters(deleteInput)
if err != nil {
return err
}
}

return nil
}

// AppendDeleteChange appends a line to delete
func (diff *Diff) AppendDeleteChange(path string) error {
diff.asVisual = append(diff.asVisual, color.RedString(fmt.Sprintf("-\t%s", path)))

if diff.deletes == nil {
diff.deletes = append(diff.deletes, &ssm.DeleteParametersInput{})
}

for i, input := range diff.deletes {
if len(input.Names) < 10 {
diff.deletes[i].Names = append(input.Names, aws.String(path))
} else if len(diff.deletes) < (i + 2) {
diff.deletes = append(diff.deletes, &ssm.DeleteParametersInput{
Names: aws.StringSlice([]string{path}),
})
}
}
return nil
}

// AppendAddChange appends a line to delete
func (diff *Diff) AppendAddChange(path, desiredValue, currentValue string) error {
if currentValue != "" {
diff.asVisual = append(diff.asVisual, color.YellowString(fmt.Sprintf("~\t%s\t%s --> %s", path, currentValue, desiredValue)))
} else {
diff.asVisual = append(diff.asVisual, color.GreenString(fmt.Sprintf("+\t%s\t%s", path, desiredValue)))
}

diff.additions = append(diff.additions, &ssm.PutParameterInput{
// KeyId: ""
Name: aws.String(path),
Overwrite: aws.Bool(true),
Type: aws.String("String"),
Value: aws.String(desiredValue),
})

return nil
}
22 changes: 22 additions & 0 deletions lib/ParameterState.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package ssmParameterStore

import (
"encoding/json"

yaml "gopkg.in/yaml.v2"
)

// ParameterState struct represents a parameter store's value based on path
type ParameterState struct {
EncryptionKey *string `json:"EncryptionKey,omitempty" yaml:"EncryptionKey,omitempty"`
EncryptedKeys []string `json:"EncryptedKeys,omitempty" yaml:"EncryptedKeys,omitempty"`
Parameters map[string]string `json:"Parameters,omitempty" yaml:"Parameters,omitempty"`
}

func (p *ParameterState) json() ([]byte, error) {
return json.MarshalIndent(p, "", " ")
}

func (p *ParameterState) yaml() ([]byte, error) {
return yaml.Marshal(p)
}
136 changes: 136 additions & 0 deletions lib/ParameterStates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package ssmParameterStore

import (
"encoding/json"
"fmt"
"io/ioutil"
"os"

"github.com/aws/aws-sdk-go/service/ssm"
yaml "gopkg.in/yaml.v2"
)

// ParameterStates is the format written to or read from disk
type ParameterStates map[string]*ParameterState

func (p *ParameterStates) json() ([]byte, error) {
return json.MarshalIndent(p, "", " ")
}

func (p *ParameterStates) yaml() ([]byte, error) {
return yaml.Marshal(p)
}

func (p *ParameterStates) toDisk(directory string) error {
for key, ps := range *p {
path, file := pathAndKey(&key)
fullpath := directory + path
fullname := fmt.Sprintf("%s/%s.%s", fullpath, file, "yaml")
fmt.Printf("Writing %s.yaml to %s\n", file, fullpath)
err := os.MkdirAll(fullpath, os.ModePerm)
if err != nil {
return err
}

contents, err := ps.yaml()
if err != nil {
return err
}

err = ioutil.WriteFile(fullname, contents, 0644)
if err != nil {
return err
}

}

return nil
}

func (p *ParameterStates) diff(current ParameterStates) (diffs Diff, err error) {

for path, ps := range *p {
for key, value := range ps.Parameters {

// Add because the path does not exist in current
if current[path] == nil {
diffs.AppendAddChange(fmt.Sprintf("%s/%s", path, key), value, "")

// Add because the key does not exist in current
} else if current[path].Parameters[key] == "" {
diffs.AppendAddChange(fmt.Sprintf("%s/%s", path, key), value, "")

// Add because the key is not up to date in current
} else if value != current[path].Parameters[key] {
diffs.AppendAddChange(fmt.Sprintf("%s/%s", path, key), value, current[path].Parameters[key])

}
}
}

for path, ps := range current {
for key := range ps.Parameters {

// Delete because desired path does not exist
if (*p)[path] == nil {
diffs.AppendDeleteChange(fmt.Sprintf("%s/%s", path, key))

// Delete because desired key does not exist
} else if (*p)[path].Parameters[key] == "" {
diffs.AppendDeleteChange(fmt.Sprintf("%s/%s", path, key))
}
}
}

return
}

func (p *ParameterStates) buildFromSSMParameters(paths []string) {

var ssmParams []ssm.Parameter
for _, path := range paths {
ssmParams = append(ssmParams, getSSMParameters(path)...)
}

if *p == nil {
*p = make(ParameterStates)
}

for _, parameter := range ssmParams {
path, key := pathAndKey(parameter.Name)
if _, ok := (*p)[path]; !ok {
(*p)[path] = &ParameterState{
Parameters: make(map[string]string),
}
}
(*p)[path].Parameters[key] = *parameter.Value
if *parameter.Type == "SecureString" {
(*p)[path].EncryptedKeys = append((*p)[path].EncryptedKeys, key)
}
}
}

func (p *ParameterStates) convertFromSSMParameters(parameters []ssm.Parameter) {
if *p == nil {
*p = make(ParameterStates)
}

for _, parameter := range parameters {
path, key := pathAndKey(parameter.Name)
if _, ok := (*p)[path]; !ok {
(*p)[path] = &ParameterState{
Parameters: make(map[string]string),
}
}
(*p)[path].Parameters[key] = *parameter.Value
if *parameter.Type == "SecureString" {
(*p)[path].EncryptedKeys = append((*p)[path].EncryptedKeys, key)
}
}
}

// NewParameterStatesFromSSM reads the current parameter store in AWS returns a ParameterStates
func NewParameterStatesFromSSM(paths []string) (p ParameterStates) {
p.buildFromSSMParameters(paths)
return
}
29 changes: 29 additions & 0 deletions lib/commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package ssmParameterStore

import "fmt"

// CMDPull handles the command line's ssm-parameter-store get
func CMDPull(paths []string, format string, directory string) {
p := NewParameterStatesFromSSM(paths)
p.toDisk(directory)
}

// CMDPush executes ssm-parameter-store push from the CLI
func CMDPush(paths []string, format string, directory string) {
desired := NewParameterStatesFromDisk(paths, format, directory)
current := NewParameterStatesFromSSM(paths)
diff, err := desired.diff(current)
Check(err)
fmt.Print(diff)
err = diff.commit()
Check(err)
}

// CMDDiff executes ssm-parameter-store diff from the CLI
func CMDDiff(paths []string, format string, directory string) {
desired := NewParameterStatesFromDisk(paths, format, directory)
current := NewParameterStatesFromSSM(paths)
diff, err := desired.diff(current)
Check(err)
fmt.Print(diff)
}
11 changes: 11 additions & 0 deletions lib/commands_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ssmParameterStore

import (
"testing"
)

func TestRun(t *testing.T) {
CMDPull([]string{"/dev", "/ops"}, "yaml", "/home/justmiles/go/src/github.com/justmiles/ssm-parameter-store/scratch")
// CMDPush([]string{"/dev", "/ops"}, "yaml", "/home/justmiles/go/src/github.com/justmiles/ssm-parameter-store/scratch")
// CMDDiff([]string{"/dev", "/ops"}, "yaml", "/home/justmiles/go/src/github.com/justmiles/ssm-parameter-store/scratch")
}
Loading

0 comments on commit e1df239

Please sign in to comment.