diff --git a/cmd/sst/main.go b/cmd/sst/main.go index 29471393a..d02fdcea0 100644 --- a/cmd/sst/main.go +++ b/cmd/sst/main.go @@ -18,14 +18,12 @@ import ( "github.com/briandowns/spinner" "github.com/fatih/color" "github.com/sst/ion/cmd/sst/cli" - "github.com/sst/ion/cmd/sst/mosaic/dev" "github.com/sst/ion/cmd/sst/mosaic/errors" "github.com/sst/ion/cmd/sst/mosaic/ui" "github.com/sst/ion/internal/util" "github.com/sst/ion/pkg/global" "github.com/sst/ion/pkg/project" "github.com/sst/ion/pkg/project/provider" - "github.com/sst/ion/pkg/server" "github.com/sst/ion/pkg/telemetry" ) @@ -723,245 +721,22 @@ var root = &cli.Command{ Short: "Manage secrets", Long: "Manage the secrets in your app defined with `sst.Secret`.", }, - Children: []*cli.Command{ - { - Name: "set", - Description: cli.Description{ - Short: "Set a secret", - Long: strings.Join([]string{ - "Set the value of the secret.", - "", - "The secrets are encrypted and stored in an S3 Bucket in your AWS account. They are also stored in the package of the functions using the secret.", - "", - ":::tip", - "If you are not running `sst dev`, you'll need to `sst deploy` to apply the secret.", - ":::", - "", - "For example, set the `sst.Secret` called `StripeSecret` to `123456789`.", - "", - "```bash frame=\"none\"", - "sst secret set StripeSecret dev_123456789", - "```", - "", - "Optionally, set the secret in a specific stage.", - "", - "```bash frame=\"none\"", - "sst secret set StripeSecret prod_123456789 --stage production", - "```", - "", - "To set something like an RSA key, you can first save it to a file.", - "", - "```bash frame=\"none\"", - "cat > tmp.txt < 0 { + color.White("# fallback") + for key, value := range fallback { + fmt.Println(key + "=" + value) + } + } + if len(secrets) > 0 { + color.White("# %s/%s", p.App().Name, p.App().Stage) + for key, value := range secrets { + fmt.Println(key + "=" + value) + } + } + return nil + }, } -func CmdSecretSet(c *cli.Cli) error { - key := c.Positional(0) - value := c.Positional(1) - if value == "" { - stat, err := os.Stdin.Stat() +var CmdSecretLoad = &cli.Command{ + Name: "load", + Description: cli.Description{ + Short: "Set multiple secrets from file", + Long: strings.Join([]string{ + "Load all the secrets from a file and set them.", + "", + "```bash frame=\"none\"", + "sst secret load ./secrets.env", + "```", + "", + "The file needs to be in the _dotenv_ or bash format of key-value pairs.", + "", + "```sh title=\"secrets.env\"", + "KEY_1=VALUE1", + "KEY_2=VALUE2", + "```", + "", + "Optionally, set the secrets in a specific stage.", + "", + "```bash frame=\"none\"", + "sst secret load ./prod.env --stage production", + "```", + "", + "", + }, "\n"), + }, + Args: []cli.Argument{ + { + Name: "file", + Required: true, + Description: cli.Description{ + Short: "The file to load secrets from", + Long: "The file to load the secrets from.", + }, + }, + }, + Examples: []cli.Example{ + { + Content: "sst secret load ./secrets.env", + Description: cli.Description{ + Short: "Loads all secrets from the file", + }, + }, + { + Content: "sst secret load ./prod.env --stage production", + Description: cli.Description{ + Short: "Set secrets for production", + }, + }, + }, + Run: func(c *cli.Cli) error { + filePath := c.Positional(0) + p, err := c.InitProject() if err != nil { return err } - isTerminal := (stat.Mode() & os.ModeCharDevice) != 0 - if isTerminal { - fmt.Print("Enter value: ") + defer p.Cleanup() + backend := p.Backend() + stage := p.App().Stage + if c.Bool("fallback") { + stage = "" + } + secrets, err := provider.GetSecrets(backend, p.App().Name, stage) + if err != nil { + return util.NewReadableError(err, "Could not get secrets") + } + file, err := os.Open(filePath) + if err != nil { + return util.NewReadableError(err, fmt.Sprintf("Could not open file %s", filePath)) } - reader := bufio.NewReader(os.Stdin) - for { - input, err := reader.ReadString('\n') + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "#") { + continue + } + parts := strings.SplitN(line, "=", 2) + if len(parts) == 2 { + ui.Success(fmt.Sprintf("Setting %s", parts[0])) + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + secrets[key] = value + } + } + err = provider.PutSecrets(backend, p.App().Name, stage, secrets) + if err != nil { + return util.NewReadableError(err, "Could not set secret") + } + url, _ := server.Discover(p.PathConfig(), p.App().Stage) + if url != "" { + dev.Deploy(c.Context, url) + } + + ui.Success("Run \"sst deploy\" to update.") + return nil + }, +} + +var CmdSecretSet = &cli.Command{ + Name: "set", + Description: cli.Description{ + Short: "Set a secret", + Long: strings.Join([]string{ + "Set the value of the secret.", + "", + "The secrets are encrypted and stored in an S3 Bucket in your AWS account. They are also stored in the package of the functions using the secret.", + "", + ":::tip", + "If you are not running `sst dev`, you'll need to `sst deploy` to apply the secret.", + ":::", + "", + "For example, set the `sst.Secret` called `StripeSecret` to `123456789`.", + "", + "```bash frame=\"none\"", + "sst secret set StripeSecret dev_123456789", + "```", + "", + "Optionally, set the secret in a specific stage.", + "", + "```bash frame=\"none\"", + "sst secret set StripeSecret prod_123456789 --stage production", + "```", + "", + "To set something like an RSA key, you can first save it to a file.", + "", + "```bash frame=\"none\"", + "cat > tmp.txt <