buildkit secrets support
- Secrets are never exposed in plaintext in the Cue tree. `dagger query` won't dump secrets anymore, Cue errors won't contain them either. - BuildKit-native secrets support through a new `mount` type. This ensures secrets will never be part of containerd layers, buildkit cache and generally speaking will never be saved to disk in plaintext. - Updated netlify as an example - Added tests - Changed the Cue definition of a secret to: ``` @dagger(secret) id: string } ``` This is to ensure both that setting the wrong input type on a secret (e.g. `dagger input text`) will fail, and attempting to misuse the secret (e.g. interpolating, passing as an env variable, etc) will also fail properly. Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
This commit is contained in:
parent
15f4c4877d
commit
9c0e2d1d95
@ -2,7 +2,6 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -87,13 +86,13 @@ func (c *Client) Do(ctx context.Context, state *state.State, fn DoFunc) (*enviro
|
|||||||
|
|
||||||
// Spawn build function
|
// Spawn build function
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
return c.buildfn(gctx, environment, fn, events)
|
return c.buildfn(gctx, state, environment, fn, events)
|
||||||
})
|
})
|
||||||
|
|
||||||
return environment, eg.Wait()
|
return environment, eg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) buildfn(ctx context.Context, env *environment.Environment, fn DoFunc, ch chan *bk.SolveStatus) error {
|
func (c *Client) buildfn(ctx context.Context, st *state.State, env *environment.Environment, fn DoFunc, ch chan *bk.SolveStatus) error {
|
||||||
lg := log.Ctx(ctx)
|
lg := log.Ctx(ctx)
|
||||||
|
|
||||||
// Scan local dirs to grant access
|
// Scan local dirs to grant access
|
||||||
@ -109,10 +108,13 @@ func (c *Client) buildfn(ctx context.Context, env *environment.Environment, fn D
|
|||||||
// buildkit auth provider (registry)
|
// buildkit auth provider (registry)
|
||||||
auth := solver.NewRegistryAuthProvider()
|
auth := solver.NewRegistryAuthProvider()
|
||||||
|
|
||||||
|
// secrets
|
||||||
|
secrets := solver.NewSecretsProvider(st)
|
||||||
|
|
||||||
// Setup solve options
|
// Setup solve options
|
||||||
opts := bk.SolveOpt{
|
opts := bk.SolveOpt{
|
||||||
LocalDirs: localdirs,
|
LocalDirs: localdirs,
|
||||||
Session: []session.Attachable{auth},
|
Session: []session.Attachable{auth, secrets},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call buildkit solver
|
// Call buildkit solver
|
||||||
@ -127,6 +129,7 @@ func (c *Client) buildfn(ctx context.Context, env *environment.Environment, fn D
|
|||||||
Gateway: gw,
|
Gateway: gw,
|
||||||
Events: ch,
|
Events: ch,
|
||||||
Auth: auth,
|
Auth: auth,
|
||||||
|
Secrets: secrets,
|
||||||
NoCache: c.noCache,
|
NoCache: c.noCache,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -165,7 +168,7 @@ func (c *Client) buildfn(ctx context.Context, env *environment.Environment, fn D
|
|||||||
return res, nil
|
return res, nil
|
||||||
}, ch)
|
}, ch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("buildkit solve: %w", bkCleanError(err))
|
return solver.CleanError(err)
|
||||||
}
|
}
|
||||||
for k, v := range resp.ExporterResponse {
|
for k, v := range resp.ExporterResponse {
|
||||||
// FIXME consume exporter response
|
// FIXME consume exporter response
|
||||||
@ -243,22 +246,3 @@ func (c *Client) logSolveStatus(ctx context.Context, ch chan *bk.SolveStatus) er
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A helper to remove noise from buildkit error messages.
|
|
||||||
// FIXME: Obviously a cleaner solution would be nice.
|
|
||||||
func bkCleanError(err error) error {
|
|
||||||
noise := []string{
|
|
||||||
"executor failed running ",
|
|
||||||
"buildkit-runc did not terminate successfully",
|
|
||||||
"rpc error: code = Unknown desc = ",
|
|
||||||
"failed to solve: ",
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := err.Error()
|
|
||||||
|
|
||||||
for _, s := range noise {
|
|
||||||
msg = strings.ReplaceAll(msg, s, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New(msg)
|
|
||||||
}
|
|
||||||
|
@ -44,7 +44,7 @@ func New(st *state.State) (*Environment, error) {
|
|||||||
|
|
||||||
// Prepare inputs
|
// Prepare inputs
|
||||||
for key, input := range st.Inputs {
|
for key, input := range st.Inputs {
|
||||||
v, err := input.Compile(st)
|
v, err := input.Compile(key, st)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -86,7 +86,7 @@ func (e *Environment) LoadPlan(ctx context.Context, s solver.Solver) error {
|
|||||||
span, ctx := opentracing.StartSpanFromContext(ctx, "environment.LoadPlan")
|
span, ctx := opentracing.StartSpanFromContext(ctx, "environment.LoadPlan")
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
|
|
||||||
planSource, err := e.state.PlanSource().Compile(e.state)
|
planSource, err := e.state.PlanSource().Compile("", e.state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -157,7 +157,7 @@ func (e *Environment) LocalDirs() map[string]string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Scan the plan
|
// 2. Scan the plan
|
||||||
plan, err := e.state.PlanSource().Compile(e.state)
|
plan, err := e.state.PlanSource().Compile("", e.state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -490,6 +490,25 @@ func (p *Pipeline) mount(ctx context.Context, dest string, mnt *compiler.Value)
|
|||||||
return nil, fmt.Errorf("invalid mount source: %q", s)
|
return nil, fmt.Errorf("invalid mount source: %q", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// eg. mount: "/foo": secret: mysecret
|
||||||
|
if secret := mnt.Lookup("secret"); secret.Exists() {
|
||||||
|
if !secret.HasAttr("secret") {
|
||||||
|
return nil, fmt.Errorf("invalid secret %q: not a secret", secret.Path().String())
|
||||||
|
}
|
||||||
|
idValue := secret.Lookup("id")
|
||||||
|
if !idValue.Exists() {
|
||||||
|
return nil, fmt.Errorf("invalid secret %q: no id field", secret.Path().String())
|
||||||
|
}
|
||||||
|
id, err := idValue.String()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid secret id: %w", err)
|
||||||
|
}
|
||||||
|
return llb.AddSecret(dest,
|
||||||
|
llb.SecretID(id),
|
||||||
|
llb.SecretFileOpt(0, 0, 0400), // uid, gid, mask)
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
// eg. mount: "/foo": { from: www.source }
|
// eg. mount: "/foo": { from: www.source }
|
||||||
from := NewPipeline(mnt.Lookup("from"), p.s)
|
from := NewPipeline(mnt.Lookup("from"), p.s)
|
||||||
if err := from.Run(ctx); err != nil {
|
if err := from.Run(ctx); err != nil {
|
||||||
|
47
solver/secretsprovider.go
Normal file
47
solver/secretsprovider.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package solver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/moby/buildkit/session"
|
||||||
|
"github.com/moby/buildkit/session/secrets"
|
||||||
|
"github.com/moby/buildkit/session/secrets/secretsprovider"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"go.dagger.io/dagger/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewSecretsProvider(st *state.State) session.Attachable {
|
||||||
|
return secretsprovider.NewSecretProvider(&inputStore{st})
|
||||||
|
}
|
||||||
|
|
||||||
|
type inputStore struct {
|
||||||
|
st *state.State
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *inputStore) GetSecret(ctx context.Context, id string) ([]byte, error) {
|
||||||
|
lg := log.Ctx(ctx)
|
||||||
|
|
||||||
|
const secretPrefix = "secret="
|
||||||
|
|
||||||
|
if !strings.HasPrefix(id, secretPrefix) {
|
||||||
|
return nil, secrets.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
id = strings.TrimPrefix(id, secretPrefix)
|
||||||
|
|
||||||
|
input, ok := s.st.Inputs[id]
|
||||||
|
if !ok {
|
||||||
|
return nil, secrets.ErrNotFound
|
||||||
|
}
|
||||||
|
if input.Secret == nil {
|
||||||
|
return nil, secrets.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
lg.
|
||||||
|
Debug().
|
||||||
|
Str("id", id).
|
||||||
|
Msg("injecting secret")
|
||||||
|
|
||||||
|
return []byte(input.Secret.PlainText()), nil
|
||||||
|
}
|
@ -3,7 +3,9 @@ package solver
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
bk "github.com/moby/buildkit/client"
|
bk "github.com/moby/buildkit/client"
|
||||||
"github.com/moby/buildkit/client/llb"
|
"github.com/moby/buildkit/client/llb"
|
||||||
@ -25,6 +27,7 @@ type Opts struct {
|
|||||||
Gateway bkgw.Client
|
Gateway bkgw.Client
|
||||||
Events chan *bk.SolveStatus
|
Events chan *bk.SolveStatus
|
||||||
Auth *RegistryAuthProvider
|
Auth *RegistryAuthProvider
|
||||||
|
Secrets session.Attachable
|
||||||
NoCache bool
|
NoCache bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +103,11 @@ func (s Solver) ResolveImageConfig(ctx context.Context, ref string, opts llb.Res
|
|||||||
|
|
||||||
// Solve will block until the state is solved and returns a Reference.
|
// Solve will block until the state is solved and returns a Reference.
|
||||||
func (s Solver) SolveRequest(ctx context.Context, req bkgw.SolveRequest) (*bkgw.Result, error) {
|
func (s Solver) SolveRequest(ctx context.Context, req bkgw.SolveRequest) (*bkgw.Result, error) {
|
||||||
return s.opts.Gateway.Solve(ctx, req)
|
res, err := s.opts.Gateway.Solve(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, CleanError(err)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solve will block until the state is solved and returns a Reference.
|
// Solve will block until the state is solved and returns a Reference.
|
||||||
@ -150,7 +157,7 @@ func (s Solver) Export(ctx context.Context, st llb.State, img *dockerfile2llb.Im
|
|||||||
|
|
||||||
opts := bk.SolveOpt{
|
opts := bk.SolveOpt{
|
||||||
Exports: []bk.ExportEntry{output},
|
Exports: []bk.ExportEntry{output},
|
||||||
Session: []session.Attachable{s.opts.Auth},
|
Session: []session.Attachable{s.opts.Auth, s.opts.Secrets},
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := make(chan *bk.SolveStatus)
|
ch := make(chan *bk.SolveStatus)
|
||||||
@ -204,3 +211,22 @@ func dumpLLB(def *bkpb.Definition) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
return json.Marshal(ops)
|
return json.Marshal(ops)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A helper to remove noise from buildkit error messages.
|
||||||
|
// FIXME: Obviously a cleaner solution would be nice.
|
||||||
|
func CleanError(err error) error {
|
||||||
|
noise := []string{
|
||||||
|
"executor failed running ",
|
||||||
|
"buildkit-runc did not terminate successfully",
|
||||||
|
"rpc error: code = Unknown desc = ",
|
||||||
|
"failed to solve: ",
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := err.Error()
|
||||||
|
|
||||||
|
for _, s := range noise {
|
||||||
|
msg = strings.ReplaceAll(msg, s, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New(msg)
|
||||||
|
}
|
||||||
|
@ -37,24 +37,24 @@ type Input struct {
|
|||||||
File *fileInput `yaml:"file,omitempty"`
|
File *fileInput `yaml:"file,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i Input) Compile(state *State) (*compiler.Value, error) {
|
func (i Input) Compile(key string, state *State) (*compiler.Value, error) {
|
||||||
switch {
|
switch {
|
||||||
case i.Dir != nil:
|
case i.Dir != nil:
|
||||||
return i.Dir.Compile(state)
|
return i.Dir.Compile(key, state)
|
||||||
case i.Git != nil:
|
case i.Git != nil:
|
||||||
return i.Git.Compile(state)
|
return i.Git.Compile(key, state)
|
||||||
case i.Docker != nil:
|
case i.Docker != nil:
|
||||||
return i.Docker.Compile(state)
|
return i.Docker.Compile(key, state)
|
||||||
case i.Text != nil:
|
case i.Text != nil:
|
||||||
return i.Text.Compile(state)
|
return i.Text.Compile(key, state)
|
||||||
case i.Secret != nil:
|
case i.Secret != nil:
|
||||||
return i.Secret.Compile(state)
|
return i.Secret.Compile(key, state)
|
||||||
case i.JSON != nil:
|
case i.JSON != nil:
|
||||||
return i.JSON.Compile(state)
|
return i.JSON.Compile(key, state)
|
||||||
case i.YAML != nil:
|
case i.YAML != nil:
|
||||||
return i.YAML.Compile(state)
|
return i.YAML.Compile(key, state)
|
||||||
case i.File != nil:
|
case i.File != nil:
|
||||||
return i.File.Compile(state)
|
return i.File.Compile(key, state)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("input has not been set")
|
return nil, fmt.Errorf("input has not been set")
|
||||||
}
|
}
|
||||||
@ -75,7 +75,7 @@ type dirInput struct {
|
|||||||
Include []string `json:"include,omitempty"`
|
Include []string `json:"include,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dir dirInput) Compile(state *State) (*compiler.Value, error) {
|
func (dir dirInput) Compile(_ string, state *State) (*compiler.Value, error) {
|
||||||
// FIXME: serialize an intermediate struct, instead of generating cue source
|
// FIXME: serialize an intermediate struct, instead of generating cue source
|
||||||
|
|
||||||
// json.Marshal([]string{}) returns []byte("null"), which wreaks havoc
|
// json.Marshal([]string{}) returns []byte("null"), which wreaks havoc
|
||||||
@ -122,7 +122,7 @@ func GitInput(remote, ref, dir string) Input {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (git gitInput) Compile(_ *State) (*compiler.Value, error) {
|
func (git gitInput) Compile(_ string, _ *State) (*compiler.Value, error) {
|
||||||
ref := "HEAD"
|
ref := "HEAD"
|
||||||
if git.Ref != "" {
|
if git.Ref != "" {
|
||||||
ref = git.Ref
|
ref = git.Ref
|
||||||
@ -148,7 +148,7 @@ type dockerInput struct {
|
|||||||
Ref string `json:"ref,omitempty"`
|
Ref string `json:"ref,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i dockerInput) Compile(_ *State) (*compiler.Value, error) {
|
func (i dockerInput) Compile(_ string, _ *State) (*compiler.Value, error) {
|
||||||
panic("NOT IMPLEMENTED")
|
panic("NOT IMPLEMENTED")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +162,7 @@ func TextInput(data string) Input {
|
|||||||
|
|
||||||
type textInput string
|
type textInput string
|
||||||
|
|
||||||
func (i textInput) Compile(_ *State) (*compiler.Value, error) {
|
func (i textInput) Compile(_ string, _ *State) (*compiler.Value, error) {
|
||||||
return compiler.Compile("", fmt.Sprintf("%q", i))
|
return compiler.Compile("", fmt.Sprintf("%q", i))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,8 +176,12 @@ func SecretInput(data string) Input {
|
|||||||
|
|
||||||
type secretInput string
|
type secretInput string
|
||||||
|
|
||||||
func (i secretInput) Compile(_ *State) (*compiler.Value, error) {
|
func (i secretInput) Compile(key string, _ *State) (*compiler.Value, error) {
|
||||||
return compiler.Compile("", fmt.Sprintf("%q", i))
|
return compiler.Compile("", fmt.Sprintf(`{id:%q}`, "secret="+key))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i secretInput) PlainText() string {
|
||||||
|
return string(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
// An input value encoded as JSON
|
// An input value encoded as JSON
|
||||||
@ -190,7 +194,7 @@ func JSONInput(data string) Input {
|
|||||||
|
|
||||||
type jsonInput string
|
type jsonInput string
|
||||||
|
|
||||||
func (i jsonInput) Compile(_ *State) (*compiler.Value, error) {
|
func (i jsonInput) Compile(_ string, _ *State) (*compiler.Value, error) {
|
||||||
return compiler.DecodeJSON("", []byte(i))
|
return compiler.DecodeJSON("", []byte(i))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,7 +208,7 @@ func YAMLInput(data string) Input {
|
|||||||
|
|
||||||
type yamlInput string
|
type yamlInput string
|
||||||
|
|
||||||
func (i yamlInput) Compile(_ *State) (*compiler.Value, error) {
|
func (i yamlInput) Compile(_ string, _ *State) (*compiler.Value, error) {
|
||||||
return compiler.DecodeYAML("", []byte(i))
|
return compiler.DecodeYAML("", []byte(i))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +224,7 @@ type fileInput struct {
|
|||||||
Path string `json:"data,omitempty"`
|
Path string `json:"data,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i fileInput) Compile(_ *State) (*compiler.Value, error) {
|
func (i fileInput) Compile(_ string, _ *State) (*compiler.Value, error) {
|
||||||
data, err := ioutil.ReadFile(i.Path)
|
data, err := ioutil.ReadFile(i.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -15,9 +15,8 @@ import (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Secret value
|
// Secret value
|
||||||
// FIXME: currently aliased as a string to mark secrets
|
|
||||||
// this requires proper support.
|
|
||||||
#Secret: {
|
#Secret: {
|
||||||
@dagger(secret)
|
@dagger(secret)
|
||||||
string | bytes
|
|
||||||
|
id: string
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ package op
|
|||||||
// `true` means also ignoring the mount cache volumes
|
// `true` means also ignoring the mount cache volumes
|
||||||
always?: true | *false
|
always?: true | *false
|
||||||
dir: string | *"/"
|
dir: string | *"/"
|
||||||
mount: [string]: "tmpfs" | "cache" | {from: _, path: string | *"/"}
|
mount: [string]: "tmpfs" | "cache" | {from: _, path: string | *"/"} | {secret: _}
|
||||||
// Map of hostnames to ip
|
// Map of hostnames to ip
|
||||||
hosts?: [string]: string
|
hosts?: [string]: string
|
||||||
// User to exec with (if left empty, will default to the set user in the image)
|
// User to exec with (if left empty, will default to the set user in the image)
|
||||||
|
@ -80,10 +80,10 @@ import (
|
|||||||
if customDomain != _|_ {
|
if customDomain != _|_ {
|
||||||
NETLIFY_DOMAIN: customDomain
|
NETLIFY_DOMAIN: customDomain
|
||||||
}
|
}
|
||||||
NETLIFY_ACCOUNT: account.name
|
NETLIFY_ACCOUNT: account.name
|
||||||
NETLIFY_AUTH_TOKEN: account.token
|
|
||||||
}
|
}
|
||||||
dir: "/src"
|
dir: "/src"
|
||||||
mount: "/src": from: contents
|
mount: "/src": from: contents
|
||||||
|
mount: "/token": secret: account.token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package netlify
|
package netlify
|
||||||
|
|
||||||
#Site: ctr: command: #"""
|
#Site: ctr: command: #"""
|
||||||
|
export NETLIFY_AUTH_TOKEN="$(cat /token)"
|
||||||
|
|
||||||
create_site() {
|
create_site() {
|
||||||
url="https://api.netlify.com/api/v1/${NETLIFY_ACCOUNT:-}/sites"
|
url="https://api.netlify.com/api/v1/${NETLIFY_ACCOUNT:-}/sites"
|
||||||
|
|
||||||
|
@ -52,6 +52,8 @@ import (
|
|||||||
mount: [string]: {
|
mount: [string]: {
|
||||||
from: dagger.#Artifact
|
from: dagger.#Artifact
|
||||||
// FIXME: support source path
|
// FIXME: support source path
|
||||||
|
} | {
|
||||||
|
secret: dagger.#Secret
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mount persistent cache directories
|
// Mount persistent cache directories
|
||||||
@ -94,10 +96,9 @@ import (
|
|||||||
// Execute setup commands, without volumes
|
// Execute setup commands, without volumes
|
||||||
for cmd in setup {
|
for cmd in setup {
|
||||||
op.#Exec & {
|
op.#Exec & {
|
||||||
args: [shell.path] + shell.args + [cmd]
|
args: [shell.path] + shell.args + [cmd]
|
||||||
"env": env
|
"env": env
|
||||||
"dir": dir
|
"dir": dir
|
||||||
"always": always
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Execute main command with volumes
|
// Execute main command with volumes
|
||||||
@ -109,7 +110,7 @@ import (
|
|||||||
"always": always
|
"always": always
|
||||||
"mount": {
|
"mount": {
|
||||||
for dest, o in mount {
|
for dest, o in mount {
|
||||||
"\(dest)": from: o.from
|
"\(dest)": o
|
||||||
// FIXME: support source path
|
// FIXME: support source path
|
||||||
}
|
}
|
||||||
for dest in cache {
|
for dest in cache {
|
||||||
|
@ -67,6 +67,33 @@ setup() {
|
|||||||
assert_line '{"in":"foobar","test":"received: foobar"}'
|
assert_line '{"in":"foobar","test":"received: foobar"}'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "compute: secrets" {
|
||||||
|
# secrets used as environment variables must fail
|
||||||
|
run "$DAGGER" compute "$TESTDIR"/compute/secrets/invalid/env
|
||||||
|
assert_failure
|
||||||
|
assert_line --partial "conflicting values"
|
||||||
|
|
||||||
|
# strings passed as secrets must fail
|
||||||
|
run "$DAGGER" compute "$TESTDIR"/compute/secrets/invalid/string
|
||||||
|
assert_failure
|
||||||
|
|
||||||
|
# Setting a text input for a secret value should fail
|
||||||
|
run "$DAGGER" compute --input-string 'mySecret=SecretValue' "$TESTDIR"/compute/secrets/simple
|
||||||
|
assert_failure
|
||||||
|
|
||||||
|
# Now test with an actual secret and make sure it works
|
||||||
|
"$DAGGER" init
|
||||||
|
dagger_new_with_plan secrets "$TESTDIR"/compute/secrets/simple
|
||||||
|
"$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
|
||||||
|
assert_output "secret=mySecret"
|
||||||
|
}
|
||||||
|
|
||||||
@test ".daggerignore" {
|
@test ".daggerignore" {
|
||||||
"$DAGGER" compute --input-dir TestData="$TESTDIR"/compute/ignore/testdata "$TESTDIR"/compute/ignore
|
"$DAGGER" compute --input-dir TestData="$TESTDIR"/compute/ignore/testdata "$TESTDIR"/compute/ignore
|
||||||
}
|
}
|
||||||
|
21
tests/compute/secrets/invalid/env/env.cue
vendored
Normal file
21
tests/compute/secrets/invalid/env/env.cue
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"dagger.io/dagger"
|
||||||
|
"dagger.io/dagger/op"
|
||||||
|
"dagger.io/alpine"
|
||||||
|
)
|
||||||
|
|
||||||
|
mySecret: dagger.#Secret
|
||||||
|
|
||||||
|
TestSecrets: #up: [
|
||||||
|
op.#Load & {
|
||||||
|
from: alpine.#Image & {
|
||||||
|
package: bash: "=~5.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
op.#Exec & {
|
||||||
|
env: foo: mySecret
|
||||||
|
},
|
||||||
|
]
|
21
tests/compute/secrets/invalid/string/string.cue
Normal file
21
tests/compute/secrets/invalid/string/string.cue
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"dagger.io/dagger/op"
|
||||||
|
"dagger.io/alpine"
|
||||||
|
)
|
||||||
|
|
||||||
|
mySecret: dagger.#Secret
|
||||||
|
|
||||||
|
TestString: #up: [
|
||||||
|
op.#Load & {
|
||||||
|
from: alpine.#Image & {
|
||||||
|
package: bash: "=~5.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
op.#Exec & {
|
||||||
|
mount: "/secret": secret: mySecret
|
||||||
|
args: ["true"]
|
||||||
|
},
|
||||||
|
]
|
34
tests/compute/secrets/simple/simple.cue
Normal file
34
tests/compute/secrets/simple/simple.cue
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"dagger.io/dagger"
|
||||||
|
"dagger.io/dagger/op"
|
||||||
|
"dagger.io/alpine"
|
||||||
|
)
|
||||||
|
|
||||||
|
mySecret: dagger.#Secret
|
||||||
|
|
||||||
|
TestSecrets: #up: [
|
||||||
|
op.#Load & {
|
||||||
|
from: alpine.#Image & {
|
||||||
|
package: bash: "=~5.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
op.#Exec & {
|
||||||
|
mount: "/secret": secret: mySecret
|
||||||
|
env: PLAIN: mySecret.id
|
||||||
|
args: [
|
||||||
|
"/bin/bash",
|
||||||
|
"--noprofile",
|
||||||
|
"--norc",
|
||||||
|
"-eo",
|
||||||
|
"pipefail",
|
||||||
|
"-c",
|
||||||
|
#"""
|
||||||
|
test "$(cat /secret)" = "SecretValue"
|
||||||
|
test "$PLAIN" != "SecretValue"
|
||||||
|
"""#,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
Reference in New Issue
Block a user