From 5b7b1cab79148dd7da9399667fdcae352e86ac1f Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Tue, 30 Nov 2021 17:51:28 -0800 Subject: [PATCH] Migrate dagger.#Secret and dagger.#Stream to new format - Refactored to keep every transformation of built-in types (e.g. FS, Secret, etc) to/from CUE in the same place (plancontext) - dagger.#Service and dagger.#Secret are now following the new FS-like format (e.g. `_service: id: string`) - Backward compatibility - dagger.#Stream is now an alias for dagger.#Service Signed-off-by: Andrea Luzzardi --- client/client.go | 2 +- cmd/dagger/cmd/common/common.go | 6 +- compiler/compiler.go | 12 ---- docs/reference/argocd.md | 16 ++--- docs/reference/dagger/README.md | 12 ++++ docs/reference/git.md | 6 +- docs/reference/kubernetes/README.md | 2 +- docs/reference/kubernetes/helm.md | 2 +- environment/pipeline.go | 56 +++++------------ plan/plan.go | 5 +- plan/task/import.go | 15 ++--- plan/task/secretenv.go | 14 ++--- plan/task/secretfile.go | 13 ++-- plancontext/context.go | 29 ++++----- plancontext/context_test.go | 10 ++- plancontext/fs.go | 55 ++++++++++++++--- plancontext/localdir.go | 35 ++--------- plancontext/secret.go | 71 +++++++++++++++++++--- plancontext/service.go | 81 +++++++++++++++++++++---- solver/secretsprovider.go | 4 +- solver/socketprovider.go | 2 +- solver/socketprovider_unix.go | 4 +- solver/socketprovider_windows.go | 4 +- state/input.go | 20 ++---- stdlib/dagger/dagger.cue | 19 +++--- tests/compute/secrets/simple/simple.cue | 2 - tests/core.bats | 4 -- 27 files changed, 284 insertions(+), 217 deletions(-) diff --git a/client/client.go b/client/client.go index 89fb6139..4e411743 100644 --- a/client/client.go +++ b/client/client.go @@ -217,7 +217,7 @@ func (c *Client) logSolveStatus(ctx context.Context, pctx *plancontext.Context, secureSprintf := func(format string, a ...interface{}) string { s := fmt.Sprintf(format, a...) for _, secret := range secrets { - s = strings.ReplaceAll(s, secret.PlainText, "***") + s = strings.ReplaceAll(s, secret.PlainText(), "***") } return s } diff --git a/cmd/dagger/cmd/common/common.go b/cmd/dagger/cmd/common/common.go index c9955894..bb875234 100644 --- a/cmd/dagger/cmd/common/common.go +++ b/cmd/dagger/cmd/common/common.go @@ -88,7 +88,11 @@ func FormatValue(val *compiler.Value) string { if val.HasAttr("artifact") { return "dagger.#Artifact" } - if val.HasAttr("secret") { + + if val.LookupPath(cue.MakePath( + cue.Hid("_secret", "alpha.dagger.io/dagger"), + cue.Str("id"), + )).Exists() { return "dagger.#Secret" } if val.IsConcreteR() != nil { diff --git a/compiler/compiler.go b/compiler/compiler.go index 1dd4b7c8..0f3c5784 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -24,10 +24,6 @@ func NewValue() *Value { return DefaultCompiler.NewValue() } -func NewValueWithContent(x interface{}, selectors ...cue.Selector) (*Value, error) { - return DefaultCompiler.NewValueWithContent(x, selectors...) -} - // FIXME can be refactored away now? func Wrap(v cue.Value) *Value { return DefaultCompiler.Wrap(v) @@ -84,14 +80,6 @@ func (c *Compiler) NewValue() *Value { return empty } -func (c *Compiler) NewValueWithContent(x interface{}, selectors ...cue.Selector) (*Value, error) { - v := c.NewValue() - if err := v.FillPath(cue.MakePath(selectors...), x); err != nil { - return nil, err - } - return v, nil -} - func (c *Compiler) Compile(name string, src string) (*Value, error) { c.lock() defer c.unlock() diff --git a/docs/reference/argocd.md b/docs/reference/argocd.md index c29bb096..11264085 100644 --- a/docs/reference/argocd.md +++ b/docs/reference/argocd.md @@ -21,7 +21,7 @@ Create an ArgoCD application |*config.version* | `*"v2.0.5" \| string` |ArgoCD CLI binary version | |*config.server* | `string` |ArgoCD server | |*config.project* | `*"default" \| string` |ArgoCD project | -|*config.token* | `dagger.#Secret` |ArgoCD authentication token | +|*config.token* | `*null \| {}` |ArgoCD authentication token | |*name* | `string` |App name | |*repo* | `string` |Repository url (git or helm) | |*path* | `string` |Folder to deploy | @@ -29,7 +29,7 @@ Create an ArgoCD application |*image.config.version* | `*"v2.0.5" \| string` |ArgoCD CLI binary version | |*image.config.server* | `string` |ArgoCD server | |*image.config.project* | `*"default" \| string` |ArgoCD project | -|*image.config.token* | `dagger.#Secret` |ArgoCD authentication token | +|*image.config.token* | `*null \| {}` |ArgoCD authentication token | |*namespace* | `*"default" \| string` |Destination namespace | |*env.APP_NAME* | `string` |- | |*env.APP_REPO* | `string` |- | @@ -52,7 +52,7 @@ Re-usable CLI component |*config.version* | `*"v2.0.5" \| string` |ArgoCD CLI binary version | |*config.server* | `string` |ArgoCD server | |*config.project* | `*"default" \| string` |ArgoCD project | -|*config.token* | `dagger.#Secret` |ArgoCD authentication token | +|*config.token* | `*null \| {}` |ArgoCD authentication token | ### argocd.#CLI Outputs @@ -69,7 +69,7 @@ ArgoCD configuration |*version* | `*"v2.0.5" \| string` |ArgoCD CLI binary version | |*server* | `string` |ArgoCD server | |*project* | `*"default" \| string` |ArgoCD project | -|*token* | `dagger.#Secret` |ArgoCD authentication token | +|*token* | `*null \| {}` |ArgoCD authentication token | ### argocd.#Config Outputs @@ -86,7 +86,7 @@ Get application's status |*config.version* | `*"v2.0.5" \| string` |ArgoCD CLI binary version | |*config.server* | `string` |ArgoCD server | |*config.project* | `*"default" \| string` |ArgoCD project | -|*config.token* | `dagger.#Secret` |ArgoCD authentication token | +|*config.token* | `*null \| {}` |ArgoCD authentication token | |*name* | `string` |ArgoCD application | ### argocd.#Status Outputs @@ -111,18 +111,18 @@ Sync an application to its targer state |*config.version* | `*"v2.0.5" \| string` |ArgoCD CLI binary version | |*config.server* | `string` |ArgoCD server | |*config.project* | `*"default" \| string` |ArgoCD project | -|*config.token* | `dagger.#Secret` |ArgoCD authentication token | +|*config.token* | `*null \| {}` |ArgoCD authentication token | |*application* | `string` |ArgoCD application | |*wait* | `*false \| bool` |Wait the application to sync correctly | |*ctr.image.config.version* | `*"v2.0.5" \| string` |ArgoCD CLI binary version | |*ctr.image.config.server* | `string` |ArgoCD server | |*ctr.image.config.project* | `*"default" \| string` |ArgoCD project | -|*ctr.image.config.token* | `dagger.#Secret` |ArgoCD authentication token | +|*ctr.image.config.token* | `*null \| {}` |ArgoCD authentication token | |*ctr.env.APPLICATION* | `string` |- | |*status.config.version* | `*"v2.0.5" \| string` |ArgoCD CLI binary version | |*status.config.server* | `string` |ArgoCD server | |*status.config.project* | `*"default" \| string` |ArgoCD project | -|*status.config.token* | `dagger.#Secret` |ArgoCD authentication token | +|*status.config.token* | `*null \| {}` |ArgoCD authentication token | |*status.name* | `string` |ArgoCD application | ### argocd.#Sync Outputs diff --git a/docs/reference/dagger/README.md b/docs/reference/dagger/README.md index 3f545bc6..fd9a775f 100644 --- a/docs/reference/dagger/README.md +++ b/docs/reference/dagger/README.md @@ -56,6 +56,18 @@ _No input._ _No output._ +## dagger.#Service + +A reference to a network service endpoint, for example: - A TCP or UDP port - A unix or npipe socket - An HTTPS endpoint + +### dagger.#Service Inputs + +_No input._ + +### dagger.#Service Outputs + +_No output._ + ## dagger.#Stream Dagger stream. Can be mounted as a UNIX socket. diff --git a/docs/reference/git.md b/docs/reference/git.md index 59990600..bb0d9bfa 100644 --- a/docs/reference/git.md +++ b/docs/reference/git.md @@ -19,7 +19,7 @@ Commit & push to git repository | Name | Type | Description | | ------------- |:-------------: |:-------------: | |*repository.remote* | `string` |Repository remote URL | -|*repository.authToken* | `dagger.#Secret` |Authentication token (PAT or password) | +|*repository.authToken* | `*null \| {}` |Authentication token (PAT or password) | |*repository.branch* | `string` |Git branch | |*name* | `string` |Username | |*email* | `string` |Email | @@ -69,8 +69,8 @@ A git repository |*remote* | `string` |Git remote link | |*ref* | `string` |Git ref: can be a commit, tag or branch. Example: "main" | |*subdir* | `*null \| string` |(optional) Subdirectory | -|*authToken* | `dagger.#Secret` |(optional) Add Personal Access Token | -|*authHeader* | `dagger.#Secret` |(optional) Add OAuth Token | +|*authToken* | `*null \| {}` |(optional) Add Personal Access Token | +|*authHeader* | `*null \| {}` |(optional) Add OAuth Token | ### git.#Repository Outputs diff --git a/docs/reference/kubernetes/README.md b/docs/reference/kubernetes/README.md index 0e702977..e09fbfc6 100644 --- a/docs/reference/kubernetes/README.md +++ b/docs/reference/kubernetes/README.md @@ -37,7 +37,7 @@ Apply Kubernetes resources |*url* | `*null \| string` |Kubernetes manifest url to deploy remote configuration | |*namespace* | `*"default" \| string` |Kubernetes Namespace to deploy to | |*version* | `*"v1.19.9" \| string` |Version of kubectl client | -|*kubeconfig* | `dagger.#Secret` |Kube config file | +|*kubeconfig* | `(string\|struct)` |Kube config file | ### kubernetes.#Resources Outputs diff --git a/docs/reference/kubernetes/helm.md b/docs/reference/kubernetes/helm.md index f17e5e56..dab0ea92 100644 --- a/docs/reference/kubernetes/helm.md +++ b/docs/reference/kubernetes/helm.md @@ -27,7 +27,7 @@ Install a Helm chart |*timeout* | `*"5m" \| string` |time to wait for any individual Kubernetes operation (like Jobs for hooks) | |*wait* | `*true \| bool` |if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as timeout | |*atomic* | `*true \| bool` |if set, installation process purges chart on fail. The wait option will be set automatically if atomic is used | -|*kubeconfig* | `dagger.#Secret` |Kube config file | +|*kubeconfig* | `(string\|struct)` |Kube config file | |*version* | `*"3.5.2" \| string` |Helm version | |*kubectlVersion* | `*"v1.19.9" \| string` |Kubectl version | diff --git a/environment/pipeline.go b/environment/pipeline.go index e1155891..65647097 100644 --- a/environment/pipeline.go +++ b/environment/pipeline.go @@ -270,16 +270,11 @@ func (p *Pipeline) doOp(ctx context.Context, op *compiler.Value, st llb.State) ( // dagger.#FS forward compat // FIXME: remove this if isFS(op) { - id, err := op.LookupPath(fsIDPath).String() + fs, err := p.pctx.FS.FromValue(op) if err != nil { - return st, err + return st, nil } - - fs := p.pctx.FS.Get(plancontext.ContextKey(id)) - if fs == nil { - return st, fmt.Errorf("fs %q not found", id) - } - return fs.Result.ToState() + return fs.Result().ToState() } do, err := op.Lookup("do").String() @@ -595,30 +590,26 @@ func (p *Pipeline) mount(ctx context.Context, dest string, mnt *compiler.Value) // eg. mount: "/foo": secret: mysecret if secret := mnt.Lookup("secret"); secret.Exists() { - id, err := getSecretID(secret) + s, err := p.pctx.Secrets.FromValue(secret) if err != nil { return nil, err } return llb.AddSecret(dest, - llb.SecretID(id), + llb.SecretID(s.ID()), llb.SecretFileOpt(0, 0, 0400), // uid, gid, mask) ), nil } // eg. mount: "/var/run/docker.sock": stream: mystream if stream := mnt.Lookup("stream"); stream.Exists() { - if !stream.HasAttr("stream") { - return nil, fmt.Errorf("invalid stream %q: not a stream", stream.Path().String()) - } - - id, err := stream.Lookup("id").String() + s, err := p.pctx.Services.FromValue(stream) if err != nil { - return nil, fmt.Errorf("invalid stream %q: %w", stream.Path().String(), err) + return nil, err } return llb.AddSSHSocket( - llb.SSHID(id), + llb.SSHID(s.ID()), llb.SSHSocketTarget(dest), ), nil } @@ -761,15 +752,11 @@ func parseStringOrSecret(pctx *plancontext.Context, v *compiler.Value) (string, } // If we get here, it's a secret - id, err := getSecretID(v) + secret, err := pctx.Secrets.FromValue(v) if err != nil { return "", err } - secret := pctx.Secrets.Get(plancontext.ContextKey(id)) - if secret == nil { - return "", fmt.Errorf("secret %s not found", id) - } - return secret.PlainText, nil + return secret.PlainText(), nil } func (p *Pipeline) Load(ctx context.Context, op *compiler.Value, st llb.State) (llb.State, error) { @@ -973,21 +960,6 @@ func (p *Pipeline) SaveImage(ctx context.Context, op *compiler.Value, st llb.Sta ), nil } -func getSecretID(secretField *compiler.Value) (string, error) { - if !secretField.HasAttr("secret") { - return "", fmt.Errorf("invalid secret %q: not a secret", secretField.Path().String()) - } - idValue := secretField.Lookup("id") - if !idValue.Exists() { - return "", fmt.Errorf("invalid secret %q: no id field", secretField.Path().String()) - } - id, err := idValue.String() - if err != nil { - return "", fmt.Errorf("invalid secret id: %w", err) - } - return id, nil -} - func (p *Pipeline) FetchGit(ctx context.Context, op *compiler.Value, st llb.State) (llb.State, error) { remote, err := op.Lookup("remote").String() if err != nil { @@ -1017,18 +989,18 @@ func (p *Pipeline) FetchGit(ctx context.Context, op *compiler.Value, st llb.Stat } // Secret if authToken := op.Lookup("authToken"); authToken.Exists() { - id, err := getSecretID(authToken) + authTokenSecret, err := p.pctx.Secrets.FromValue(authToken) if err != nil { return st, err } - gitOpts = append(gitOpts, llb.AuthTokenSecret(id)) + gitOpts = append(gitOpts, llb.AuthTokenSecret(authTokenSecret.ID())) } if authHeader := op.Lookup("authHeader"); authHeader.Exists() { - id, err := getSecretID(authHeader) + authHeaderSecret, err := p.pctx.Secrets.FromValue(authHeader) if err != nil { return st, err } - gitOpts = append(gitOpts, llb.AuthHeaderSecret(id)) + gitOpts = append(gitOpts, llb.AuthHeaderSecret(authHeaderSecret.ID())) } gitOpts = append(gitOpts, llb.WithCustomName(p.vertexNamef("FetchGit %s@%s", remoteRedacted, ref))) diff --git a/plan/plan.go b/plan/plan.go index f41364c4..d6802fbb 100644 --- a/plan/plan.go +++ b/plan/plan.go @@ -67,10 +67,7 @@ func (p *Plan) registerLocalDirs() error { if err != nil { return err } - - p.context.LocalDirs.Register(&plancontext.LocalDir{ - Path: dir, - }) + p.context.LocalDirs.Add(dir) } return nil diff --git a/plan/task/import.go b/plan/task/import.go index 5f54e5e6..20feb38c 100644 --- a/plan/task/import.go +++ b/plan/task/import.go @@ -68,13 +68,10 @@ func (c importTask) Run(ctx context.Context, pctx *plancontext.Context, s solver return nil, err } - id := pctx.FS.Register(&plancontext.FS{ - Result: result, - }) - - return compiler.NewValueWithContent(id, - cue.Str("fs"), - cue.Hid("_fs", "alpha.dagger.io/dagger"), - cue.Str("id"), - ) + fs := pctx.FS.New(result) + out := compiler.NewValue() + if err := out.FillPath(cue.ParsePath("fs"), fs.Value()); err != nil { + return nil, err + } + return out, nil } diff --git a/plan/task/secretenv.go b/plan/task/secretenv.go index 6ec3c821..90fc0b6d 100644 --- a/plan/task/secretenv.go +++ b/plan/task/secretenv.go @@ -36,12 +36,10 @@ func (c secretEnvTask) Run(ctx context.Context, pctx *plancontext.Context, _ sol if env == "" { return nil, fmt.Errorf("environment variable %q not set", secretEnv.Envvar) } - id := pctx.Secrets.Register(&plancontext.Secret{ - PlainText: env, - }) - - return compiler.NewValueWithContent(id, - cue.Str("contents"), - cue.Str("id"), - ) + secret := pctx.Secrets.New(env) + out := compiler.NewValue() + if err := out.FillPath(cue.ParsePath("contents"), secret.Value()); err != nil { + return nil, err + } + return out, nil } diff --git a/plan/task/secretfile.go b/plan/task/secretfile.go index e75d056d..2aba3eb0 100644 --- a/plan/task/secretfile.go +++ b/plan/task/secretfile.go @@ -35,12 +35,11 @@ func (c secretFileTask) Run(ctx context.Context, pctx *plancontext.Context, _ so if err != nil { return nil, err } - id := pctx.Secrets.Register(&plancontext.Secret{ - PlainText: string(data), - }) - return compiler.NewValueWithContent(id, - cue.Str("contents"), - cue.Str("id"), - ) + secret := pctx.Secrets.New(string(data)) + out := compiler.NewValue() + if err := out.FillPath(cue.ParsePath("contents"), secret.Value()); err != nil { + return nil, err + } + return out, nil } diff --git a/plancontext/context.go b/plancontext/context.go index 2efa74cf..8b4fc548 100644 --- a/plancontext/context.go +++ b/plancontext/context.go @@ -2,18 +2,10 @@ package plancontext import ( "crypto/sha256" - "encoding/json" "fmt" ) -type ContextKey string - // Context holds the execution context for a plan. -// -// Usage: -// ctx := plancontext.New() -// id := ctx.Secrets.Register("mysecret") -// secret := ctx.Secrets.Get(id) type Context struct { Platform *platformContext FS *fsContext @@ -28,25 +20,26 @@ func New() *Context { platform: defaultPlatform, }, FS: &fsContext{ - store: make(map[ContextKey]*FS), + store: make(map[string]*FS), }, LocalDirs: &localDirContext{ - store: make(map[ContextKey]*LocalDir), + store: []string{}, }, Secrets: &secretContext{ - store: make(map[ContextKey]*Secret), + store: make(map[string]*Secret), }, Services: &serviceContext{ - store: make(map[ContextKey]*Service), + store: make(map[string]*Service), }, } } -func hashID(v interface{}) ContextKey { - data, err := json.Marshal(v) - if err != nil { - panic(err) +func hashID(values ...string) string { + hash := sha256.New() + for _, v := range values { + if _, err := hash.Write([]byte(v)); err != nil { + panic(err) + } } - hash := sha256.Sum256(data) - return ContextKey(fmt.Sprintf("%x", hash)) + return fmt.Sprintf("%x", hash) } diff --git a/plancontext/context_test.go b/plancontext/context_test.go index 652c22fa..bbd3e300 100644 --- a/plancontext/context_test.go +++ b/plancontext/context_test.go @@ -9,10 +9,8 @@ import ( func TestContext(t *testing.T) { ctx := New() - id := ctx.Secrets.Register(&Secret{ - PlainText: "test", - }) - secret := ctx.Secrets.Get(id) - require.NotNil(t, secret) - require.Equal(t, "test", secret.PlainText) + secret := ctx.Secrets.New("test") + get, err := ctx.Secrets.FromValue(secret.Value()) + require.NoError(t, err) + require.Equal(t, "test", get.PlainText()) } diff --git a/plancontext/fs.go b/plancontext/fs.go index 7a8b1267..8073b95c 100644 --- a/plancontext/fs.go +++ b/plancontext/fs.go @@ -1,32 +1,71 @@ package plancontext import ( + "fmt" "sync" + "cuelang.org/go/cue" + "github.com/google/uuid" bkgw "github.com/moby/buildkit/frontend/gateway/client" + "go.dagger.io/dagger/compiler" +) + +var ( + fsIDPath = cue.MakePath( + cue.Hid("_fs", "alpha.dagger.io/dagger"), + cue.Str("id"), + ) ) type FS struct { - Result bkgw.Reference + id string + result bkgw.Reference +} + +func (fs *FS) Result() bkgw.Reference { + return fs.result +} + +func (fs *FS) Value() *compiler.Value { + v := compiler.NewValue() + if err := v.FillPath(fsIDPath, fs.id); err != nil { + panic(err) + } + return v } type fsContext struct { l sync.RWMutex - store map[ContextKey]*FS + store map[string]*FS } -func (c *fsContext) Register(fs *FS) ContextKey { +func (c *fsContext) New(result bkgw.Reference) *FS { c.l.Lock() defer c.l.Unlock() - id := hashID(fs) - c.store[id] = fs - return id + fs := &FS{ + // FIXME: get a hash from result instead + id: uuid.New().String(), + result: result, + } + + c.store[fs.id] = fs + return fs } -func (c *fsContext) Get(id ContextKey) *FS { +func (c *fsContext) FromValue(v *compiler.Value) (*FS, error) { c.l.RLock() defer c.l.RUnlock() - return c.store[id] + id, err := v.LookupPath(fsIDPath).String() + if err != nil { + return nil, fmt.Errorf("invalid FS %q: %w", v.Path(), err) + } + + fs, ok := c.store[id] + if !ok { + return nil, fmt.Errorf("fs %q not found", id) + } + + return fs, nil } diff --git a/plancontext/localdir.go b/plancontext/localdir.go index b2ea9494..8600cb61 100644 --- a/plancontext/localdir.go +++ b/plancontext/localdir.go @@ -5,41 +5,16 @@ import ( "sync" ) -type LocalDir struct { - Path string -} - type localDirContext struct { l sync.RWMutex - store map[ContextKey]*LocalDir + store []string } -func (c *localDirContext) Register(directory *LocalDir) ContextKey { +func (c *localDirContext) Add(dir string) { c.l.Lock() defer c.l.Unlock() - id := hashID(directory) - c.store[id] = directory - return id -} - -func (c *localDirContext) Get(id ContextKey) *LocalDir { - c.l.RLock() - defer c.l.RUnlock() - - return c.store[id] -} - -func (c *localDirContext) List() []*LocalDir { - c.l.RLock() - defer c.l.RUnlock() - - directories := make([]*LocalDir, 0, len(c.store)) - for _, d := range c.store { - directories = append(directories, d) - } - - return directories + c.store = append(c.store, dir) } func (c *localDirContext) Paths() (map[string]string, error) { @@ -48,12 +23,12 @@ func (c *localDirContext) Paths() (map[string]string, error) { directories := make(map[string]string) for _, d := range c.store { - abs, err := filepath.Abs(d.Path) + abs, err := filepath.Abs(d) if err != nil { return nil, err } - directories[d.Path] = abs + directories[d] = abs } return directories, nil diff --git a/plancontext/secret.go b/plancontext/secret.go index 2c052d69..6ccd75c1 100644 --- a/plancontext/secret.go +++ b/plancontext/secret.go @@ -1,26 +1,77 @@ package plancontext -import "sync" +import ( + "fmt" + "sync" + + "cuelang.org/go/cue" + "go.dagger.io/dagger/compiler" +) + +var ( + secretIDPath = cue.MakePath( + cue.Hid("_secret", "alpha.dagger.io/dagger"), + cue.Str("id"), + ) +) + +type Secret struct { + id string + plainText string +} + +func (s *Secret) ID() string { + return s.id +} + +func (s *Secret) PlainText() string { + return s.plainText +} + +func (s *Secret) Value() *compiler.Value { + v := compiler.NewValue() + if err := v.FillPath(secretIDPath, s.id); err != nil { + panic(err) + } + return v +} type secretContext struct { l sync.RWMutex - store map[ContextKey]*Secret + store map[string]*Secret } -type Secret struct { - PlainText string -} +func (c *secretContext) New(plaintext string) *Secret { + secret := &Secret{ + id: hashID(plaintext), + plainText: plaintext, + } -func (c *secretContext) Register(secret *Secret) ContextKey { c.l.Lock() defer c.l.Unlock() - id := hashID(secret.PlainText) - c.store[id] = secret - return id + c.store[secret.id] = secret + return secret } -func (c *secretContext) Get(id ContextKey) *Secret { +func (c *secretContext) FromValue(v *compiler.Value) (*Secret, error) { + c.l.RLock() + defer c.l.RUnlock() + + id, err := v.LookupPath(secretIDPath).String() + if err != nil { + return nil, fmt.Errorf("invalid secret %q: %w", v.Path(), err) + } + + secret, ok := c.store[id] + if !ok { + return nil, fmt.Errorf("secret %q not found", id) + } + + return secret, nil +} + +func (c *secretContext) Get(id string) *Secret { c.l.RLock() defer c.l.RUnlock() diff --git a/plancontext/service.go b/plancontext/service.go index 3dbffdc2..3f5ad71f 100644 --- a/plancontext/service.go +++ b/plancontext/service.go @@ -1,27 +1,84 @@ package plancontext -import "sync" +import ( + "fmt" + "sync" + + "cuelang.org/go/cue" + "go.dagger.io/dagger/compiler" +) + +var ( + serviceIDPath = cue.MakePath( + cue.Hid("_service", "alpha.dagger.io/dagger"), + cue.Str("id"), + ) +) + +type Service struct { + id string + + unix string + npipe string +} + +func (s *Service) ID() string { + return s.id +} + +func (s *Service) Unix() string { + return s.unix +} + +func (s *Service) NPipe() string { + return s.npipe +} + +func (s *Service) Value() *compiler.Value { + v := compiler.NewValue() + if err := v.FillPath(serviceIDPath, s.id); err != nil { + panic(err) + } + return v +} type serviceContext struct { l sync.RWMutex - store map[ContextKey]*Service + store map[string]*Service } -type Service struct { - Unix string - Npipe string -} - -func (c *serviceContext) Register(service *Service) ContextKey { +func (c *serviceContext) New(unix, npipe string) *Service { c.l.Lock() defer c.l.Unlock() - id := hashID(service) - c.store[id] = service - return id + s := &Service{ + id: hashID(unix, npipe), + unix: unix, + npipe: npipe, + } + + c.store[s.id] = s + return s } -func (c *serviceContext) Get(id ContextKey) *Service { +func (c *serviceContext) FromValue(v *compiler.Value) (*Service, error) { + c.l.RLock() + defer c.l.RUnlock() + + id, err := v.LookupPath(serviceIDPath).String() + if err != nil { + return nil, fmt.Errorf("invalid service %q: %w", v.Path(), err) + } + + s, ok := c.store[id] + if !ok { + return nil, fmt.Errorf("service %q not found", id) + } + + return s, nil +} + +func (c *serviceContext) Get(id string) *Service { c.l.RLock() defer c.l.RUnlock() diff --git a/solver/secretsprovider.go b/solver/secretsprovider.go index 8e811bde..664f70a3 100644 --- a/solver/secretsprovider.go +++ b/solver/secretsprovider.go @@ -21,7 +21,7 @@ type inputStore struct { func (s *inputStore) GetSecret(ctx context.Context, id string) ([]byte, error) { lg := log.Ctx(ctx) - secret := s.pctx.Secrets.Get(plancontext.ContextKey(id)) + secret := s.pctx.Secrets.Get(id) if secret == nil { return nil, secrets.ErrNotFound } @@ -31,5 +31,5 @@ func (s *inputStore) GetSecret(ctx context.Context, id string) ([]byte, error) { Str("id", id). Msg("injecting secret") - return []byte(secret.PlainText), nil + return []byte(secret.PlainText()), nil } diff --git a/solver/socketprovider.go b/solver/socketprovider.go index 00d9ec94..25358094 100644 --- a/solver/socketprovider.go +++ b/solver/socketprovider.go @@ -36,7 +36,7 @@ func (sp *SocketProvider) ForwardAgent(stream sshforward.SSH_ForwardAgentServer) id = v[0] } - service := sp.pctx.Services.Get(plancontext.ContextKey(id)) + service := sp.pctx.Services.Get(id) if service == nil { return fmt.Errorf("invalid socket id %q", id) } diff --git a/solver/socketprovider_unix.go b/solver/socketprovider_unix.go index 529ed0ea..fb83ce1b 100644 --- a/solver/socketprovider_unix.go +++ b/solver/socketprovider_unix.go @@ -12,9 +12,9 @@ import ( ) func dialService(service *plancontext.Service) (net.Conn, error) { - if service.Unix == "" { + if service.Unix() == "" { return nil, errors.New("unsupported socket type") } - return net.DialTimeout("unix", service.Unix, time.Second) + return net.DialTimeout("unix", service.Unix(), time.Second) } diff --git a/solver/socketprovider_windows.go b/solver/socketprovider_windows.go index 0a425527..d643e5e9 100644 --- a/solver/socketprovider_windows.go +++ b/solver/socketprovider_windows.go @@ -13,10 +13,10 @@ import ( ) func dialService(service *plancontext.Service) (net.Conn, error) { - if service.Npipe == "" { + if service.NPipe() == "" { return nil, errors.New("unsupported socket type") } dur := time.Second - return winio.DialPipe(service.Npipe, &dur) + return winio.DialPipe(service.NPipe(), &dur) } diff --git a/state/input.go b/state/input.go index 48f561b2..22b1fefd 100644 --- a/state/input.go +++ b/state/input.go @@ -12,7 +12,6 @@ import ( "cuelang.org/go/cue" "go.dagger.io/dagger/compiler" - "go.dagger.io/dagger/plancontext" ) // An input is a value or artifact supplied by the user. @@ -121,9 +120,7 @@ func (dir dirInput) Compile(state *State) (*compiler.Value, error) { return nil, err } - state.Context.LocalDirs.Register(&plancontext.LocalDir{ - Path: p, - }) + state.Context.LocalDirs.Add(p) llb := fmt.Sprintf( `#up: [{do: "local", dir: %s, include: %s, exclude: %s}]`, @@ -195,11 +192,8 @@ func SecretInput(data string) Input { type secretInput string func (i secretInput) Compile(st *State) (*compiler.Value, error) { - id := st.Context.Secrets.Register(&plancontext.Secret{ - PlainText: i.PlainText(), - }) - secretValue := fmt.Sprintf(`{id: %q}`, id) - return compiler.Compile("", secretValue) + secret := st.Context.Secrets.New(i.PlainText()) + return secret.Value(), nil } func (i secretInput) PlainText() string { @@ -301,10 +295,6 @@ type socketInput struct { } func (i socketInput) Compile(st *State) (*compiler.Value, error) { - id := st.Context.Services.Register(&plancontext.Service{ - Unix: i.Unix, - Npipe: i.Npipe, - }) - socketValue := fmt.Sprintf(`{id: %q}`, id) - return compiler.Compile("", socketValue) + service := st.Context.Services.New(i.Unix, i.Npipe) + return service.Value(), nil } diff --git a/stdlib/dagger/dagger.cue b/stdlib/dagger/dagger.cue index c37f6621..8dd631cd 100644 --- a/stdlib/dagger/dagger.cue +++ b/stdlib/dagger/dagger.cue @@ -24,13 +24,18 @@ import ( ... } -// Dagger stream. Can be mounted as a UNIX socket. -#Stream: { - @dagger(stream) - - id: string +// A reference to a network service endpoint, for example: +// - A TCP or UDP port +// - A unix or npipe socket +// - An HTTPS endpoint +#Service: { + _service: id: string } +// Dagger stream. Can be mounted as a UNIX socket. +// FIXME: Deprecated. For backward compatibility only, use #Service instead. +#Stream: #Service + // A reference to an external secret, for example: // - A password // - A SSH private key @@ -38,9 +43,7 @@ import ( // Secrets are never merged in the Cue tree. They can only be used // by a special filesystem mount designed to minimize leak risk. #Secret: { - @dagger(secret) - - id: string + _secret: id: string } #Input: { diff --git a/tests/compute/secrets/simple/simple.cue b/tests/compute/secrets/simple/simple.cue index 0b8d3d7f..43374444 100644 --- a/tests/compute/secrets/simple/simple.cue +++ b/tests/compute/secrets/simple/simple.cue @@ -17,7 +17,6 @@ TestSecrets: #up: [ op.#Exec & { mount: "/secret": secret: mySecret - env: PLAIN: mySecret.id args: [ "/bin/bash", "--noprofile", @@ -27,7 +26,6 @@ TestSecrets: #up: [ "-c", #""" test "$(cat /secret)" = "SecretValue" - test "$PLAIN" != "SecretValue" """#, ] }, diff --git a/tests/core.bats b/tests/core.bats index 25fa80f2..04e5c88d 100644 --- a/tests/core.bats +++ b/tests/core.bats @@ -141,10 +141,6 @@ setup() { "$DAGGER" input secret mySecret SecretValue run "$DAGGER" up assert_success - - # Make sure the secret doesn't show in dagger query - run "$DAGGER" query mySecret.id -f text - assert_success } @test "core: stream" {