Skip to content

Commit

Permalink
feat: add export command
Browse files Browse the repository at this point in the history
  • Loading branch information
adikari committed Sep 30, 2022
1 parent 1110049 commit 86e29e0
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 73 deletions.
138 changes: 138 additions & 0 deletions cmd/export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package cmd

import (
"bufio"
"encoding/json"
"fmt"
"io"
"os"
"sort"
"strings"

"github.com/adikari/safebox/v2/store"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
)

const doubleQuoteSpecialChars = "\\\n\r\"!$`"

var (
exportFormat string
outputFile string

exportCmd = &cobra.Command{
Use: "export",
Short: "Exports all configuration to a file",
RunE: export,
Example: `TODO: export command example`,
}
)

func init() {
exportCmd.Flags().StringVarP(&exportFormat, "format", "f", "json", "Output format (json, yaml, dotenv)")
exportCmd.Flags().StringVarP(&outputFile, "output-file", "o", "", "Output file (default is standard output)")
exportCmd.MarkFlagFilename("output-file")

rootCmd.AddCommand(exportCmd)
}

func export(cmd *cobra.Command, args []string) error {
config, err := loadConfig()

if err != nil {
return errors.Wrap(err, "failed to load config")
}

store, err := store.GetStore(config.Provider)

if err != nil {
return errors.Wrap(err, "failed to instantiate store")
}

configs, err := store.GetMany(config.Configs)

if err != nil {
return errors.Wrap(err, "failed to get params")
}

file := os.Stdout
if outputFile != "" {
if file, err = os.OpenFile(outputFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
return errors.Wrap(err, "Failed to open output file for writing")
}
defer file.Close()
defer file.Sync()
}
w := bufio.NewWriter(file)
defer w.Flush()

params := map[string]string{}
for _, c := range configs {
params[c.Key()] = *c.Value
}

switch strings.ToLower(exportFormat) {
case "json":
err = exportAsJson(params, w)
case "yaml":
err = exportAsYaml(params, w)
case "dotenv":
err = exportAsEnvFile(params, w)
default:
err = errors.Errorf("unsupported export format: %s", exportFormat)
}

if err != nil {
return errors.Wrap(err, "failed to export parameters")
}

return nil
}

func exportAsEnvFile(params map[string]string, w io.Writer) error {
for _, k := range sortedKeys(params) {
key := strings.ToUpper(k)
key = strings.Replace(key, "-", "_", -1)
w.Write([]byte(fmt.Sprintf(`%s="%s"`+"\n", key, doubleQuoteEscape(params[k]))))
}
return nil
}

func exportAsJson(params map[string]string, w io.Writer) error {
d, err := json.MarshalIndent(params, "", " ")
if err != nil {
return err
}
w.Write([]byte(d))
return nil
}

func exportAsYaml(params map[string]string, w io.Writer) error {
return yaml.NewEncoder(w).Encode(params)
}

func sortedKeys(params map[string]string) []string {
keys := make([]string, len(params))
i := 0
for k := range params {
keys[i] = k
i++
}
sort.Strings(keys)
return keys
}

func doubleQuoteEscape(line string) string {
for _, c := range doubleQuoteSpecialChars {
toReplace := "\\" + string(c)
if c == '\n' {
toReplace = `\n`
}
if c == '\r' {
toReplace = `\r`
}
line = strings.Replace(line, string(c), toReplace, -1)
}
return line
}
8 changes: 1 addition & 7 deletions cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,7 @@ func list(cmd *cobra.Command, args []string) error {
return errors.Wrap(err, "failed to instantiate store")
}

var keys []string

for _, value := range config.Configs {
keys = append(keys, value.Key)
}

configs, err := store.GetMany(keys)
configs, err := store.GetMany(config.Configs)

if err != nil {
return errors.Wrap(err, "failed to list params")
Expand Down
86 changes: 24 additions & 62 deletions store/ssmstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,15 @@ func (s *SSMStore) Put(input ConfigInput) error {
return nil
}

func (s *SSMStore) Delete(key string) error {
_, err := s.Get(key)
func (s *SSMStore) Delete(config ConfigInput) error {
_, err := s.Get(config)

if err != nil {
return err
}

deleteParameterInput := &ssm.DeleteParameterInput{
Name: aws.String(key),
Name: aws.String(config.Key),
}

_, err = s.svc.DeleteParameter(deleteParameterInput)
Expand All @@ -92,15 +92,9 @@ func (s *SSMStore) Delete(key string) error {
return nil
}

func (s *SSMStore) GetMany(keys []string) ([]Config, error) {
var names []*string

for _, key := range keys {
names = append(names, aws.String(key))
}

func (s *SSMStore) GetMany(configs []ConfigInput) ([]Config, error) {
getParametersInput := &ssm.GetParametersInput{
Names: names,
Names: getNames(configs),
WithDecryption: aws.Bool(true),
}

Expand All @@ -119,62 +113,14 @@ func (s *SSMStore) GetMany(keys []string) ([]Config, error) {
return params, nil
}

func (s *SSMStore) GetAll() ([]Config, error) {
return nil, nil
}

func (s *SSMStore) Get(key string) (Config, error) {
getParametersInput := &ssm.GetParametersInput{
Names: []*string{aws.String(key)},
WithDecryption: aws.Bool(true),
}

resp, err := s.svc.GetParameters(getParametersInput)
func (s *SSMStore) Get(config ConfigInput) (Config, error) {
configs, err := s.GetMany([]ConfigInput{config})

if err != nil {
return Config{}, err
}

if len(resp.Parameters) == 0 {
return Config{}, ConfigNotFoundError
}

param := resp.Parameters[0]
var parameter *ssm.ParameterMetadata
var describeParametersInput *ssm.DescribeParametersInput

// There is no way to use describe parameters to get a single key
// if that key uses paths, so instead get all the keys for a path,
// then find the one you are looking for :(
describeParametersInput = &ssm.DescribeParametersInput{
ParameterFilters: []*ssm.ParameterStringFilter{
{
Key: aws.String("Path"),
Option: aws.String("OneLevel"),
Values: []*string{aws.String(basePath(key))},
},
},
}

if err := s.svc.DescribeParametersPages(describeParametersInput, func(o *ssm.DescribeParametersOutput, lastPage bool) bool {
for _, param := range o.Parameters {
if *param.Name == key {
parameter = param
return false
}
}
return true
}); err != nil {
return Config{}, err
}

if parameter == nil {
return Config{}, ConfigNotFoundError
}

return Config{
Value: param.Value,
}, nil
return configs[0], nil
}

func basePath(key string) string {
Expand All @@ -196,3 +142,19 @@ func parameterToConfig(param *ssm.Parameter) Config {
DataType: *param.DataType,
}
}

func getNames(configs []ConfigInput) []*string {
var keys []string

for _, value := range configs {
keys = append(keys, value.Key)
}

var names []*string

for _, key := range keys {
names = append(names, aws.String(key))
}

return names
}
7 changes: 3 additions & 4 deletions store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@ var (
type Store interface {
Put(input ConfigInput) error
PutMany(input []ConfigInput) error
Get(key string) (Config, error)
GetMany(keys []string) ([]Config, error)
GetAll() ([]Config, error)
Delete(key string) error
Get(config ConfigInput) (Config, error)
GetMany(configs []ConfigInput) ([]Config, error)
Delete(config ConfigInput) error
}

func GetStore(provider string) (Store, error) {
Expand Down

0 comments on commit 86e29e0

Please sign in to comment.