Skip to content

Commit

Permalink
add ability to generate types (#25)
Browse files Browse the repository at this point in the history
* add minimal example

* add ability to generate node types

* load generate config

* generate files automatically
  • Loading branch information
adikari committed Jul 6, 2023
1 parent 203f740 commit e5d1f83
Show file tree
Hide file tree
Showing 15 changed files with 177 additions and 37 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,16 @@ To install it directly find the right version for your machine in [releases](htt
1. Create a configuration file called `safebox.yml`.

```yaml
# yaml-language-server: $schema=https://raw.githubusercontent.com/monebag/safebox/main/schema.json
service: my-service
provider: ssm

# Optional: generate the given files based on the parameters
generate:
- type: types-node
path: types/env.d.ts
- type: dotenv
path: .env

config:
defaults:
DB_NAME: "database name updated"
Expand All @@ -49,6 +55,8 @@ secret:

shared:
SHARED_KEY: "shared key"

# yaml-language-server: $schema=https://raw.githubusercontent.com/monebag/safebox/main/schema.json
```

2. Use `safebox` CLI tool to deploy your configuration.
Expand Down
20 changes: 19 additions & 1 deletion cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func init() {
deployCmd.Flags().StringVarP(&prompt, "prompt", "p", "", "prompt for configurations (missing or all)")
}

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

if prompt != "" && prompt != "all" && prompt != "missing" {
Expand Down Expand Up @@ -122,6 +122,24 @@ func deploy(cmd *cobra.Command, args []string) error {

fmt.Printf("%d new configs deployed. service = %s, stage = %s\n", len(configsToDeploy), config.Service, stage)

if len(config.Generate) > 0 {
for _, t := range config.Generate {
err := exportToFile(ExportParams{
config: config,
format: t.Type,
output: t.Path,
})

if err != nil {
fmt.Printf("Error: failed to generate file type = %s, output = %s\n", t.Type, t.Path)
fmt.Printf(" %s\n", err)
continue
}

fmt.Printf("file written: %s\n", t.Path)
}
}

return nil
}

Expand Down
92 changes: 76 additions & 16 deletions cmd/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"

c "github.com/adikari/safebox/v2/config"
"github.com/adikari/safebox/v2/store"
"github.com/pkg/errors"
"github.com/spf13/cobra"
Expand All @@ -31,28 +33,44 @@ var (
)

func init() {
exportCmd.Flags().StringVarP(&exportFormat, "format", "f", "json", "output format (json, yaml, dotenv)")
exportCmd.Flags().StringVarP(&exportFormat, "format", "f", "json", "output format (json, yaml, dotenv, types-node)")
exportCmd.Flags().StringVarP(&outputFile, "output-file", "o", "", "output file (default is standard output)")
exportCmd.Flags().StringSliceVarP(&keysToExport, "key", "k", []string{}, "only export specified config (default is export all)")
exportCmd.MarkFlagFilename("output-file")

rootCmd.AddCommand(exportCmd)
}

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

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

store, err := store.GetStore(config.Provider)
return exportToFile(ExportParams{
config: config,
keysToExport: keysToExport,
format: exportFormat,
output: outputFile,
})
}

type ExportParams struct {
config *c.Config
keysToExport []string
format string
output string
}

func exportToFile(p ExportParams) error {
store, err := store.GetStore(p.config.Provider)

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

toExport, err := configsToExport(config.All)
toExport, err := configsToExport(p.config.All, p.keysToExport)

if err != nil {
return err
Expand All @@ -64,29 +82,28 @@ func export(cmd *cobra.Command, args []string) error {
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, err := getFileBuffer(p.output)

if err != nil {
return errors.Wrap(err, "failed to write file")
}
w := bufio.NewWriter(file)

defer w.Flush()

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

switch strings.ToLower(exportFormat) {
switch strings.ToLower(p.format) {
case "json":
err = exportAsJson(params, w)
case "yaml":
err = exportAsYaml(params, w)
case "dotenv":
err = exportAsEnvFile(params, w)
case "types-node":
err = exportAsTypesNode(params, w)
default:
err = errors.Errorf("unsupported export format: %s", exportFormat)
}
Expand All @@ -98,6 +115,23 @@ func export(cmd *cobra.Command, args []string) error {
return nil
}

func exportAsTypesNode(params map[string]string, w io.Writer) error {
w.Write([]byte(fmt.Sprintf("declare global {\n")))
w.Write([]byte(fmt.Sprintf(" namespace NodeJS {\n")))
w.Write([]byte(fmt.Sprintf(" interface ProcessEnv {\n")))

for _, k := range sortedKeys(params) {
key := strings.ToUpper(k)
w.Write([]byte(fmt.Sprintf(` %s: string;`+"\n", key)))
}

w.Write([]byte(fmt.Sprintf(" }\n")))
w.Write([]byte(fmt.Sprintf(" }\n")))
w.Write([]byte(fmt.Sprintf("}\n")))

return nil
}

func exportAsEnvFile(params map[string]string, w io.Writer) error {
for _, k := range sortedKeys(params) {
key := strings.ToUpper(k)
Expand Down Expand Up @@ -145,14 +179,14 @@ func doubleQuoteEscape(line string) string {
return line
}

func configsToExport(configs []store.ConfigInput) ([]store.ConfigInput, error) {
if len(keysToExport) == 0 {
func configsToExport(configs []store.ConfigInput, keys []string) ([]store.ConfigInput, error) {
if len(keys) == 0 {
return configs, nil
}

result := []store.ConfigInput{}

for _, key := range keysToExport {
for _, key := range keys {
var found bool

for _, config := range configs {
Expand All @@ -170,3 +204,29 @@ func configsToExport(configs []store.ConfigInput) ([]store.ConfigInput, error) {

return result, nil
}

func getFileBuffer(output string) (*bufio.Writer, error) {
if output == "" {
return bufio.NewWriter(os.Stdout), nil
}

directory := filepath.Dir(output)

if _, err := os.Stat(directory); errors.Is(err, os.ErrNotExist) {
err := os.MkdirAll(directory, os.ModePerm)
if err != nil {
return nil, errors.Wrap(err, "failed to write file")
}
}

file, err := os.OpenFile(output, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)

if err != nil {
return nil, errors.Wrap(err, "failed to open output file for writing")
}

defer file.Close()
defer file.Sync()

return bufio.NewWriter(file), nil
}
2 changes: 1 addition & 1 deletion cmd/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func init() {
rootCmd.AddCommand(importCmd)
}

func importE(cmd *cobra.Command, args []string) error {
func importE(_ *cobra.Command, _ []string) error {
log.Fatalf("not implemented")
return nil
}
2 changes: 1 addition & 1 deletion cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func init() {
rootCmd.AddCommand(listCmd)
}

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

if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var rootCmd = &cobra.Command{
Short: "SafeBox is a secret manager CLI program",
Long: `A Fast and Flexible secret manager built with love by adikari in Go.`,
SilenceUsage: true,
Run: func(cmd *cobra.Command, args []string) {
Run: func(cmd *cobra.Command, _ []string) {
cmd.Usage()
},
}
Expand Down
25 changes: 17 additions & 8 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import (
)

type rawConfig struct {
Provider string
Service string
Prefix string

Provider string
Service string
Prefix string
Generate []Generate `yaml:"generate"`
Config map[string]map[string]string
Secret map[string]map[string]string
CloudformationStacks []string `yaml:"cloudformation-stacks"`
Expand All @@ -27,12 +27,18 @@ type Config struct {
Service string
Stage string
Prefix string
Generate []Generate
All []store.ConfigInput
Configs []store.ConfigInput
Secrets []store.ConfigInput
Stacks []string
}

type Generate struct {
Type string
Path string
}

type LoadConfigInput struct {
Path string
Stage string
Expand All @@ -50,6 +56,7 @@ func Load(param LoadConfigInput) (*Config, error) {
err = yaml.Unmarshal(yamlFile, &rc)

if err != nil {
fmt.Printf("%v", err)
return nil, fmt.Errorf("could not parse safebox config file %s", param.Path)
}

Expand All @@ -59,10 +66,12 @@ func Load(param LoadConfigInput) (*Config, error) {
return nil, errors.Wrap(err, "invalid configuration")
}

c := Config{}
c.Service = rc.Service
c.Stage = param.Stage
c.Provider = rc.Provider
c := Config{
Service: rc.Service,
Stage: param.Stage,
Provider: rc.Provider,
Generate: rc.Generate,
}

if c.Provider == "" {
c.Provider = store.SsmProvider
Expand Down
3 changes: 2 additions & 1 deletion example/custom-prefix.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# yaml-language-server: $schema=../schema.json
service: safebox
provider: ssm

Expand All @@ -22,3 +21,5 @@ secret:

shared:
APOLLO_KEY: "apollo key"

# yaml-language-server: $schema=../schema.json
21 changes: 21 additions & 0 deletions example/minimal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
service: safebox
provider: ssm

generate:
- type: types-node
path: types/env.d.ts
- type: json
path: env.json

config:
defaults:
DB_NAME: "database name updated"

shared:
SHARED_KEY: "shared key"

secret:
defaults:
API_KEY: "key of the api endpoint"

# yaml-language-server: $schema=../schema.json
9 changes: 8 additions & 1 deletion example/safebox.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
# yaml-language-server: $schema=../schema.json
service: safebox
provider: ssm

cloudformation-stacks:
- "{{.stage}}-shared-infra-SharedInfraServerless"
- "{{.stage}}-user-debug-stack"

generate:
- type: types-node
path: types/env.d.ts
- type: json
path: env.json

config:
defaults:
DB_NAME: "database name updated"
Expand Down Expand Up @@ -34,3 +39,5 @@ secret:

shared:
APOLLO_KEY: "apollo key"

# yaml-language-server: $schema=../schema.json
3 changes: 2 additions & 1 deletion example/secretsmanager.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# yaml-language-server: $schema=../schema.json
service: safebox
provider: secrets-manager

Expand All @@ -23,3 +22,5 @@ secret:

shared:
APOLLO_KEY: "apollo key"

# yaml-language-server: $schema=../schema.json
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,5 @@ require (
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/exp v0.0.0-20221114191408-850992195362 // indirect
golang.org/x/sys v0.1.0 // indirect
)
Loading

0 comments on commit e5d1f83

Please sign in to comment.