Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set env variables from a file #950

Merged
merged 13 commits into from
May 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* fix(cli): return an exit code equal to 1 when a command or flag is malformed
* feat(install.sh): handling word answers as user inputs for install script
* fix(region_migrations): help message for `migration-abort` ([PR#951](https://github.com/Scalingo/cli/pull/951))
* feat(env): set variables from an env file ([PR#950](https://github.com/Scalingo/cli/pull/950))

### 1.28.2

Expand Down
22 changes: 14 additions & 8 deletions cmd/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,22 +79,28 @@ var (
}

envSetCommand = cli.Command{
Name: "env-set",
Category: "Environment",
Flags: []cli.Flag{&appFlag},
Name: "env-set",
Category: "Environment",
Flags: []cli.Flag{&appFlag, &addonFlag,
&cli.StringFlag{Name: "file", Aliases: []string{"f"}, Usage: "Read env file and set them"},
},
Usage: "Set the environment variables of your apps",
ArgsUsage: "variable-assignment...",
Description: CommandDescription{
Description: "Set environment variables for the app",
Examples: []string{"scalingo --app my-app env-set VAR1=VAL1 VAR2=VAL2"},
SeeAlso: []string{"env", "env-get", "env-unset"},
Examples: []string{
"scalingo --app my-app env-set VAR1=VAL1 VAR2=VAL2",
"scalingo --app my-app env-set --file .env",
"scalingo --app my-app env-set --file - < .env",
"scalingo --app my-app env-set --file .env VAR2=VAL2",
},
SeeAlso: []string{"env", "env-get", "env-unset"},
}.Render(),

Action: func(c *cli.Context) error {
currentApp := detect.CurrentApp(c)
var err error
if c.Args().Len() > 0 {
err = env.Add(c.Context, currentApp, c.Args().Slice())
if c.Args().Len() > 0 || len(c.String("f")) > 0 {
err = env.Add(c.Context, currentApp, c.Args().Slice(), c.String("f"))
} else {
cli.ShowCommandHelp(c, "env-set")
return nil
Expand Down
82 changes: 61 additions & 21 deletions env/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,53 @@ import (
"context"
"errors"
"fmt"
"os"
"regexp"
"strings"

"gopkg.in/errgo.v1"
"github.com/joho/godotenv"

"github.com/Scalingo/cli/config"
"github.com/Scalingo/go-scalingo/v6"
scalingoerrors "github.com/Scalingo/go-utils/errors/v2"
)

var (
setInvalidSyntaxError = errors.New("format is invalid, accepted: VAR=VAL")
errSetInvalidSyntax = errors.New("format is invalid, accepted: VAR=VAL")

nameFormat = regexp.MustCompile(`^[a-zA-Z0-9-_]+$`)
errInvalidNameFormat = fmt.Errorf("name can only be composed with alphanumerical characters, hyphens and underscores")
)

func Add(ctx context.Context, app string, params []string) error {
var variables scalingo.Variables
for _, param := range params {
if err := isEnvEditValid(param); err != nil {
return errgo.Newf("'%s' is invalid: %s", param, err)
}
func Add(ctx context.Context, app string, params []string, filePath string) error {
variablesFromFile, err := readFromFile(ctx, filePath)
if err != nil {
return scalingoerrors.Notef(ctx, err, "read .env file")
}

name, value := parseVariable(param)
variables = append(variables, &scalingo.Variable{
variables, err := readFromCmdLine(ctx, variablesFromFile, params)
if err != nil {
return scalingoerrors.Notef(ctx, err, "read variables from command line")
}

scalingoVariables := scalingo.Variables{}
for name, value := range variables {
scalingoVariables = append(scalingoVariables, &scalingo.Variable{
Name: name,
Value: value,
})
}

c, err := config.ScalingoClient(ctx)
if err != nil {
return errgo.Notef(err, "fail to get Scalingo client")
return scalingoerrors.Notef(ctx, err, "get Scalingo client")
}
_, _, err = c.VariableMultipleSet(ctx, app, variables)
_, _, err = c.VariableMultipleSet(ctx, app, scalingoVariables)
if err != nil {
return errgo.Mask(err, errgo.Any)
return scalingoerrors.Notef(ctx, err, "set multiple variables")
}

for _, variable := range variables {
for _, variable := range scalingoVariables {
fmt.Printf("%s has been set to '%s'.\n", variable.Name, variable.Value)
}
fmt.Println("\nRestart your containers to apply these environment changes on your application:")
Expand All @@ -55,28 +62,27 @@ func Add(ctx context.Context, app string, params []string) error {
func Delete(ctx context.Context, app string, varNames []string) error {
c, err := config.ScalingoClient(ctx)
if err != nil {
return errgo.Notef(err, "fail to get Scalingo client")
return scalingoerrors.Notef(ctx, err, "get Scalingo client")
}
vars, err := c.VariablesList(ctx, app)

if err != nil {
return errgo.Mask(err, errgo.Any)
return scalingoerrors.Notef(ctx, err, "list variables before deletion")
}

var varsToUnset scalingo.Variables

for _, varName := range varNames {
v, ok := vars.Contains(varName)
if !ok {
return errgo.Newf("%s variable does not exist", varName)
return scalingoerrors.Newf(ctx, "%s variable does not exist", varName)
}
varsToUnset = append(varsToUnset, v)
}

for _, v := range varsToUnset {
err := c.VariableUnset(ctx, app, v.ID)
if err != nil {
return errgo.Mask(err, errgo.Any)
return scalingoerrors.Notef(ctx, err, "unset variable %s", v.Name)
}
fmt.Printf("%s has been unset.\n", v.Name)
}
Expand All @@ -87,12 +93,12 @@ func Delete(ctx context.Context, app string, varNames []string) error {

func isEnvEditValid(edit string) error {
if !strings.Contains(edit, "=") {
return setInvalidSyntaxError
return errSetInvalidSyntax
}
name, value := parseVariable(edit)

if name == "" || value == "" {
return setInvalidSyntaxError
return errSetInvalidSyntax
}

if !nameFormat.MatchString(name) {
Expand All @@ -106,3 +112,37 @@ func parseVariable(param string) (string, string) {
editSplit := strings.SplitN(param, "=", 2)
return editSplit[0], editSplit[1]
}

func readFromCmdLine(ctx context.Context, variables map[string]string, params []string) (map[string]string, error) {
for _, param := range params {
err := isEnvEditValid(param)
if err != nil {
return nil, scalingoerrors.Newf(ctx, "'%s' is invalid: %s", param, err)
}

name, value := parseVariable(param)
variables[name] = value
}

return variables, nil
}

func readFromFile(ctx context.Context, filePath string) (map[string]string, error) {
if filePath == "" {
return map[string]string{}, nil
}

if filePath == "-" {
variables, err := godotenv.Parse(os.Stdin)
if err != nil {
return nil, scalingoerrors.Notef(ctx, err, "parse .env from stdin")
}
return variables, nil
}

variables, err := godotenv.Read(filePath)
if err != nil {
return nil, scalingoerrors.Notef(ctx, err, "invalid .env file")
}
return variables, nil
}
8 changes: 5 additions & 3 deletions env/edit_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package env

import "testing"
import (
"testing"
)

func TestAdd(t *testing.T) {
}
Expand All @@ -18,8 +20,8 @@ func TestIsEnvEditValid(t *testing.T) {
for _, v = range vs {
if err := isEnvEditValid(v); err == nil {
t.Fatal(v, "should not be valid")
} else if err != setInvalidSyntaxError {
t.Fatal("expected", setInvalidSyntaxError, "error, got", err)
} else if err != errSetInvalidSyntax {
t.Fatal("expected", errSetInvalidSyntax, "error, got", err)
}
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/google/go-github/v47 v47.1.0
github.com/gorilla/websocket v1.5.0
github.com/gosuri/uilive v0.0.4
github.com/joho/godotenv v1.5.1
francois2metz marked this conversation as resolved.
Show resolved Hide resolved
github.com/kelseyhightower/envconfig v1.4.0
github.com/olekukonko/tablewriter v0.0.5
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+h
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions vendor/github.com/joho/godotenv/LICENCE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading