diff --git a/internal/boxcli/featureflag/envsec.go b/internal/boxcli/featureflag/envsec.go new file mode 100644 index 00000000000..4ab1169ee7b --- /dev/null +++ b/internal/boxcli/featureflag/envsec.go @@ -0,0 +1,6 @@ +// Copyright 2023 Jetpack Technologies Inc and contributors. All rights reserved. +// Use of this source code is governed by the license in the LICENSE file. + +package featureflag + +var Envsec = disable("ENVSEC") diff --git a/internal/devconfig/config.go b/internal/devconfig/config.go index 3f809a4ad60..bc2442cbe57 100644 --- a/internal/devconfig/config.go +++ b/internal/devconfig/config.go @@ -26,6 +26,10 @@ type Config struct { // Env allows specifying env variables Env map[string]string `json:"env,omitempty"` + + // Only allows "envsec" for now + FromEnv string `json:"from_env,omitempty"` + // Shell configures the devbox shell environment. Shell *shellConfig `json:"shell,omitempty"` // Nixpkgs specifies the repository to pull packages from diff --git a/internal/devconfig/env.go b/internal/devconfig/env.go new file mode 100644 index 00000000000..55ff62428db --- /dev/null +++ b/internal/devconfig/env.go @@ -0,0 +1,26 @@ +package devconfig + +import ( + "go.jetpack.io/devbox/internal/boxcli/featureflag" + "go.jetpack.io/devbox/internal/boxcli/usererr" + "go.jetpack.io/devbox/internal/integrations/envsec" +) + +func (c *Config) ComputedEnv(projectDir string) (map[string]string, error) { + env := map[string]string{} + var err error + if featureflag.Envsec.Enabled() { + if c.FromEnv == "envsec" { + env, err = envsec.Env(projectDir) + if err != nil { + return nil, err + } + } else if c.FromEnv != "" { + return nil, usererr.New("unknown from_env value: %s", c.FromEnv) + } + } + for k, v := range c.Env { + env[k] = v + } + return env, nil +} diff --git a/internal/impl/devbox.go b/internal/impl/devbox.go index 5444aad1e3b..94e91942095 100644 --- a/internal/impl/devbox.go +++ b/internal/impl/devbox.go @@ -872,7 +872,10 @@ func (d *Devbox) computeNixEnv(ctx context.Context, usePrintDevEnvCache bool) (m } // Include env variables in devbox.json - configEnv := d.configEnvs(env) + configEnv, err := d.configEnvs(env) + if err != nil { + return nil, err + } addEnvIfNotPreviouslySetByDevbox(env, configEnv) markEnvsAsSetByDevbox(pluginEnv, configEnv) @@ -1052,14 +1055,18 @@ func (d *Devbox) checkOldEnvrc() error { return nil } -// configEnvs takes the computed env variables (nix + plugin) and adds env +// configEnvs takes the existing environment (nix + plugin) and adds env // variables defined in Config. It also parses variables in config // that are referenced by $VAR or ${VAR} and replaces them with -// their value in the computed env variables. Note, this doesn't +// their value in the existing env variables. Note, this doesn't // allow env variables from outside the shell to be referenced so // no leaked variables are caused by this function. -func (d *Devbox) configEnvs(computedEnv map[string]string) map[string]string { - return conf.OSExpandEnvMap(d.cfg.Env, computedEnv, d.ProjectDir()) +func (d *Devbox) configEnvs(existingEnv map[string]string) (map[string]string, error) { + env, err := d.cfg.ComputedEnv(d.ProjectDir()) + if err != nil { + return nil, err + } + return conf.OSExpandEnvMap(env, existingEnv, d.ProjectDir()), nil } // ignoreCurrentEnvVar contains environment variables that Devbox should remove diff --git a/internal/integrations/envsec/envsec.go b/internal/integrations/envsec/envsec.go new file mode 100644 index 00000000000..e71e0cd4503 --- /dev/null +++ b/internal/integrations/envsec/envsec.go @@ -0,0 +1,63 @@ +package envsec + +import ( + "encoding/json" + "os/exec" + + "github.com/pkg/errors" + "go.jetpack.io/devbox/internal/boxcli/usererr" + "go.jetpack.io/devbox/internal/cmdutil" +) + +func Env(projectDir string) (map[string]string, error) { + + if err := ensureEnvsecInstalled(); err != nil { + return nil, err + } + + if err := ensureEnvsecInitialized(); err != nil { + return nil, err + } + + return envsecList(projectDir) +} + +func ensureEnvsecInstalled() error { + if !cmdutil.Exists("envsec") { + return usererr.New("envsec is not installed or not in path") + } + return nil +} + +func ensureEnvsecInitialized() error { + cmd := exec.Command("envsec", "init") + // TODO handle user not logged in + // envsec init is currently broken in that it exits with 0 even if the user is not logged in + return cmd.Run() +} + +func envsecList(projectDir string) (map[string]string, error) { + cmd := exec.Command( + "envsec", "ls", "--show", + "--format", "json", + "--environment", "dev") + cmd.Dir = projectDir + out, err := cmd.Output() + if err != nil { + return nil, errors.WithStack(err) + } + var values []struct { + Name string `json:"name"` + Value string `json:"value"` + } + + if err := json.Unmarshal(out, &values); err != nil { + return nil, errors.Wrap(err, "failed to parse envsec output") + } + + m := map[string]string{} + for _, v := range values { + m[v.Name] = v.Value + } + return m, nil +}