Skip to content

Commit

Permalink
cli: add local secrets override functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
eandre committed Nov 4, 2022
1 parent d172ad9 commit 6d81be4
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 17 deletions.
2 changes: 1 addition & 1 deletion cli/daemon/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ func (r *Run) buildAndStart(ctx context.Context, tracker *optracker.OpTracker) e
if r.App.PlatformID() == "" {
return fmt.Errorf("the app defines secrets, but is not yet linked to encore.dev; link it with `encore app link` to use secrets")
}
data, err := r.mgr.Secret.Get(ctx, r.App.PlatformID())
data, err := r.mgr.Secret.Get(ctx, r.App, expSet)
if err != nil {
return err
}
Expand Down
23 changes: 10 additions & 13 deletions cli/daemon/run/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,16 @@ type TestParams struct {

// Test runs the tests.
func (mgr *Manager) Test(ctx context.Context, params TestParams) (err error) {
var secrets map[string]string
if pid := params.App.PlatformID(); pid != "" {
data, err := mgr.Secret.Get(ctx, pid)
if err != nil {
return err
}
secrets = data.Values
expSet, err := params.App.Experiments(params.Environ)
if err != nil {
return err
}

secretData, err := mgr.Secret.Get(ctx, params.App, expSet)
if err != nil {
return err
}
secrets := secretData.Values

var (
sqlServers []*config.SQLServer
Expand Down Expand Up @@ -159,11 +161,6 @@ func (mgr *Manager) Test(ctx context.Context, params TestParams) (err error) {
return err
}

experiments, err := params.App.Experiments(params.Environ)
if err != nil {
return err
}

cfg := &compiler.Config{
Parse: params.Parse,
Revision: params.Parse.Meta.AppRevision,
Expand All @@ -174,7 +171,7 @@ func (mgr *Manager) Test(ctx context.Context, params TestParams) (err error) {
EncoreRuntimePath: env.EncoreRuntimePath(),
EncoreGoRoot: env.EncoreGoRoot(),
BuildTags: []string{"encore_local", "encore_no_gcp", "encore_no_aws", "encore_no_azure"},
Experiments: experiments,
Experiments: expSet,
Meta: &cueutil.Meta{
APIBaseURL: apiBaseURL,
EnvName: "local",
Expand Down
75 changes: 73 additions & 2 deletions cli/daemon/secret/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package secret

import (
"bytes"
"context"
"encoding/json"
"fmt"
Expand All @@ -10,10 +11,15 @@ import (
"sync"
"time"

"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
"cuelang.org/go/cue/load"
"github.com/rs/zerolog/log"
"golang.org/x/sync/singleflight"

"encr.dev/cli/daemon/apps"
"encr.dev/cli/internal/platform"
"encr.dev/internal/experiments"
)

// New returns a new manager.
Expand All @@ -32,16 +38,29 @@ type Manager struct {

// Data is a snapshot of an Encore app's development secret values.
type Data struct {
// Synced is when the values were last synced.
// Synced is when the values were last synced,
// or the zero value if no sync has taken place.
Synced time.Time
// Values is a key-value map of defined secrets.
Values map[string]string
}

// Get gets the secrets for the given app.
func (f *Manager) Get(ctx context.Context, appSlug string) (*Data, error) {
func (f *Manager) Get(ctx context.Context, app *apps.Instance, expSet *experiments.Set) (data *Data, err error) {
f.pollOnce.Do(f.startPolling)

defer func() {
if err == nil && experiments.LocalSecretsOverride.Enabled(expSet) {
// Return a new data object so we don't write the overrides to the cache.
data, err = f.applyLocalOverrides(app, data)
}
}()

appSlug := app.PlatformID()
if appSlug == "" {
return &Data{}, nil
}

// Do we have the secrets in our cache?
f.mu.Lock()
data, ok := f.cache[appSlug]
Expand Down Expand Up @@ -201,3 +220,55 @@ func (f *Manager) secretsPath(appSlug string) (string, error) {
}
return filepath.Join(dir, "encore", "secrets", appSlug+".json"), nil
}

// applyLocalOverrides parses the local secrets override file, if any,
// and returns a new Data object with the overrides applied.
//
// If there are no overrides src is returned directly.
// The original src data object is never modified.
func (f *Manager) applyLocalOverrides(app *apps.Instance, src *Data) (*Data, error) {
const name = ".secrets.local.cue"
data, err := os.ReadFile(filepath.Join(app.Root(), name))
if err != nil {
if os.IsNotExist(err) {
return src, nil
}
return nil, err
}

updated := &Data{
Synced: src.Synced,
Values: make(map[string]string, len(src.Values)),
}
for k, v := range src.Values {
updated.Values[k] = v
}

ctx := cuecontext.New()
loadCfg := &load.Config{
Stdin: bytes.NewReader(data),
}

inst := load.Instances([]string{"-"}, loadCfg)[0]
if inst.Err != nil {
return nil, fmt.Errorf("parse local secrets: %v", inst.Err)
}
secrets := ctx.BuildInstance(inst)
if err := secrets.Err(); err != nil {
return nil, fmt.Errorf("parse local secrets: %v", err)
}

it, err := secrets.Fields(cue.Hidden(false), cue.Concrete(true))
if err != nil {
return nil, fmt.Errorf("parse local secrets: %v", err)
}
for it.Next() {
key := it.Selector().String()
val, err := it.Value().String()
if err != nil {
return nil, fmt.Errorf("parse local secrets: secret key %s is not a string", key)
}
updated.Values[key] = val
}
return updated, nil
}
5 changes: 5 additions & 0 deletions e2e-tests/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ func RunApp(c testing.TB, appRoot string, logger RunLogger, env []string) *RunAp
expSet, err := experiments.NewSet(nil, env)
assertNil(err)

secrets := secret.New()
secretData, err := secrets.Get(ctx, app, expSet)
assertNil(err)

p, err := run.StartProc(&StartProcParams{
Ctx: ctx,
BuildDir: build.Dir,
Expand All @@ -87,6 +91,7 @@ func RunApp(c testing.TB, appRoot string, logger RunLogger, env []string) *RunAp
Redis: redisSrv,
ServiceConfigs: build.Configs,
Experiments: expSet,
Secrets: secretData.Values,
})
assertNil(err)
c.Cleanup(p.Close)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
env ENCORE_EXPERIMENT=local-secrets-override
run
call GET /svc.GetFoo
checkresp '{"Data": "bar"}'

-- svc/svc.go --
package svc

import "context"

type Resp struct {
Data string
}

var secrets struct {
Foo string
}

//encore:api public
func GetFoo(ctx context.Context) (*Resp, error) {
return &Resp{Data: secrets.Foo}, nil
}

-- .secrets.local.cue --
Foo: "bar"
9 changes: 8 additions & 1 deletion internal/experiments/experiment.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,18 @@ const envName = "ENCORE_EXPERIMENT"
type Name string

const (
/* Current experiments are listed here */
/* Current experiments are listed here */

// LocalSecretsOverride is an experiment to allow for secrets
// to be overridden with values from a ".secrets.local" file.
LocalSecretsOverride Name = "local-secrets-override"
)

// Valid reports whether the given name is a known experiment.
func (x Name) Valid() bool {
switch x {
case LocalSecretsOverride:
return true
default:
return false
}
Expand Down

0 comments on commit 6d81be4

Please sign in to comment.