diff --git a/commands/commands.go b/commands/commands.go index 0febace..b07111f 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -10,6 +10,7 @@ import ( "github.com/reverbdotcom/sbx/up" "github.com/reverbdotcom/sbx/version" "github.com/reverbdotcom/sbx/web" + "github.com/reverbdotcom/sbx/env" ) type RunFn func() (string, error) @@ -31,6 +32,7 @@ COMMANDS info progress headlamp + env DESCRIPTION @@ -47,6 +49,7 @@ DESCRIPTION version v shows the version of the sbx cli. info i shows the summary of the sandbox. progress p opens deployment progress in a browser. + env e shows the configured environment variables for sbx. headlamp h opens headlamp ( kubernetes dashboard ) in a browser. USAGE: @@ -82,5 +85,7 @@ func Commands() map[string]RunFn { "i": summary.Run, "headlamp": web.OpenHeadlamp, "h": web.OpenHeadlamp, + "env": env.Run, + "e": env.Run, } } diff --git a/env/env.go b/env/env.go new file mode 100644 index 0000000..22b3821 --- /dev/null +++ b/env/env.go @@ -0,0 +1,72 @@ +package env + +import ( + "os" + "fmt" + "time" + "github.com/joho/godotenv" + "github.com/reverbdotcom/sbx/errr" +) + +const format = "%-20s%-20s%-120s\n" +const durationTooLong = 8 * time.Hour + +const DURATION = "DURATION" +var allowlist = map[string]string{ + DURATION: "how long sandboxes live for", +} + +var warning = errr.Warning + +func Run() (string, error) { + output := fmt.Sprintf(format, "KEY", "VALUE", "DESCRIPTION") + output += fmt.Sprint("\n") + + for key, description := range allowlist { + val := Getenv(key) + if val == "" { + val = "n/a" + } + + output += fmt.Sprintf(format, key, val, description) + } + + return output, nil +} + +func Verify() (error) { + fmtErr := func(msg string) error { + return fmt.Errorf("invalid env, please fix the following issue: %s", msg) + } + + dur := Getenv(DURATION) + if dur != "" { + parsed, err := time.ParseDuration(dur) + if err != nil { + return fmtErr(fmt.Sprintf("%s is an invalid duration", dur)) + } + + if parsed > durationTooLong { + warning(fmt.Sprintf("%s is a long duration! this is okay on occassion, but consider lowering it", dur)) + } + } + + return nil +} + +var Getenv = _getenv +func _getenv(key string) string { + return readenv()[key] +} + +func readenv() map[string]string { + filepath := fmt.Sprintf("%s/.sbx", os.Getenv("HOME")) + + var err error + env, err := godotenv.Read(filepath) + if err != nil { + env = make(map[string]string) + } + + return env +} diff --git a/env/env_test.go b/env/env_test.go new file mode 100644 index 0000000..63f2151 --- /dev/null +++ b/env/env_test.go @@ -0,0 +1,42 @@ +package env + +import ( + "testing" +) + +func TestVerify(t *testing.T) { + t.Run("it errs if duration is invalid", func(t *testing.T) { + Getenv = func(key string) string { + if (key == DURATION) { + return "invalid" + } + + return "" + } + + err := Verify() + if err == nil { + t.Errorf("got nil, want error") + } + }) + + t.Run("it warns when duration is too long", func(t *testing.T) { + Getenv = func(key string) string { + if (key == DURATION) { + return "10h" + } + + return "" + } + + warned := false + warning = func(message string) { + warned = true + } + + _ = Verify() + if !warned { + t.Error("got no warning, want warning about duration being too long") + } + }) +} diff --git a/errr/errr.go b/errr/errr.go index f494c9c..45f1897 100644 --- a/errr/errr.go +++ b/errr/errr.go @@ -8,3 +8,7 @@ import ( func New(message string) error { return errors.New(fmt.Sprintf("🚫 %s", message)) } + +func Warning(message string) { + fmt.Printf("⚠️️ %s\n", message) +} diff --git a/go.mod b/go.mod index 03baf28..afe32d4 100644 --- a/go.mod +++ b/go.mod @@ -6,5 +6,6 @@ require github.com/google/go-github/v67 v67.0.0 require ( github.com/google/go-querystring v1.1.0 // indirect + github.com/joho/godotenv v1.5.1 // indirect golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect ) diff --git a/go.sum b/go.sum index 39a0b98..457a099 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/google/go-github/v67 v67.0.0 h1:g11NDAmfaBaCO8qYdI9fsmbaRipHNWRIU/2YG github.com/google/go-github/v67 v67.0.0/go.mod h1:zH3K7BxjFndr9QSeFibx4lTKkYS3K9nDanoI1NjaOtY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0= golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/name/name.go b/name/name.go index 5df37f3..d071245 100644 --- a/name/name.go +++ b/name/name.go @@ -9,11 +9,14 @@ import ( "strings" "github.com/reverbdotcom/sbx/cli" + "github.com/reverbdotcom/sbx/env" ) const maxStep = 2 const sandbox = "sandbox-" +var getenv = env.Getenv + func Run() (string, error) { return Name() } @@ -110,8 +113,17 @@ func hash(name string, step int) (string, error) { return strings.ToLower(words[index]), nil } -func prefix(name string) string { - return sandbox + name +func prefix(name string) (string) { + return sandbox + duration() + name +} + +func duration() (string) { + dur := getenv(env.DURATION) + if dur == "" { + return dur + } + + return fmt.Sprintf("%s-", dur) } func properNames() ([]string, error) { diff --git a/name/name_test.go b/name/name_test.go index 703a5f5..36c112a 100644 --- a/name/name_test.go +++ b/name/name_test.go @@ -9,13 +9,34 @@ func TestRun(t *testing.T) { return []string{"blake", "julian", "kevin"}, nil } + getenv = func(key string) (string) { + return "" + } + + Branch = func() (string, error) { + return "nn-sbx-1234", nil + } + t.Run("it generates a sandbox name", func(t *testing.T) { - Branch = func() (string, error) { - return "nn-sbx-1234", nil + got, err := Run() + want := "sandbox-blake-julian-kevin" + + if err != nil { + t.Errorf("got %v, want nil", err) + } + + if got != want { + t.Errorf("got %v, want %v", got, want) + } + }) + + t.Run("it includes duration when set", func(t *testing.T) { + getenv = func(key string) (string) { + return "5h" } got, err := Run() - want := "sandbox-blake-julian-kevin" + want := "sandbox-5h-blake-julian-kevin" if err != nil { t.Errorf("got %v, want nil", err) @@ -32,6 +53,10 @@ func TestProperNames(t *testing.T) { return []string{"blake", "julian", "kevin", "a", "super-long-name-that-does-not-fit"}, nil } + getenv = func(key string) (string) { + return "" + } + t.Run("it should be longer than 2 and less than 13", func(t *testing.T) { words, err := properNames() @@ -56,6 +81,10 @@ func TestName(t *testing.T) { return []string{"blake", "julian", "kevin"}, nil } + getenv = func(key string) (string) { + return "" + } + t.Run("it generates a sandbox name", func(t *testing.T) { Branch = func() (string, error) { return "nn-sbx-1234", nil diff --git a/parser/parser.go b/parser/parser.go index adc1d60..3a4f98d 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -37,6 +37,7 @@ func cmdfn(command string) (*commands.RunFn, error) { "h", "version", "v", + "env", } if !slices.Contains(general, command) { diff --git a/sbx.go b/sbx.go index 85468fa..7f1caac 100644 --- a/sbx.go +++ b/sbx.go @@ -4,11 +4,15 @@ import ( "fmt" "os" + "github.com/reverbdotcom/sbx/env" "github.com/reverbdotcom/sbx/errr" "github.com/reverbdotcom/sbx/parser" ) func main() { + err := env.Verify() + onError(err) + cmdfn, err := parser.Parse(os.Args) onError(err)