diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 2fbbe26..6a13a51 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -5,6 +5,8 @@ before: - ./scripts/completions.sh builds: - binary: safebox + ldflags: + - -s -w env: - CGO_ENABLED=0 goos: @@ -16,7 +18,7 @@ checksum: name_template: 'checksums.txt' archives: - - format: 'binary' + - format: 'tar.gz' name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" files: - completions/* diff --git a/README.md b/README.md index 437700e..d9b03f9 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ generate: config: defaults: DB_NAME: "database name updated" - API_ENDPOINT: "http://some-endpoint-{{ .stage }}.com" + API_ENDPOINT: "http://some-endpoint-{{ .stage }}.com" # stage is only available when passed through the cli flag prod: DB_NAME: "production db name" @@ -61,14 +61,18 @@ secret: 2. Use `safebox` CLI tool to deploy your configuration. ```bash +$ safebox deploy --config path/to/safebox.yml --prompt="missing" + +# staged deployment when you want to have separate config per environment. +# eg. prod, dev $ safebox deploy --stage --config path/to/safebox.yml --prompt="missing" ``` You can then run list command to view the pushed configurations. The variables under -1. `defaults` is deployed with path prefix of `//` -1. `shared` is deployed with path prefix of `//shared/` +1. `defaults` is deployed with path prefix of `//` or `/` +1. `shared` is deployed with path prefix of `//shared/` or `/shared>` ### CLI Reference @@ -92,7 +96,7 @@ Available Commands: Flags: -c, --config string path to safebox configuration file (default "safebox.yml") -h, --help help for safebox - -s, --stage string stage to deploy to (default "dev") + -s, --stage string stage to deploy to -v, --version version for safebox Use "safebox [command] --help" for more information about a command. @@ -153,7 +157,7 @@ Following is the configuration file will all possible options: ```yaml service: my-service -provider: secrets-manager # ssm OR secrets-manager +provider: secrets-manager # ssm OR secrets-manager prefix: "/custom/prefix/{{.stage}}/" # Optional. Defaults to ///. Prefix all parameters. Does not apply for shared stacks: # Outputs from cloudformation stacks that needs to be interpolated. diff --git a/cmd/deploy.go b/cmd/deploy.go index 28a4b9f..7a2b929 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -14,10 +14,9 @@ var ( prompt string deployCmd = &cobra.Command{ - Use: "deploy", - Short: "Deploys all configurations specified in config file", - RunE: deploy, - Example: `TODO: deploy command example`, + Use: "deploy", + Short: "Deploys all configurations specified in config file", + RunE: deploy, } ) @@ -38,7 +37,11 @@ func deploy(_ *cobra.Command, _ []string) error { return errors.Wrap(err, "failed to load config") } - st, err := store.GetStore(store.StoreConfig{Session: config.Session, Provider: config.Provider}) + st, err := store.GetStore(store.StoreConfig{ + Provider: config.Provider, + Region: config.Region, + FilePath: config.Filepath, + }) if err != nil { return errors.Wrap(err, "failed to instantiate store") @@ -117,11 +120,9 @@ func deploy(_ *cobra.Command, _ []string) error { fmt.Printf("%s\n", errors.Wrap(err, "Error: failed to remove orphan")) } - fmt.Printf("%d orphans removed.\n", len(orphans)) + fmt.Printf("orphans removed = %d.\n", len(orphans)) } - fmt.Printf("%d new configs deployed. service = %s, stage = %s, region = %s\n", len(configsToDeploy), config.Service, stage, *config.Session.Config.Region) - if len(config.Generate) > 0 { for _, t := range config.Generate { err := exportToFile(ExportParams{ @@ -136,10 +137,15 @@ func deploy(_ *cobra.Command, _ []string) error { continue } - fmt.Printf("file written: %s\n", t.Path) + fmt.Printf("wrote file -> %s\n", t.Path) } } + PrintSummary(Summary{ + Message: fmt.Sprintf("%s = %d", "new configs", len(configsToDeploy)), + Config: *config, + }) + return nil } diff --git a/cmd/export.go b/cmd/export.go index 1b7467b..699f2a9 100644 --- a/cmd/export.go +++ b/cmd/export.go @@ -25,10 +25,9 @@ var ( keysToExport []string exportCmd = &cobra.Command{ - Use: "export", - Short: "Exports all configuration to a file", - RunE: export, - Example: `TODO: export command example`, + Use: "export", + Short: "Exports all configuration to a file", + RunE: export, } ) @@ -64,7 +63,11 @@ type ExportParams struct { } func exportToFile(p ExportParams) error { - store, err := store.GetStore(store.StoreConfig{Session: p.config.Session, Provider: p.config.Provider}) + store, err := store.GetStore(store.StoreConfig{ + Provider: p.config.Provider, + Region: p.config.Region, + FilePath: p.config.Filepath, + }) if err != nil { return errors.Wrap(err, "failed to instantiate store") diff --git a/cmd/get.go b/cmd/get.go new file mode 100644 index 0000000..0154e03 --- /dev/null +++ b/cmd/get.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "fmt" + + "github.com/adikari/safebox/v2/store" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + getParam string + + getCmd = &cobra.Command{ + Use: "get", + Short: "Gets parameter", + RunE: getE, + } +) + +func init() { + getCmd.Flags().StringVarP(&getParam, "param", "p", "", "parameter to get") + getCmd.MarkFlagRequired("param") + + rootCmd.AddCommand(getCmd) +} + +func getE(_ *cobra.Command, _ []string) error { + config, err := loadConfig() + + if err != nil { + return errors.Wrap(err, "failed to load config") + } + + st, err := store.GetStore(store.StoreConfig{ + Provider: config.Provider, + Region: config.Region, + FilePath: config.Filepath, + }) + + if err != nil { + return errors.Wrap(err, "failed to instantiate store") + } + + found, err := st.Get(store.ConfigInput{Name: fmt.Sprintf("%s%s", config.Prefix, getParam)}) + + if err != nil { + return errors.Wrap(err, "failed to get param") + } + + if found != nil { + fmt.Printf("%s\n", *found.Value) + } + return nil +} diff --git a/cmd/import.go b/cmd/import.go index edc861b..ace578e 100644 --- a/cmd/import.go +++ b/cmd/import.go @@ -11,10 +11,9 @@ var ( inputFile string importCmd = &cobra.Command{ - Use: "import", - Short: "Imports all configuration from a file", - RunE: importE, - Example: `TODO: import command example`, + Use: "import", + Short: "Imports all configuration from a file", + RunE: importE, } ) diff --git a/cmd/list.go b/cmd/list.go index 8decec7..f307af5 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -14,10 +14,9 @@ import ( // runCmd represents the exec command var listCmd = &cobra.Command{ - Use: "list", - Short: "Lists all the configs available", - RunE: list, - Example: `TODO: list command example`, + Use: "list", + Short: "Lists all the configs available", + RunE: list, } var ( @@ -39,7 +38,11 @@ func list(_ *cobra.Command, _ []string) error { return errors.Wrap(err, "failed to load config") } - store, err := store.GetStore(store.StoreConfig{Session: config.Session, Provider: config.Provider}) + store, err := store.GetStore(store.StoreConfig{ + Provider: config.Provider, + Region: config.Region, + FilePath: config.Filepath, + }) if err != nil { return errors.Wrap(err, "failed to instantiate store") @@ -66,7 +69,12 @@ func list(_ *cobra.Command, _ []string) error { func printList(configs []store.Config, cfg *config.Config) { if len(configs) <= 0 { - fmt.Printf("no configurations to list. stage = %s, service = %s, region = %s\n", cfg.Stage, cfg.Service, *cfg.Session.Config.Region) + + PrintSummary(Summary{ + Message: "Total parameters = 0", + Config: *cfg, + }) + return } @@ -86,9 +94,12 @@ func printList(configs []store.Config, cfg *config.Config) { fmt.Fprintln(w, "") } - fmt.Fprintln(w, "---") - fmt.Fprintf(w, "Total parameters = %d, stage = %s, service = %s, region = %s\n", len(configs), cfg.Stage, cfg.Service, *cfg.Session.Config.Region) + + PrintSummary(Summary{ + Message: fmt.Sprintf("Total parameters = %d", len(configs)), + Config: *cfg, + }) w.Flush() } diff --git a/cmd/print.go b/cmd/print.go new file mode 100644 index 0000000..2f7fb4e --- /dev/null +++ b/cmd/print.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "fmt" + + c "github.com/adikari/safebox/v2/config" +) + +type Summary struct { + Message string + Config c.Config +} + +func PrintSummary(s Summary) { + msg := "" + + if s.Message != "" { + msg += fmt.Sprintf("%s", s.Message) + } + + if s.Config.Service != "" && s.Config.Provider != "gpg" { + msg += fmt.Sprintf(", service = %s", s.Config.Service) + } + + if s.Config.Stage != "" { + msg += fmt.Sprintf(", stage = %s", s.Config.Stage) + } + + if s.Config.Region != "" && s.Config.Provider != "gpg" { + msg += fmt.Sprintf(", region = %s", s.Config.Region) + } + + if s.Config.Provider == "gpg" { + msg += fmt.Sprintf(", file = %s", s.Config.Filepath) + } + + fmt.Printf("%s\n", msg) +} diff --git a/cmd/root.go b/cmd/root.go index bbe05b5..81b0657 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -25,7 +25,7 @@ var rootCmd = &cobra.Command{ } func init() { - rootCmd.PersistentFlags().StringVarP(&stage, "stage", "s", "dev", "stage to deploy to") + rootCmd.PersistentFlags().StringVarP(&stage, "stage", "s", "", "stage to deploy to") rootCmd.PersistentFlags().StringVarP(&pathToConfig, "config", "c", "", "path to safebox configuration file") rootCmd.MarkFlagFilename("config") diff --git a/config/config.go b/config/config.go index 3e76a64..1716047 100644 --- a/config/config.go +++ b/config/config.go @@ -5,12 +5,15 @@ import ( "fmt" "html/template" "io/ioutil" + "os" + "os/user" + "path/filepath" "strings" "github.com/adikari/safebox/v2/aws" "github.com/adikari/safebox/v2/store" + "github.com/adikari/safebox/v2/util" a "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" "github.com/pkg/errors" "gopkg.in/yaml.v2" ) @@ -24,6 +27,7 @@ type rawConfig struct { Secret map[string]map[string]string CloudformationStacks []string `yaml:"cloudformation-stacks"` Region string `yaml:"region"` + DBDir string `yaml:"db_dir"` } type Config struct { @@ -32,11 +36,12 @@ type Config struct { Stage string Prefix string Generate []Generate + Region string All []store.ConfigInput Configs []store.ConfigInput Secrets []store.ConfigInput Stacks []string - Session *session.Session + Filepath string } type Generate struct { @@ -81,15 +86,21 @@ func Load(param LoadConfigInput) (*Config, error) { } if c.Provider == "" { - c.Provider = store.SsmProvider + c.Provider = util.SsmProvider } - c.Session = aws.NewSession(a.Config{Region: &rc.Region}) + if c.Provider == util.GpgProvider { + c.Filepath = getFilePath(c, rc) + } + + variables, err := loadVariables(&c, rc) - variables, err := loadVariables(c, rc) + if c.Region == "" { + c.Region = "local" + } if err != nil { - return nil, errors.Wrap(err, "failed to variables for interpolation") + return nil, errors.Wrap(err, "failed to load variables for interpolation") } c.Prefix, err = Interpolate(getPrefix(param.Stage, c.Service, rc.Prefix), variables) @@ -157,7 +168,10 @@ func Load(param LoadConfigInput) (*Config, error) { } func formatSharedPath(stage string, key string) string { - return fmt.Sprintf("/%s/shared/%s", stage, key) + if stage != "" { + return fmt.Sprintf("/%s/shared/%s", stage, key) + } + return fmt.Sprintf("/shared/%s", key) } func formatPath(prefix string, key string) string { @@ -165,12 +179,16 @@ func formatPath(prefix string, key string) string { } func getPrefix(stage string, service string, defaultPrefix string) string { - if defaultPrefix == "" { + if defaultPrefix != "" { + // TODO: validate prefix starts and ends with / + return defaultPrefix + } + + if stage != "" { return fmt.Sprintf("/%s/%s/", stage, service) } - // TODO: validate prefix starts and ends with / - return defaultPrefix + return fmt.Sprintf("/%s/", service) } func validateConfig(rc rawConfig) error { @@ -185,19 +203,27 @@ func validateConfig(rc rawConfig) error { return nil } -func loadVariables(c Config, rc rawConfig) (map[string]string, error) { - st := aws.NewSts(c.Session) +// loadVariables for interpolation +// TODO: in future as we support more stores, this many need to be refactored to handled each +func loadVariables(c *Config, rc rawConfig) (map[string]string, error) { + if !util.IsAwsProvider(c.Provider) { + return map[string]string{}, nil + } + + session := aws.NewSession(a.Config{Region: &rc.Region}) + st := aws.NewSts(session) + c.Region = *session.Config.Region id, err := st.GetCallerIdentity() if err != nil { - return nil, err + return nil, errors.New("Failed to login to AWS") } variables := map[string]string{ "stage": c.Stage, "service": c.Service, - "region": *c.Session.Config.Region, + "region": c.Region, "account": *id.Account, } @@ -211,7 +237,7 @@ func loadVariables(c Config, rc rawConfig) (map[string]string, error) { // add cloudformation outputs to variables available for interpolation if len(c.Stacks) > 0 { - cf := aws.NewCloudformation(c.Session) + cf := aws.NewCloudformation(session) outputs, err := cf.GetOutputs(c.Stacks) if err != nil { @@ -273,3 +299,32 @@ func readConfigFile(path string) ([]byte, error) { return nil, fmt.Errorf("missing file %s", strings.Join(defaultConfigPaths, " or ")) } + +func getFilePath(config Config, rc rawConfig) string { + d := rc.DBDir + if d == "" { + ex, err := os.Executable() + exPath := "." + if err == nil { + exPath = filepath.Dir(ex) + } + d = exPath + } + + dir := filepath.Clean(d) + + usr, _ := user.Current() + homedir := usr.HomeDir + if dir == "~" { + dir = homedir + } else if strings.HasPrefix(dir, "~/") { + dir = filepath.Join(homedir, dir[2:]) + } + + filename := fmt.Sprintf("%s-%s", config.Stage, config.Service) + if config.Stage == "" { + filename = fmt.Sprintf("%s", config.Service) + } + + return filepath.Join(dir, filename) +} diff --git a/example/safebox-gpg.yml b/example/safebox-gpg.yml new file mode 100644 index 0000000..c1ab0cb --- /dev/null +++ b/example/safebox-gpg.yml @@ -0,0 +1,22 @@ +# yaml-language-server: $schema=../schema.json +# gpg store is in progress and not completed yet +service: secrets +provider: gpg + +generate: + - type: types-node + path: types/env.d.ts + +config: + defaults: + DB_NAME: "database name" + FILE_NAME: "file name" + + shared: + KEY: "some key" + +secret: + defaults: + API_KEY: "key of the api endpoint" + DB_SECRET: "database secret" + diff --git a/go.mod b/go.mod index d0302cc..2ace165 100644 --- a/go.mod +++ b/go.mod @@ -12,8 +12,11 @@ require ( require ( github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/kr/pretty v0.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/sys v0.1.0 // indirect + golang.org/x/sys v0.11.0 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) diff --git a/go.sum b/go.sum index fb79557..ca0cd60 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,9 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -16,6 +17,11 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -32,13 +38,14 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/npm/binary.js b/npm/binary.js new file mode 100644 index 0000000..7c668b4 --- /dev/null +++ b/npm/binary.js @@ -0,0 +1,109 @@ +const { Binary } = require("binary-install"); +const os = require("os"); +const { join } = require("path"); +const cTable = require("console.table"); + +const error = msg => { + console.error(msg); + process.exit(1); +}; + +const { version, repository } = require("./package.json"); + +const name = 'safebox'; + +const supportedPlatforms = [ + { + TYPE: "Darwin", + ARCHITECTURE: "x64", + TARGET: "darwin_amd64", + NAME: name + }, + { + TYPE: "Darwin", + ARCHITECTURE: "arm64", + TARGET: "darwin_arm64", + NAME: name + }, + { + TYPE: "Windows_NT", + ARCHITECTURE: "x64", + TARGET: "windows_amd64.exe", + NAME: `${name}.exe` + }, + { + TYPE: "Windows_NT", + ARCHITECTURE: "arm64", + TARGET: "windows_arm64.exe", + NAME: `${name}.exe` + }, + { + TYPE: "Windows_NT", + ARCHITECTURE: "ia32", + TARGET: "windows_386.exe", + NAME: `${name}.exe` + }, + { + TYPE: "Linux", + ARCHITECTURE: "ia32", + TARGET: "linux_386", + NAME: name + }, + { + TYPE: "Linux", + ARCHITECTURE: "x64", + TARGET: "linux_amd64", + NAME: name + }, + { + TYPE: "Linux", + ARCHITECTURE: "arm64", + TARGET: "linux_arm64", + NAME: name + }, +]; + +const getPlatform = () => { + const type = os.type(); + const architecture = os.arch(); + + for (let supportedPlatform of supportedPlatforms) { + if ( + type === supportedPlatform.TYPE && + architecture === supportedPlatform.ARCHITECTURE + ) { + return supportedPlatform; + } + } + + error( + `Platform with type "${type}" and architecture "${architecture}" is not supported by ${name}.\nYour system must be one of the following:\n\n${cTable.getTable( + supportedPlatforms + )}` + ); +}; + +const getBinary = () => { + const platform = getPlatform(); + + const url = `${repository.url}/releases/download/v${version}/${name}_${version}_${platform.TARGET}.tar.gz`; + + return new Binary(platform.NAME, url, version, { + installDirectory: join(__dirname, "node_modules", ".bin") + }); +}; + +const run = () => { + const binary = getBinary(); + binary.run(); +}; + +const install = () => { + const binary = getBinary(); + binary.install(); +}; + +module.exports = { + install, + run +}; diff --git a/npm/constants.js b/npm/constants.js deleted file mode 100644 index 876e114..0000000 --- a/npm/constants.js +++ /dev/null @@ -1,39 +0,0 @@ -const packageJson = require('./package.json'); -const path = require('path'); - -// Mapping from Node's `process.arch` to Golang's `$GOARCH` -const ARCH_MAPPING = { - ia32: '386', - x64: 'amd64', - arm: 'arm', - arm64: 'arm64' -}; - -// Mapping between Node's `process.platform` to Golang's -const PLATFORM_MAPPING = { - darwin: 'darwin', - linux: 'linux', - win32: 'windows', - freebsd: 'freebsd' -}; - -const name = 'safebox'; -const version = packageJson.version; -const platform = PLATFORM_MAPPING[process.platform]; -const arch = ARCH_MAPPING[process.arch]; -const ext = platform === PLATFORM_MAPPING.win32 ? '.exe' : ''; -const binaryName = `${name}${ext}`; -const binaryUrl = `https://github.com/monebag/safebox/releases/download/v${version}/safebox_${version}_${platform}_${arch}${ext}`; -const bin = path.join(__dirname, "bin"); - -const constants = { - name, - version, - platform, - arch, - binaryName, - bin, - binaryUrl, -}; - -module.exports = constants; diff --git a/npm/install.js b/npm/install.js index efc825c..9b5bba1 100644 --- a/npm/install.js +++ b/npm/install.js @@ -1,92 +1,4 @@ #!/usr/bin/env node -'use strict'; - -const request = require('request'), - os = require('os'), - fs = require('fs'), - path = require('path'), - constants = require('./constants'); - -const { name, platform, arch, binaryName, bin, binaryUrl, version } = constants; - -if (!arch) { - error(`${name} is not supported for this architecture: ${arch}`); -} - -if (!platform) { - error(`${name} is not supported for this platform: ${platform}`); -} - -if (!fs.existsSync(bin)){ - fs.mkdirSync(bin); -} - -let retries = 0; -const MAX_RETRIES = 3; - -const install = () => { - const tmpdir = os.tmpdir(); - const binary = path.join(tmpdir, `${binaryName}-${version}`); - - const copyBinary = () => { - const dest = path.join(bin, binaryName); - fs.copyFileSync(binary, dest); - fs.chmodSync(dest, 0o744) - } - - if (fs.existsSync(binary)) { - copyBinary(); - return; - } - - const req = request({ uri: binaryUrl }); - - if (retries > 0) { - console.log(`retrying to install safebox - retry ${retries} out of ${MAX_RETRIES}`) - } - - const download = fs.createWriteStream(binary); - - req.on('response', res => { - if (res.statusCode !== 200) { - throw new Error(`Error downloading safebox binary. HTTP Status Code: ${res.statusCode}`); - } - - req.pipe(download); - }); - - req.on('complete', () => { - try { - if (!fs.existsSync(binary)) { - throw new Error(`${binary} does not exist`) - } - - copyBinary(); - } catch (error) { - retries += 1; - if (retries <= MAX_RETRIES) { - install(); - } - } - }); -}; - -const uninstall = () => { - fs.unlinkSync(path.join(bin, binaryName)); -} - -let actions = { - "install": install, - "uninstall": uninstall -}; - -let argv = process.argv; -if (argv && argv.length > 2) { - let cmd = process.argv[2]; - if (!actions[cmd]) { - error("Invalid command. `install` and `uninstall` are the only supported commands"); - } - - actions[cmd](); -} +const { install } = require("./binary"); +install(); diff --git a/npm/package-lock.json b/npm/package-lock.json new file mode 100644 index 0000000..ca3bd36 --- /dev/null +++ b/npm/package-lock.json @@ -0,0 +1,526 @@ +{ + "name": "@adikari/safebox", + "version": "1.3.5", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@adikari/safebox", + "version": "1.3.5", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "binary-install": "^1.1.0", + "console.table": "^0.10.0" + }, + "bin": { + "safebox": "run.js" + } + }, + "node_modules/axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "dependencies": { + "follow-redirects": "^1.14.8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/binary-install": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/binary-install/-/binary-install-1.1.0.tgz", + "integrity": "sha512-rkwNGW+3aQVSZoD0/o3mfPN6Yxh3Id0R/xzTVBVVpGNlVz8EGwusksxRlbk/A5iKTZt9zkMn3qIqmAt3vpfbzg==", + "dependencies": { + "axios": "^0.26.1", + "rimraf": "^3.0.2", + "tar": "^6.1.11" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/console.table": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/console.table/-/console.table-0.10.0.tgz", + "integrity": "sha512-dPyZofqggxuvSf7WXvNjuRfnsOk1YazkVP8FdxH4tcH2c37wc79/Yl6Bhr7Lsu00KMgy2ql/qCMuNu8xctZM8g==", + "dependencies": { + "easy-table": "1.1.0" + }, + "engines": { + "node": "> 0.10" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "optional": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/easy-table": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.1.0.tgz", + "integrity": "sha512-oq33hWOSSnl2Hoh00tZWaIPi1ievrD9aFG82/IgjlycAnW9hHx5PkJiXpxPsgEE+H7BsbVQXFVFST8TEXS6/pA==", + "optionalDependencies": { + "wcwidth": ">=1.0.1" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tar": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", + "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "optional": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + }, + "dependencies": { + "axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "requires": { + "follow-redirects": "^1.14.8" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "binary-install": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/binary-install/-/binary-install-1.1.0.tgz", + "integrity": "sha512-rkwNGW+3aQVSZoD0/o3mfPN6Yxh3Id0R/xzTVBVVpGNlVz8EGwusksxRlbk/A5iKTZt9zkMn3qIqmAt3vpfbzg==", + "requires": { + "axios": "^0.26.1", + "rimraf": "^3.0.2", + "tar": "^6.1.11" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "console.table": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/console.table/-/console.table-0.10.0.tgz", + "integrity": "sha512-dPyZofqggxuvSf7WXvNjuRfnsOk1YazkVP8FdxH4tcH2c37wc79/Yl6Bhr7Lsu00KMgy2ql/qCMuNu8xctZM8g==", + "requires": { + "easy-table": "1.1.0" + } + }, + "defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "optional": true, + "requires": { + "clone": "^1.0.2" + } + }, + "easy-table": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.1.0.tgz", + "integrity": "sha512-oq33hWOSSnl2Hoh00tZWaIPi1ievrD9aFG82/IgjlycAnW9hHx5PkJiXpxPsgEE+H7BsbVQXFVFST8TEXS6/pA==", + "requires": { + "wcwidth": ">=1.0.1" + } + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==" + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, + "tar": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", + "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "optional": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/npm/package.json b/npm/package.json index bd02f84..c3ed2b7 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,6 +1,6 @@ { "name": "@adikari/safebox", - "version": "1.3.5", + "version": "1.3.6", "description": "A Fast and Flexible secret manager built with love by adikari in Go", "main": "index.js", "bin": "./run.js", @@ -11,12 +11,13 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/monebag/safebox.git" + "url": "https://github.com/monebag/safebox" }, "author": "Subash Adhikari ", "license": "MIT", "homepage": "https://github.com/monebag/safebox", "dependencies": { - "request": "^2.88.2" + "binary-install": "^1.1.0", + "console.table": "^0.10.0" } } diff --git a/npm/run.js b/npm/run.js index 7ec6aae..e761559 100644 --- a/npm/run.js +++ b/npm/run.js @@ -1,19 +1,4 @@ #!/usr/bin/env node -const { spawnSync } = require('child_process'), - path = require('path'), - constants = require('./constants'); -const binaryPath = path.join(constants.bin, constants.binaryName); - -const [, , ...args] = process.argv; - -const options = { cwd: process.cwd(), stdio: "inherit" }; - -const result = spawnSync(binaryPath, args, options); - -if (result.error) { - console.error(result.error); - process.exit(1); -} - -process.exit(result.status); +const { run, install: maybeInstall } = require("./binary"); +maybeInstall(true).then(run); diff --git a/schema.json b/schema.json index 038c148..ea403ea 100644 --- a/schema.json +++ b/schema.json @@ -1,8 +1,9 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "additionalProperties": false, "description": "Configuration for safebox to deploy parameters to various parameter stores", + "type": "object", "properties": { "service": { "type": "string", @@ -11,6 +12,7 @@ "provider": { "type": "string", "enum": ["ssm", "secrets-manager"], + "default": "ssm", "description": "Deploy parameters to the given provider. Eg. ssm, secrets-manager" }, "region": { @@ -55,7 +57,7 @@ "prefix": { "type": "string", "description": "Prefix to apply to all parameters. Does not apply for shared", - "default": "///" + "default": "// when stage is not provided. otherwise //service/" }, "generate": { "type": "array", @@ -111,7 +113,6 @@ } } }, - "required": ["service", "provider"], - "type": "object" + "required": ["service", "provider"] } diff --git a/store/gpgstore.go b/store/gpgstore.go new file mode 100644 index 0000000..80577f0 --- /dev/null +++ b/store/gpgstore.go @@ -0,0 +1,210 @@ +package store + +import ( + "encoding/json" + "errors" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + "time" +) + +var _ Store = &GpgStore{} + +type GpgStore struct { + filename string + path string +} + +type GpgStoreOptions struct { + Path string +} + +func NewGpgStore(config GpgStoreOptions) (*GpgStore, error) { + store := &GpgStore{ + path: config.Path, + } + + dir := filepath.Dir(config.Path) + + if _, err := os.Stat(dir); err == nil { + return store, nil + } + + err := os.MkdirAll(dir, 0755) + + if err != nil { + return nil, err + } + + return store, nil +} + +func (s *GpgStore) PutMany(input []ConfigInput) error { + updates := []Config{} + + for _, c := range input { + t := "String" + + if c.Secret == true { + t = "SecureString" + } + + now := time.Now() + + name := c.Name + value := c.Value + + updates = append(updates, Config{ + Name: &name, + Value: &value, + Version: "1", + Type: t, + Created: now, + Modified: now, + }) + } + + existing, _ := s.read() + for _, e := range existing { + found, i := find(*e.Name, updates) + if found != nil { + v, _ := strconv.Atoi(e.Version) + found.Version = strconv.Itoa(v + 1) + updates[i] = *found + } else { + updates = append(updates, e) + } + } + + return s.write(updates) +} + +func (s *GpgStore) Put(input ConfigInput) error { + return s.PutMany([]ConfigInput{input}) +} + +func (s *GpgStore) DeleteMany(input []ConfigInput) error { + existing, _ := s.read() + updates := []Config{} + + for _, e := range existing { + found := false + for _, i := range input { + if i.Name == *e.Name { + found = true + break + } + } + + if !found { + updates = append(updates, e) + } + } + + if err := s.write(updates); err != nil { + return err + } + + return nil +} + +func (s *GpgStore) GetMany(input []ConfigInput) ([]Config, error) { + if len(input) <= 0 { + return []Config{}, nil + } + + existing, err := s.read() + + if err != nil { + return nil, nil + } + + configs := []Config{} + + for _, i := range input { + if found, _ := find(i.Name, existing); found != nil { + configs = append(configs, *found) + } + } + + return configs, nil +} + +func (s *GpgStore) Get(input ConfigInput) (*Config, error) { + if configs, _ := s.GetMany([]ConfigInput{input}); configs != nil && len(configs) > 0 { + return &configs[0], nil + } + + return nil, nil +} + +func (s *GpgStore) GetByPath(path string) ([]Config, error) { + existing, _ := s.read() + result := []Config{} + + for _, e := range existing { + if strings.HasPrefix(*e.Name, path) { + result = append(result, e) + } + } + + return result, nil +} + +// Read a record from json file +func (s *GpgStore) read() ([]Config, error) { + if _, err := stat(s.path); err != nil { + return []Config{}, err + } + + b, err := ioutil.ReadFile(s.path) + + if err != nil { + return []Config{}, err + } + + configs := []Config{} + + err = json.Unmarshal(b, &configs) + + if err != nil { + return nil, errors.New("failed to parse data in database") + } + + return configs, nil +} + +func (s *GpgStore) write(configs []Config) error { + b, err := json.MarshalIndent(configs, "", "\t") + + if err != nil { + return err + } + + if err := ioutil.WriteFile(s.path, b, 0644); err != nil { + return err + } + + return nil +} + +func find(id string, all []Config) (*Config, int) { + for i, c := range all { + if *c.Name == id { + return &c, i + } + } + + return nil, -1 +} + +func stat(path string) (fi os.FileInfo, err error) { + if fi, err = os.Stat(path); os.IsNotExist(err) { + fi, err = os.Stat(path) + } + + return +} diff --git a/store/store.go b/store/store.go index 6224d53..1ba7217 100644 --- a/store/store.go +++ b/store/store.go @@ -6,23 +6,21 @@ import ( "strings" "time" - "github.com/aws/aws-sdk-go/aws/session" + "github.com/adikari/safebox/v2/aws" + "github.com/adikari/safebox/v2/util" + a "github.com/aws/aws-sdk-go/aws" ) type Config struct { Name *string Value *string Modified time.Time + Created time.Time Version string Type string DataType string } -const ( - SsmProvider = "ssm" - SecretsManagerProvider = "secrets-manager" -) - type ConfigInput struct { Name string Value string @@ -35,26 +33,27 @@ var ( ) type Store interface { - Put(input ConfigInput) error PutMany(input []ConfigInput) error Get(input ConfigInput) (*Config, error) GetMany(inputs []ConfigInput) ([]Config, error) GetByPath(path string) ([]Config, error) - Delete(input ConfigInput) error DeleteMany(inputs []ConfigInput) error } type StoreConfig struct { Provider string - Session *session.Session + Region string + FilePath string } func GetStore(cfg StoreConfig) (Store, error) { switch cfg.Provider { - case SsmProvider: - return NewSSMStore(cfg.Session) - case SecretsManagerProvider: - return NewSecretsManagerStore(cfg.Session) + case util.SsmProvider: + return NewSSMStore(aws.NewSession(a.Config{Region: &cfg.Region})) + case util.SecretsManagerProvider: + return NewSecretsManagerStore(aws.NewSession(a.Config{Region: &cfg.Region})) + case util.GpgProvider: + return NewGpgStore(GpgStoreOptions{Path: cfg.FilePath}) default: return nil, fmt.Errorf("invalid provider `%s`", cfg.Provider) } diff --git a/util/constants.go b/util/constants.go new file mode 100644 index 0000000..885d2b7 --- /dev/null +++ b/util/constants.go @@ -0,0 +1,7 @@ +package util + +const ( + SsmProvider = "ssm" + SecretsManagerProvider = "secrets-manager" + GpgProvider = "gpg" +) diff --git a/util/util.go b/util/util.go index b0ae4e4..485feba 100644 --- a/util/util.go +++ b/util/util.go @@ -14,3 +14,15 @@ func ChunkSlice[T any](slice []T, chunkSize int) [][]T { return chunks } + +func IsAwsProvider(provider string) bool { + providers := []string{SsmProvider, SecretsManagerProvider} + + for _, p := range providers { + if provider == p { + return true + } + } + + return false +}