Merge pull request #459 from aluzzardi/gitflow-ux

gitflow
This commit is contained in:
Andrea Luzzardi 2021-05-25 15:38:18 -07:00 committed by GitHub
commit 93d7bb08e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 1092 additions and 1258 deletions

View File

@ -1,44 +0,0 @@
package main
import (
"dagger.io/dagger/op"
)
// Reproduce inline issue.
// See https://github.com/dagger/dagger/issues/395
test: adhoc: repro395: {
good: {
// This field is correctly computed because its intermediary pipeline is not inlined.
hello: sayHello.message
// Intermediary pipeline cannot be inlined: it must be visible in a field
sayHello: {
message: {
string
#up: [
op.#FetchContainer & { ref: "alpine" },
op.#Exec & {
args: ["sh", "-c", "echo hello > /message"]
},
op.#Export & { source: "/message", format: "string" },
]
}
}
}
bad: {
// This field is NOT correctly computed because its intermediary pipeline is inlined.
hello: {
message: {
string
#up: [
op.#FetchContainer & { ref: "alpine" },
op.#Exec & {
args: ["sh", "-c", "echo hello > /message"]
},
op.#Export & { source: "/message", format: "string" },
]
}
}.message
}
}

View File

@ -1,69 +0,0 @@
// A dagger workflow to develop dagger
package main
import (
"dagger.io/dagger"
"dagger.io/os"
"dagger.io/alpine"
"dagger.io/docker"
"dagger.io/go"
)
// Dagger source code
source: dagger.#Artifact
test: {
// Go unit tests
unit: {
logs: (os.#File & {
from: build.ctr
path: "/test.log"
read: format: "string"
}).read.data
}
// Full suite of bats integration tests
integration: {
// FIXME
}
}
// Build the dagger binaries
build: {
ctr: go.#Container & {
"source": source
setup: [
"apk add --no-cache file",
]
command: """
go test -v ./... > /test.log
go build -o /binaries/ ./cmd/... > /build.log
"""
}
binaries: docker.#Container & {
image: ctr
outputDir: "/binaries"
}
logs: (os.#File & {
from: ctr
path: "/build.log"
read: format: "string"
}).read.data
}
// Execute `dagger help`
usage: docker.#Container & {
image: alpine.#Image
command: "dagger help"
volume: binaries: {
from: build.binaries
dest: "/usr/local/dagger/bin/"
}
shell: search: "/usr/local/dagger/bin": true
}

View File

@ -1 +0,0 @@
../../examples/hello-world

View File

@ -1,21 +0,0 @@
package main
import (
"dagger.io/docker"
"dagger.io/os"
)
let ctr = docker.#Container & {
command: "echo 'hello world!' > /etc/motd"
}
motd: (os.#File & {
from: ctr
path: "/etc/motd"
read: format: "string"
}).read.data
etc: (os.#Dir & {
from: ctr
path: "/etc"
}).read.tree

View File

@ -2,65 +2,84 @@ package common
import ( import (
"context" "context"
"os"
"dagger.io/go/dagger" "dagger.io/go/dagger"
"dagger.io/go/dagger/state"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
func GetCurrentEnvironmentState(ctx context.Context, store *dagger.Store) *dagger.EnvironmentState { func CurrentWorkspace(ctx context.Context) *state.Workspace {
lg := log.Ctx(ctx) lg := log.Ctx(ctx)
environmentName := viper.GetString("environment") if workspacePath := viper.GetString("workspace"); workspacePath != "" {
if environmentName != "" { workspace, err := state.Open(ctx, workspacePath)
st, err := store.LookupEnvironmentByName(ctx, environmentName)
if err != nil { if err != nil {
lg. lg.
Fatal(). Fatal().
Err(err). Err(err).
Str("environmentName", environmentName). Str("path", workspacePath).
Msg("failed to lookup environment by name") Msg("failed to open workspace")
}
return workspace
}
workspace, err := state.Current(ctx)
if err != nil {
lg.
Fatal().
Err(err).
Msg("failed to determine current workspace")
}
return workspace
}
func CurrentEnvironmentState(ctx context.Context, workspace *state.Workspace) *state.State {
lg := log.Ctx(ctx)
environmentName := viper.GetString("environment")
if environmentName != "" {
st, err := workspace.Get(ctx, environmentName)
if err != nil {
lg.
Fatal().
Err(err).
Msg("failed to load environment")
} }
return st return st
} }
wd, err := os.Getwd() environments, err := workspace.List(ctx)
if err != nil {
lg.Fatal().Err(err).Msg("cannot get current working directory")
}
st, err := store.LookupEnvironmentByPath(ctx, wd)
if err != nil { if err != nil {
lg. lg.
Fatal(). Fatal().
Err(err). Err(err).
Str("environmentPath", wd). Msg("failed to list environments")
Msg("failed to lookup environment by path")
} }
if len(st) == 0 {
if len(environments) == 0 {
lg. lg.
Fatal(). Fatal().
Err(err). Msg("no environments")
Str("environmentPath", wd).
Msg("no environments match the current directory")
} }
if len(st) > 1 {
environments := []string{} if len(environments) > 1 {
for _, s := range st { envNames := []string{}
environments = append(environments, s.Name) for _, e := range environments {
envNames = append(envNames, e.Name)
} }
lg. lg.
Fatal(). Fatal().
Err(err). Err(err).
Str("environmentPath", wd). Strs("environments", envNames).
Strs("environments", environments). Msg("multiple environments available in the workspace, select one with `--environment`")
Msg("multiple environments match the current directory, select one with `--environment`")
} }
return st[0]
return environments[0]
} }
// Re-compute an environment (equivalent to `dagger up`). // Re-compute an environment (equivalent to `dagger up`).
func EnvironmentUp(ctx context.Context, state *dagger.EnvironmentState, noCache bool) *dagger.Environment { func EnvironmentUp(ctx context.Context, state *state.State, noCache bool) *dagger.Environment {
lg := log.Ctx(ctx) lg := log.Ctx(ctx)
c, err := dagger.NewClient(ctx, "", noCache) c, err := dagger.NewClient(ctx, "", noCache)

View File

@ -10,12 +10,11 @@ import (
"cuelang.org/go/cue" "cuelang.org/go/cue"
"dagger.io/go/cmd/dagger/cmd/common" "dagger.io/go/cmd/dagger/cmd/common"
"dagger.io/go/cmd/dagger/logger" "dagger.io/go/cmd/dagger/logger"
"dagger.io/go/dagger"
"dagger.io/go/dagger/compiler" "dagger.io/go/dagger/compiler"
"dagger.io/go/dagger/state"
"go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3"
"go.mozilla.org/sops/v3/decrypt" "go.mozilla.org/sops/v3/decrypt"
"github.com/google/uuid"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -36,16 +35,16 @@ var computeCmd = &cobra.Command{
lg := logger.New() lg := logger.New()
ctx := lg.WithContext(cmd.Context()) ctx := lg.WithContext(cmd.Context())
st := &dagger.EnvironmentState{ st := &state.State{
ID: uuid.New().String(), Name: "FIXME",
Name: "FIXME", Path: args[0],
PlanSource: dagger.DirInput(args[0], []string{"*.cue", "cue.mod"}), Plan: args[0],
} }
for _, input := range viper.GetStringSlice("input-string") { for _, input := range viper.GetStringSlice("input-string") {
parts := strings.SplitN(input, "=", 2) parts := strings.SplitN(input, "=", 2)
k, v := parts[0], parts[1] k, v := parts[0], parts[1]
err := st.SetInput(k, dagger.TextInput(v)) err := st.SetInput(k, state.TextInput(v))
if err != nil { if err != nil {
lg. lg.
Fatal(). Fatal().
@ -58,7 +57,7 @@ var computeCmd = &cobra.Command{
for _, input := range viper.GetStringSlice("input-dir") { for _, input := range viper.GetStringSlice("input-dir") {
parts := strings.SplitN(input, "=", 2) parts := strings.SplitN(input, "=", 2)
k, v := parts[0], parts[1] k, v := parts[0], parts[1]
err := st.SetInput(k, dagger.DirInput(v, []string{})) err := st.SetInput(k, state.DirInput(v, []string{}))
if err != nil { if err != nil {
lg. lg.
Fatal(). Fatal().
@ -71,7 +70,7 @@ var computeCmd = &cobra.Command{
for _, input := range viper.GetStringSlice("input-git") { for _, input := range viper.GetStringSlice("input-git") {
parts := strings.SplitN(input, "=", 2) parts := strings.SplitN(input, "=", 2)
k, v := parts[0], parts[1] k, v := parts[0], parts[1]
err := st.SetInput(k, dagger.GitInput(v, "", "")) err := st.SetInput(k, state.GitInput(v, "", ""))
if err != nil { if err != nil {
lg. lg.
Fatal(). Fatal().
@ -102,7 +101,7 @@ var computeCmd = &cobra.Command{
lg.Fatal().Msg("invalid json") lg.Fatal().Msg("invalid json")
} }
err = st.SetInput("", dagger.JSONInput(string(content))) err = st.SetInput("", state.JSONInput(string(content)))
if err != nil { if err != nil {
lg.Fatal().Err(err).Msg("failed to add input") lg.Fatal().Err(err).Msg("failed to add input")
} }
@ -125,7 +124,7 @@ var computeCmd = &cobra.Command{
content = plaintext content = plaintext
} }
err = st.SetInput("", dagger.YAMLInput(string(content))) err = st.SetInput("", state.YAMLInput(string(content)))
if err != nil { if err != nil {
lg.Fatal().Err(err).Msg("failed to add input") lg.Fatal().Err(err).Msg("failed to add input")
} }
@ -143,7 +142,7 @@ var computeCmd = &cobra.Command{
} }
if len(content) > 0 { if len(content) > 0 {
err = st.SetInput(k, dagger.FileInput(v)) err = st.SetInput(k, state.FileInput(v))
if err != nil { if err != nil {
lg.Fatal().Err(err).Msg("failed to set input string") lg.Fatal().Err(err).Msg("failed to set input string")
} }

View File

@ -1,31 +0,0 @@
package cmd
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var deleteCmd = &cobra.Command{
Use: "delete",
Short: "Delete an environment after taking it offline (WARNING: may destroy infrastructure)",
Args: cobra.NoArgs,
PreRun: func(cmd *cobra.Command, args []string) {
// Fix Viper bug for duplicate flags:
// https://github.com/spf13/viper/issues/233
if err := viper.BindPFlags(cmd.Flags()); err != nil {
panic(err)
}
},
Run: func(cmd *cobra.Command, args []string) {
// lg := logger.New()
// ctx := lg.WithContext(cmd.Context())
panic("not implemented")
},
}
func init() {
if err := viper.BindPFlags(deleteCmd.Flags()); err != nil {
panic(err)
}
}

49
cmd/dagger/cmd/init.go Normal file
View File

@ -0,0 +1,49 @@
package cmd
import (
"os"
"dagger.io/go/cmd/dagger/logger"
"dagger.io/go/dagger/state"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var initCmd = &cobra.Command{
Use: "init",
Args: cobra.MaximumNArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
// Fix Viper bug for duplicate flags:
// https://github.com/spf13/viper/issues/233
if err := viper.BindPFlags(cmd.Flags()); err != nil {
panic(err)
}
},
Run: func(cmd *cobra.Command, args []string) {
lg := logger.New()
ctx := lg.WithContext(cmd.Context())
dir := viper.GetString("workspace")
if dir == "" {
cwd, err := os.Getwd()
if err != nil {
lg.
Fatal().
Err(err).
Msg("failed to get current working dir")
}
dir = cwd
}
_, err := state.Init(ctx, dir)
if err != nil {
lg.Fatal().Err(err).Msg("failed to initialize workspace")
}
},
}
func init() {
if err := viper.BindPFlags(initCmd.Flags()); err != nil {
panic(err)
}
}

View File

@ -2,7 +2,7 @@ package input
import ( import (
"dagger.io/go/cmd/dagger/logger" "dagger.io/go/cmd/dagger/logger"
"dagger.io/go/dagger" "dagger.io/go/dagger/state"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -22,7 +22,7 @@ var containerCmd = &cobra.Command{
lg := logger.New() lg := logger.New()
ctx := lg.WithContext(cmd.Context()) ctx := lg.WithContext(cmd.Context())
updateEnvironmentInput(ctx, args[0], dagger.DockerInput(args[1])) updateEnvironmentInput(ctx, args[0], state.DockerInput(args[1]))
}, },
} }

View File

@ -1,8 +1,12 @@
package input package input
import ( import (
"path/filepath"
"strings"
"dagger.io/go/cmd/dagger/cmd/common"
"dagger.io/go/cmd/dagger/logger" "dagger.io/go/cmd/dagger/logger"
"dagger.io/go/dagger" "dagger.io/go/dagger/state"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -22,7 +26,24 @@ var dirCmd = &cobra.Command{
lg := logger.New() lg := logger.New()
ctx := lg.WithContext(cmd.Context()) ctx := lg.WithContext(cmd.Context())
updateEnvironmentInput(ctx, args[0], dagger.DirInput(args[1], []string{})) p, err := filepath.Abs(args[1])
if err != nil {
lg.Fatal().Err(err).Str("path", args[1]).Msg("unable to resolve path")
}
workspace := common.CurrentWorkspace(ctx)
if !strings.HasPrefix(p, workspace.Path) {
lg.Fatal().Err(err).Str("path", args[1]).Msg("dir is outside the workspace")
}
p, err = filepath.Rel(workspace.Path, p)
if err != nil {
lg.Fatal().Err(err).Str("path", args[1]).Msg("unable to resolve path")
}
if !strings.HasPrefix(p, ".") {
p = "./" + p
}
updateEnvironmentInput(ctx, args[0], state.DirInput(p, []string{}))
}, },
} }

View File

@ -2,7 +2,7 @@ package input
import ( import (
"dagger.io/go/cmd/dagger/logger" "dagger.io/go/cmd/dagger/logger"
"dagger.io/go/dagger" "dagger.io/go/dagger/state"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -32,7 +32,7 @@ var gitCmd = &cobra.Command{
subDir = args[3] subDir = args[3]
} }
updateEnvironmentInput(ctx, args[0], dagger.GitInput(args[1], ref, subDir)) updateEnvironmentInput(ctx, args[0], state.GitInput(args[1], ref, subDir))
}, },
} }

View File

@ -2,7 +2,7 @@ package input
import ( import (
"dagger.io/go/cmd/dagger/logger" "dagger.io/go/cmd/dagger/logger"
"dagger.io/go/dagger" "dagger.io/go/dagger/state"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -25,7 +25,7 @@ var jsonCmd = &cobra.Command{
updateEnvironmentInput( updateEnvironmentInput(
ctx, ctx,
args[0], args[0],
dagger.JSONInput(readInput(ctx, args[1])), state.JSONInput(readInput(ctx, args[1])),
) )
}, },
} }

View File

@ -10,6 +10,7 @@ import (
"dagger.io/go/cmd/dagger/logger" "dagger.io/go/cmd/dagger/logger"
"dagger.io/go/dagger" "dagger.io/go/dagger"
"dagger.io/go/dagger/compiler" "dagger.io/go/dagger/compiler"
"dagger.io/go/dagger/state"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -30,16 +31,11 @@ var listCmd = &cobra.Command{
lg := logger.New() lg := logger.New()
ctx := lg.WithContext(cmd.Context()) ctx := lg.WithContext(cmd.Context())
store, err := dagger.DefaultStore() workspace := common.CurrentWorkspace(ctx)
if err != nil { environment := common.CurrentEnvironmentState(ctx, workspace)
lg.Fatal().Err(err).Msg("failed to load store")
}
environment := common.GetCurrentEnvironmentState(ctx, store)
lg = lg.With(). lg = lg.With().
Str("environmentName", environment.Name). Str("environment", environment.Name).
Str("environmentId", environment.ID).
Logger() Logger()
c, err := dagger.NewClient(ctx, "", false) c, err := dagger.NewClient(ctx, "", false)
@ -90,9 +86,9 @@ var listCmd = &cobra.Command{
}, },
} }
func isUserSet(env *dagger.EnvironmentState, val *compiler.Value) bool { func isUserSet(env *state.State, val *compiler.Value) bool {
for _, i := range env.Inputs { for key := range env.Inputs {
if val.Path().String() == i.Key { if val.Path().String() == key {
return true return true
} }
} }

View File

@ -6,7 +6,7 @@ import (
"os" "os"
"dagger.io/go/cmd/dagger/cmd/common" "dagger.io/go/cmd/dagger/cmd/common"
"dagger.io/go/dagger" "dagger.io/go/dagger/state"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -32,21 +32,16 @@ func init() {
) )
} }
func updateEnvironmentInput(ctx context.Context, target string, input dagger.Input) { func updateEnvironmentInput(ctx context.Context, target string, input state.Input) {
lg := log.Ctx(ctx) lg := log.Ctx(ctx)
store, err := dagger.DefaultStore() workspace := common.CurrentWorkspace(ctx)
if err != nil { st := common.CurrentEnvironmentState(ctx, workspace)
lg.Fatal().Err(err).Msg("failed to load store")
}
st := common.GetCurrentEnvironmentState(ctx, store)
st.SetInput(target, input) st.SetInput(target, input)
if err := store.UpdateEnvironment(ctx, st, nil); err != nil { if err := workspace.Save(ctx, st); err != nil {
lg.Fatal().Err(err).Str("environmentId", st.ID).Str("environmentName", st.Name).Msg("cannot update environment") lg.Fatal().Err(err).Str("environment", st.Name).Msg("cannot update environment")
} }
lg.Info().Str("environmentId", st.ID).Str("environmentName", st.Name).Msg("updated environment")
} }
func readInput(ctx context.Context, source string) string { func readInput(ctx context.Context, source string) string {

View File

@ -1,14 +1,20 @@
package input package input
import ( import (
"fmt"
"syscall"
"dagger.io/go/cmd/dagger/logger"
"dagger.io/go/dagger/state"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"golang.org/x/term"
) )
var secretCmd = &cobra.Command{ var secretCmd = &cobra.Command{
Use: "secret TARGET VALUE", Use: "secret <TARGET> [-f] [<VALUE|PATH>]",
Short: "Add an encrypted input secret", Short: "Add an encrypted input secret",
Args: cobra.ExactArgs(2), Args: cobra.RangeArgs(1, 2),
PreRun: func(cmd *cobra.Command, args []string) { PreRun: func(cmd *cobra.Command, args []string) {
// Fix Viper bug for duplicate flags: // Fix Viper bug for duplicate flags:
// https://github.com/spf13/viper/issues/233 // https://github.com/spf13/viper/issues/233
@ -17,14 +23,35 @@ var secretCmd = &cobra.Command{
} }
}, },
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// lg := logger.New() lg := logger.New()
// ctx := lg.WithContext(cmd.Context()) ctx := lg.WithContext(cmd.Context())
panic("not implemented") var secret string
if len(args) == 1 {
// No value specified: prompt terminal
fmt.Print("Secret: ")
data, err := term.ReadPassword(syscall.Stdin)
if err != nil {
lg.Fatal().Err(err).Msg("unable to read secret from terminal")
}
fmt.Println("")
secret = string(data)
} else {
// value specified: read it
secret = readInput(ctx, args[1])
}
updateEnvironmentInput(
ctx,
args[0],
state.SecretInput(secret),
)
}, },
} }
func init() { func init() {
secretCmd.Flags().BoolP("file", "f", false, "Read value from file")
if err := viper.BindPFlags(secretCmd.Flags()); err != nil { if err := viper.BindPFlags(secretCmd.Flags()); err != nil {
panic(err) panic(err)
} }

View File

@ -2,7 +2,7 @@ package input
import ( import (
"dagger.io/go/cmd/dagger/logger" "dagger.io/go/cmd/dagger/logger"
"dagger.io/go/dagger" "dagger.io/go/dagger/state"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -25,7 +25,7 @@ var textCmd = &cobra.Command{
updateEnvironmentInput( updateEnvironmentInput(
ctx, ctx,
args[0], args[0],
dagger.TextInput(readInput(ctx, args[1])), state.TextInput(readInput(ctx, args[1])),
) )
}, },
} }

View File

@ -3,7 +3,6 @@ package input
import ( import (
"dagger.io/go/cmd/dagger/cmd/common" "dagger.io/go/cmd/dagger/cmd/common"
"dagger.io/go/cmd/dagger/logger" "dagger.io/go/cmd/dagger/logger"
"dagger.io/go/dagger"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -23,17 +22,13 @@ var unsetCmd = &cobra.Command{
lg := logger.New() lg := logger.New()
ctx := lg.WithContext(cmd.Context()) ctx := lg.WithContext(cmd.Context())
store, err := dagger.DefaultStore() workspace := common.CurrentWorkspace(ctx)
if err != nil { st := common.CurrentEnvironmentState(ctx, workspace)
lg.Fatal().Err(err).Msg("failed to load store")
}
st := common.GetCurrentEnvironmentState(ctx, store)
st.RemoveInputs(args[0]) st.RemoveInputs(args[0])
if err := store.UpdateEnvironment(ctx, st, nil); err != nil { if err := workspace.Save(ctx, st); err != nil {
lg.Fatal().Err(err).Str("environmentId", st.ID).Str("environmentName", st.Name).Msg("cannot update environment") lg.Fatal().Err(err).Str("environment", st.Name).Msg("cannot update environment")
} }
lg.Info().Str("environmentId", st.ID).Str("environmentName", st.Name).Msg("updated environment") lg.Info().Str("environment", st.Name).Msg("updated environment")
}, },
} }

View File

@ -2,7 +2,7 @@ package input
import ( import (
"dagger.io/go/cmd/dagger/logger" "dagger.io/go/cmd/dagger/logger"
"dagger.io/go/dagger" "dagger.io/go/dagger/state"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -25,7 +25,7 @@ var yamlCmd = &cobra.Command{
updateEnvironmentInput( updateEnvironmentInput(
ctx, ctx,
args[0], args[0],
dagger.YAMLInput(readInput(ctx, args[1])), state.YAMLInput(readInput(ctx, args[1])),
) )
}, },
} }

View File

@ -1,7 +1,6 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"os/user" "os/user"
@ -9,9 +8,8 @@ import (
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"dagger.io/go/cmd/dagger/cmd/common"
"dagger.io/go/cmd/dagger/logger" "dagger.io/go/cmd/dagger/logger"
"dagger.io/go/dagger"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -30,12 +28,9 @@ var listCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
lg := logger.New() lg := logger.New()
ctx := lg.WithContext(cmd.Context()) ctx := lg.WithContext(cmd.Context())
store, err := dagger.DefaultStore()
if err != nil {
lg.Fatal().Err(err).Msg("failed to load store")
}
environments, err := store.ListEnvironments(ctx) workspace := common.CurrentWorkspace(ctx)
environments, err := workspace.List(ctx)
if err != nil { if err != nil {
lg. lg.
Fatal(). Fatal().
@ -43,47 +38,15 @@ var listCmd = &cobra.Command{
Msg("cannot list environments") Msg("cannot list environments")
} }
environmentID := getCurrentEnvironmentID(ctx, store)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.TabIndent) w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.TabIndent)
for _, r := range environments { defer w.Flush()
line := fmt.Sprintf("%s\t%s\t", r.Name, formatPlanSource(r.PlanSource)) for _, e := range environments {
if r.ID == environmentID { line := fmt.Sprintf("%s\t%s\t", e.Name, formatPath(e.Path))
line = fmt.Sprintf("%s- active environment", line)
}
fmt.Fprintln(w, line) fmt.Fprintln(w, line)
} }
w.Flush()
}, },
} }
func init() {
if err := viper.BindPFlags(listCmd.Flags()); err != nil {
panic(err)
}
}
func getCurrentEnvironmentID(ctx context.Context, store *dagger.Store) string {
lg := log.Ctx(ctx)
wd, err := os.Getwd()
if err != nil {
lg.Warn().Err(err).Msg("cannot get current working directory")
return ""
}
st, err := store.LookupEnvironmentByPath(ctx, wd)
if err != nil {
// Ignore error
return ""
}
if len(st) == 1 {
return st[0].ID
}
return ""
}
func formatPath(p string) string { func formatPath(p string) string {
usr, err := user.Current() usr, err := user.Current()
if err != nil { if err != nil {
@ -99,15 +62,8 @@ func formatPath(p string) string {
return p return p
} }
func formatPlanSource(i dagger.Input) string { func init() {
switch i.Type { if err := viper.BindPFlags(listCmd.Flags()); err != nil {
case dagger.InputTypeDir: panic(err)
return formatPath(i.Dir.Path)
case dagger.InputTypeGit:
return i.Git.Remote
case dagger.InputTypeDocker:
return i.Docker.Ref
} }
return "no plan"
} }

View File

@ -1,24 +1,15 @@
package cmd package cmd
import ( import (
"context"
"net/url"
"os"
"path/filepath"
"dagger.io/go/cmd/dagger/cmd/common" "dagger.io/go/cmd/dagger/cmd/common"
"dagger.io/go/cmd/dagger/logger" "dagger.io/go/cmd/dagger/logger"
"dagger.io/go/dagger"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
var newCmd = &cobra.Command{ var newCmd = &cobra.Command{
Use: "new", Use: "new",
Short: "Create a new environment", Args: cobra.ExactArgs(1),
Args: cobra.MaximumNArgs(1),
PreRun: func(cmd *cobra.Command, args []string) { PreRun: func(cmd *cobra.Command, args []string) {
// Fix Viper bug for duplicate flags: // Fix Viper bug for duplicate flags:
// https://github.com/spf13/viper/issues/233 // https://github.com/spf13/viper/issues/233
@ -29,119 +20,22 @@ var newCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
lg := logger.New() lg := logger.New()
ctx := lg.WithContext(cmd.Context()) ctx := lg.WithContext(cmd.Context())
store, err := dagger.DefaultStore()
if err != nil { workspace := common.CurrentWorkspace(ctx)
lg.Fatal().Err(err).Msg("failed to load store")
}
if viper.GetString("environment") != "" { if viper.GetString("environment") != "" {
lg. lg.
Fatal(). Fatal().
Msg("cannot use option -d,--environment for this command") Msg("cannot use option -e,--environment for this command")
} }
name := args[0]
name := "" if _, err := workspace.Create(ctx, name); err != nil {
if len(args) > 0 {
name = args[0]
} else {
name = getNewEnvironmentName(ctx)
}
st := &dagger.EnvironmentState{
Name: name,
PlanSource: getPlanSource(ctx),
}
err = store.CreateEnvironment(ctx, st)
if err != nil {
lg.Fatal().Err(err).Msg("failed to create environment") lg.Fatal().Err(err).Msg("failed to create environment")
} }
lg.
Info().
Str("environmentId", st.ID).
Str("environmentName", st.Name).
Msg("environment created")
if viper.GetBool("up") {
common.EnvironmentUp(ctx, st, false)
}
}, },
} }
func getNewEnvironmentName(ctx context.Context) string {
lg := log.Ctx(ctx)
workDir, err := os.Getwd()
if err != nil {
lg.
Fatal().
Err(err).
Msg("failed to get current working dir")
}
currentDir := filepath.Base(workDir)
if currentDir == "/" {
return "root"
}
return currentDir
}
func getPlanSource(ctx context.Context) dagger.Input {
lg := log.Ctx(ctx)
src := dagger.Input{}
checkFirstSet := func() {
if src.Type != dagger.InputTypeEmpty {
lg.Fatal().Msg("only one of those options can be set: --plan-dir, --plan-git, --plan-package, --plan-file")
}
}
planDir := viper.GetString("plan-dir")
planGit := viper.GetString("plan-git")
if planDir != "" {
checkFirstSet()
src = dagger.DirInput(planDir, []string{"*.cue", "cue.mod"})
}
if planGit != "" {
checkFirstSet()
u, err := url.Parse(planGit)
if err != nil {
lg.Fatal().Err(err).Str("url", planGit).Msg("cannot get current working directory")
}
ref := u.Fragment // eg. #main
u.Fragment = ""
remote := u.String()
src = dagger.GitInput(remote, ref, "")
}
if src.Type == dagger.InputTypeEmpty {
var err error
wd, err := os.Getwd()
if err != nil {
lg.Fatal().Err(err).Msg("cannot get current working directory")
}
return dagger.DirInput(wd, []string{"*.cue", "cue.mod"})
}
return src
}
func init() { func init() {
newCmd.Flags().BoolP("up", "u", false, "Bring the environment online")
newCmd.Flags().String("plan-dir", "", "Load plan from a local directory")
newCmd.Flags().String("plan-git", "", "Load plan from a git repository")
newCmd.Flags().String("plan-package", "", "Load plan from a cue package")
newCmd.Flags().String("plan-file", "", "Load plan from a cue or json file")
newCmd.Flags().String("setup", "auto", "Specify whether to prompt user for initial setup (no|yes|auto)")
if err := viper.BindPFlags(newCmd.Flags()); err != nil { if err := viper.BindPFlags(newCmd.Flags()); err != nil {
panic(err) panic(err)
} }

View File

@ -1,33 +0,0 @@
package plan
import (
"dagger.io/go/cmd/dagger/logger"
"dagger.io/go/dagger"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var dirCmd = &cobra.Command{
Use: "dir PATH",
Short: "Load plan from a local directory",
Args: cobra.ExactArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
// Fix Viper bug for duplicate flags:
// https://github.com/spf13/viper/issues/233
if err := viper.BindPFlags(cmd.Flags()); err != nil {
panic(err)
}
},
Run: func(cmd *cobra.Command, args []string) {
lg := logger.New()
ctx := lg.WithContext(cmd.Context())
updateEnvironmentPlan(ctx, dagger.DirInput(args[0], []string{"*.cue", "cue.mod"}))
},
}
func init() {
if err := viper.BindPFlags(dirCmd.Flags()); err != nil {
panic(err)
}
}

View File

@ -1,31 +0,0 @@
package plan
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var fileCmd = &cobra.Command{
Use: "file PATH|-",
Short: "Load plan from a cue file",
Args: cobra.ExactArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
// Fix Viper bug for duplicate flags:
// https://github.com/spf13/viper/issues/233
if err := viper.BindPFlags(cmd.Flags()); err != nil {
panic(err)
}
},
Run: func(cmd *cobra.Command, args []string) {
// lg := logger.New()
// ctx := lg.WithContext(cmd.Context())
panic("not implemented")
},
}
func init() {
if err := viper.BindPFlags(fileCmd.Flags()); err != nil {
panic(err)
}
}

View File

@ -1,43 +0,0 @@
package plan
import (
"dagger.io/go/cmd/dagger/logger"
"dagger.io/go/dagger"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var gitCmd = &cobra.Command{
Use: "git REMOTE [REF] [SUBDIR]",
Short: "Load plan from a git package",
Args: cobra.RangeArgs(1, 3),
PreRun: func(cmd *cobra.Command, args []string) {
// Fix Viper bug for duplicate flags:
// https://github.com/spf13/viper/issues/233
if err := viper.BindPFlags(cmd.Flags()); err != nil {
panic(err)
}
},
Run: func(cmd *cobra.Command, args []string) {
lg := logger.New()
ctx := lg.WithContext(cmd.Context())
ref := "HEAD"
if len(args) > 1 {
ref = args[1]
}
subDir := ""
if len(args) > 2 {
subDir = args[2]
}
updateEnvironmentPlan(ctx, dagger.GitInput(args[0], ref, subDir))
},
}
func init() {
if err := viper.BindPFlags(gitCmd.Flags()); err != nil {
panic(err)
}
}

View File

@ -1,31 +0,0 @@
package plan
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var packageCmd = &cobra.Command{
Use: "package PKG",
Short: "Load plan from a cue package",
Args: cobra.ExactArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
// Fix Viper bug for duplicate flags:
// https://github.com/spf13/viper/issues/233
if err := viper.BindPFlags(cmd.Flags()); err != nil {
panic(err)
}
},
Run: func(cmd *cobra.Command, args []string) {
// lg := logger.New()
// ctx := lg.WithContext(cmd.Context())
panic("not implemented")
},
}
func init() {
if err := viper.BindPFlags(packageCmd.Flags()); err != nil {
panic(err)
}
}

View File

@ -1,42 +0,0 @@
package plan
import (
"context"
"dagger.io/go/cmd/dagger/cmd/common"
"dagger.io/go/dagger"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
// Cmd exposes the top-level command
var Cmd = &cobra.Command{
Use: "plan",
Short: "Manage an environment's plan",
}
func init() {
Cmd.AddCommand(
packageCmd,
dirCmd,
gitCmd,
fileCmd,
)
}
func updateEnvironmentPlan(ctx context.Context, planSource dagger.Input) {
lg := log.Ctx(ctx)
store, err := dagger.DefaultStore()
if err != nil {
lg.Fatal().Err(err).Msg("failed to load store")
}
st := common.GetCurrentEnvironmentState(ctx, store)
st.PlanSource = planSource
if err := store.UpdateEnvironment(ctx, st, nil); err != nil {
lg.Fatal().Err(err).Str("environmentId", st.ID).Str("environmentName", st.Name).Msg("cannot update environment")
}
lg.Info().Str("environmentId", st.ID).Str("environmentName", st.Name).Msg("updated environment")
}

View File

@ -30,16 +30,11 @@ var queryCmd = &cobra.Command{
cueOpts := parseQueryFlags() cueOpts := parseQueryFlags()
store, err := dagger.DefaultStore() workspace := common.CurrentWorkspace(ctx)
if err != nil { state := common.CurrentEnvironmentState(ctx, workspace)
lg.Fatal().Err(err).Msg("failed to load store")
}
state := common.GetCurrentEnvironmentState(ctx, store)
lg = lg.With(). lg = lg.With().
Str("environmentName", state.Name). Str("environment", state.Name).
Str("environmentId", state.ID).
Logger() Logger()
cuePath := cue.MakePath() cuePath := cue.MakePath()

View File

@ -6,7 +6,6 @@ import (
"dagger.io/go/cmd/dagger/cmd/input" "dagger.io/go/cmd/dagger/cmd/input"
"dagger.io/go/cmd/dagger/cmd/output" "dagger.io/go/cmd/dagger/cmd/output"
"dagger.io/go/cmd/dagger/cmd/plan"
"dagger.io/go/cmd/dagger/logger" "dagger.io/go/cmd/dagger/logger"
"github.com/moby/buildkit/util/appcontext" "github.com/moby/buildkit/util/appcontext"
"github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go"
@ -24,6 +23,7 @@ func init() {
rootCmd.PersistentFlags().String("log-format", "", "Log format (json, pretty). Defaults to json if the terminal is not a tty") rootCmd.PersistentFlags().String("log-format", "", "Log format (json, pretty). Defaults to json if the terminal is not a tty")
rootCmd.PersistentFlags().StringP("log-level", "l", "info", "Log level") rootCmd.PersistentFlags().StringP("log-level", "l", "info", "Log level")
rootCmd.PersistentFlags().StringP("environment", "e", "", "Select an environment") rootCmd.PersistentFlags().StringP("environment", "e", "", "Select an environment")
rootCmd.PersistentFlags().StringP("workspace", "w", "", "Specify a workspace (defaults to current git repository)")
rootCmd.PersistentPreRun = func(*cobra.Command, []string) { rootCmd.PersistentPreRun = func(*cobra.Command, []string) {
go checkVersion() go checkVersion()
@ -33,17 +33,16 @@ func init() {
} }
rootCmd.AddCommand( rootCmd.AddCommand(
computeCmd, initCmd,
newCmd, newCmd,
computeCmd,
listCmd, listCmd,
queryCmd, queryCmd,
upCmd, upCmd,
downCmd, downCmd,
deleteCmd,
historyCmd, historyCmd,
loginCmd, loginCmd,
logoutCmd, logoutCmd,
plan.Cmd,
input.Cmd, input.Cmd,
output.Cmd, output.Cmd,
versionCmd, versionCmd,

View File

@ -3,7 +3,6 @@ package cmd
import ( import (
"dagger.io/go/cmd/dagger/cmd/common" "dagger.io/go/cmd/dagger/cmd/common"
"dagger.io/go/cmd/dagger/logger" "dagger.io/go/cmd/dagger/logger"
"dagger.io/go/dagger"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -23,15 +22,13 @@ var upCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
lg := logger.New() lg := logger.New()
ctx := lg.WithContext(cmd.Context()) ctx := lg.WithContext(cmd.Context())
store, err := dagger.DefaultStore()
if err != nil {
lg.Fatal().Err(err).Msg("failed to load store")
}
state := common.GetCurrentEnvironmentState(ctx, store) workspace := common.CurrentWorkspace(ctx)
result := common.EnvironmentUp(ctx, state, viper.GetBool("no-cache")) st := common.CurrentEnvironmentState(ctx, workspace)
state.Computed = result.Computed().JSON().String() result := common.EnvironmentUp(ctx, st, viper.GetBool("no-cache"))
if err := store.UpdateEnvironment(ctx, state, nil); err != nil {
st.Computed = result.Computed().JSON().PrettyString()
if err := workspace.Save(ctx, st); err != nil {
lg.Fatal().Err(err).Msg("failed to update environment") lg.Fatal().Err(err).Msg("failed to update environment")
} }
}, },

View File

@ -26,6 +26,7 @@ import (
"dagger.io/go/pkg/progressui" "dagger.io/go/pkg/progressui"
"dagger.io/go/dagger/compiler" "dagger.io/go/dagger/compiler"
"dagger.io/go/dagger/state"
) )
// A dagger client // A dagger client
@ -63,7 +64,7 @@ func NewClient(ctx context.Context, host string, noCache bool) (*Client, error)
type ClientDoFunc func(context.Context, *Environment, Solver) error type ClientDoFunc func(context.Context, *Environment, Solver) error
// FIXME: return completed *Route, instead of *compiler.Value // FIXME: return completed *Route, instead of *compiler.Value
func (c *Client) Do(ctx context.Context, state *EnvironmentState, fn ClientDoFunc) (*Environment, error) { func (c *Client) Do(ctx context.Context, state *state.State, fn ClientDoFunc) (*Environment, error) {
lg := log.Ctx(ctx) lg := log.Ctx(ctx)
eg, gctx := errgroup.WithContext(ctx) eg, gctx := errgroup.WithContext(ctx)

View File

@ -112,5 +112,5 @@ func (s JSON) PrettyString() string {
if err := json.Indent(b, []byte(raw), "", " "); err != nil { if err := json.Indent(b, []byte(raw), "", " "); err != nil {
return raw return raw
} }
return b.String() return fmt.Sprintf("%s\n", b.String())
} }

View File

@ -10,6 +10,7 @@ import (
"cuelang.org/go/cue" "cuelang.org/go/cue"
cueflow "cuelang.org/go/tools/flow" cueflow "cuelang.org/go/tools/flow"
"dagger.io/go/dagger/compiler" "dagger.io/go/dagger/compiler"
"dagger.io/go/dagger/state"
"dagger.io/go/stdlib" "dagger.io/go/stdlib"
"github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go"
@ -19,7 +20,7 @@ import (
) )
type Environment struct { type Environment struct {
state *EnvironmentState state *state.State
// Layer 1: plan configuration // Layer 1: plan configuration
plan *compiler.Value plan *compiler.Value
@ -31,7 +32,7 @@ type Environment struct {
computed *compiler.Value computed *compiler.Value
} }
func NewEnvironment(st *EnvironmentState) (*Environment, error) { func NewEnvironment(st *state.State) (*Environment, error) {
e := &Environment{ e := &Environment{
state: st, state: st,
@ -41,15 +42,15 @@ func NewEnvironment(st *EnvironmentState) (*Environment, error) {
} }
// Prepare inputs // Prepare inputs
for _, input := range st.Inputs { for key, input := range st.Inputs {
v, err := input.Value.Compile() v, err := input.Compile(st)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if input.Key == "" { if key == "" {
err = e.input.FillPath(cue.MakePath(), v) err = e.input.FillPath(cue.MakePath(), v)
} else { } else {
err = e.input.FillPath(cue.ParsePath(input.Key), v) err = e.input.FillPath(cue.ParsePath(key), v)
} }
if err != nil { if err != nil {
return nil, err return nil, err
@ -59,16 +60,12 @@ func NewEnvironment(st *EnvironmentState) (*Environment, error) {
return e, nil return e, nil
} }
func (e *Environment) ID() string {
return e.state.ID
}
func (e *Environment) Name() string { func (e *Environment) Name() string {
return e.state.Name return e.state.Name
} }
func (e *Environment) PlanSource() Input { func (e *Environment) PlanSource() state.Input {
return e.state.PlanSource return e.state.PlanSource()
} }
func (e *Environment) Plan() *compiler.Value { func (e *Environment) Plan() *compiler.Value {
@ -88,7 +85,7 @@ func (e *Environment) LoadPlan(ctx context.Context, s 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() planSource, err := e.state.PlanSource().Compile(e.state)
if err != nil { if err != nil {
return err return err
} }
@ -106,7 +103,7 @@ func (e *Environment) LoadPlan(ctx context.Context, s Solver) error {
} }
plan, err := compiler.Build(sources) plan, err := compiler.Build(sources)
if err != nil { if err != nil {
return fmt.Errorf("plan config: %w", err) return fmt.Errorf("plan config: %w", compiler.Err(err))
} }
e.plan = plan e.plan = plan
@ -159,7 +156,7 @@ func (e *Environment) LocalDirs() map[string]string {
} }
// 2. Scan the plan // 2. Scan the plan
plan, err := e.state.PlanSource.Compile() plan, err := e.state.PlanSource().Compile(e.state)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -0,0 +1,24 @@
package dagger
import (
"testing"
"dagger.io/go/dagger/state"
"github.com/stretchr/testify/require"
)
func TestLocalDirs(t *testing.T) {
st := &state.State{
Path: "/tmp/source",
Plan: "/tmp/source/plan",
}
require.NoError(t, st.SetInput("www.source", state.DirInput("/", []string{})))
environment, err := NewEnvironment(st)
require.NoError(t, err)
localdirs := environment.LocalDirs()
require.Len(t, localdirs, 2)
require.Contains(t, localdirs, "/")
require.Contains(t, localdirs, "/tmp/source/plan")
}

View File

@ -1,22 +0,0 @@
package dagger
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestInputDir(t *testing.T) {
st := &EnvironmentState{
PlanSource: DirInput("/tmp/source", []string{}),
}
require.NoError(t, st.SetInput("www.source", DirInput("/", []string{})))
environment, err := NewEnvironment(st)
require.NoError(t, err)
localdirs := environment.LocalDirs()
require.Len(t, localdirs, 2)
require.Contains(t, localdirs, "/")
require.Contains(t, localdirs, "/tmp/source")
}

159
dagger/keychain/encrypt.go Normal file
View File

@ -0,0 +1,159 @@
package keychain
import (
"context"
"fmt"
"os"
"time"
"go.mozilla.org/sops/v3"
sopsaes "go.mozilla.org/sops/v3/aes"
sopsage "go.mozilla.org/sops/v3/age"
"go.mozilla.org/sops/v3/cmd/sops/common"
sopskeys "go.mozilla.org/sops/v3/keys"
sopsyaml "go.mozilla.org/sops/v3/stores/yaml"
"go.mozilla.org/sops/v3/version"
)
var (
cipher = sopsaes.NewCipher()
)
// setupEnv: hack to inject a SOPS env var for age
func setupEnv() error {
p, err := Path()
if err != nil {
return err
}
return os.Setenv("SOPS_AGE_KEY_FILE", p)
}
// Encrypt data using SOPS with the AGE backend, using the provided public key
func Encrypt(ctx context.Context, path string, plaintext []byte, key string) ([]byte, error) {
if err := setupEnv(); err != nil {
return nil, err
}
store := &sopsyaml.Store{}
branches, err := store.LoadPlainFile(plaintext)
if err != nil {
return nil, err
}
ageKeys, err := sopsage.MasterKeysFromRecipients(key)
if err != nil {
return nil, err
}
ageMasterKeys := make([]sopskeys.MasterKey, 0, len(ageKeys))
for _, k := range ageKeys {
ageMasterKeys = append(ageMasterKeys, k)
}
var group sops.KeyGroup
group = append(group, ageMasterKeys...)
tree := sops.Tree{
Branches: branches,
Metadata: sops.Metadata{
KeyGroups: []sops.KeyGroup{group},
EncryptedSuffix: "secret",
Version: version.Version,
},
FilePath: path,
}
// Generate a data key
dataKey, errs := tree.GenerateDataKey()
if len(errs) > 0 {
return nil, fmt.Errorf("error encrypting the data key with one or more master keys: %v", errs)
}
err = common.EncryptTree(common.EncryptTreeOpts{
DataKey: dataKey, Tree: &tree, Cipher: cipher,
})
if err != nil {
return nil, err
}
return store.EmitEncryptedFile(tree)
}
// Reencrypt a file with new content using the same keys
func Reencrypt(_ context.Context, path string, plaintext []byte) ([]byte, error) {
if err := setupEnv(); err != nil {
return nil, err
}
current, err := os.ReadFile(path)
if err != nil {
return nil, err
}
// Load the encrypted file
store := &sopsyaml.Store{}
tree, err := store.LoadEncryptedFile(current)
if err != nil {
return nil, err
}
// Update the file with the new data
newBranches, err := store.LoadPlainFile(plaintext)
if err != nil {
return nil, err
}
tree.Branches = newBranches
// Re-encrypt the file
key, err := tree.Metadata.GetDataKey()
if err != nil {
return nil, err
}
err = common.EncryptTree(common.EncryptTreeOpts{
DataKey: key, Tree: &tree, Cipher: cipher,
})
if err != nil {
return nil, err
}
return store.EmitEncryptedFile(tree)
}
// Decrypt data using sops
func Decrypt(_ context.Context, encrypted []byte) ([]byte, error) {
if err := setupEnv(); err != nil {
return nil, err
}
store := &sopsyaml.Store{}
// Load SOPS file and access the data key
tree, err := store.LoadEncryptedFile(encrypted)
if err != nil {
return nil, err
}
key, err := tree.Metadata.GetDataKey()
if err != nil {
return nil, err
}
// Decrypt the tree
mac, err := tree.Decrypt(key, cipher)
if err != nil {
return nil, err
}
// Compute the hash of the cleartext tree and compare it with
// the one that was stored in the document. If they match,
// integrity was preserved
originalMac, err := cipher.Decrypt(
tree.Metadata.MessageAuthenticationCode,
key,
tree.Metadata.LastModified.Format(time.RFC3339),
)
if err != nil {
return nil, err
}
if originalMac != mac {
return nil, fmt.Errorf("failed to verify data integrity. expected mac %q, got %q", originalMac, mac)
}
return store.EmitPlainFile(tree.Branches)
}

96
dagger/keychain/keys.go Normal file
View File

@ -0,0 +1,96 @@
package keychain
import (
"context"
"errors"
"fmt"
"os"
"os/user"
"path"
"path/filepath"
"time"
"filippo.io/age"
"github.com/rs/zerolog/log"
)
func Path() (string, error) {
usr, err := user.Current()
if err != nil {
return "", err
}
return path.Join(usr.HomeDir, ".dagger", "keys.txt"), nil
}
func Default(ctx context.Context) (string, error) {
keys, err := List(ctx)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return Generate(ctx)
}
return "", err
}
if len(keys) == 0 {
return "", errors.New("no identities found in the keys file")
}
return keys[0].Recipient().String(), nil
}
func Generate(ctx context.Context) (string, error) {
keysFile, err := Path()
if err != nil {
return "", err
}
k, err := age.GenerateX25519Identity()
if err != nil {
return "", fmt.Errorf("internal error: %v", err)
}
if err := os.MkdirAll(filepath.Dir(keysFile), 0755); err != nil {
return "", err
}
f, err := os.OpenFile(keysFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
if err != nil {
return "", fmt.Errorf("failed to open keys file %q: %v", keysFile, err)
}
defer f.Close()
fmt.Fprintf(f, "# created: %s\n", time.Now().Format(time.RFC3339))
fmt.Fprintf(f, "# public key: %s\n", k.Recipient())
fmt.Fprintf(f, "%s\n", k)
pubkey := k.Recipient().String()
log.Ctx(ctx).Debug().Str("publicKey", pubkey).Msg("generating keypair")
return pubkey, nil
}
func List(ctx context.Context) ([]*age.X25519Identity, error) {
keysFile, err := Path()
if err != nil {
return nil, err
}
f, err := os.Open(keysFile)
if err != nil {
return nil, fmt.Errorf("failed to open keys file file %q: %w", keysFile, err)
}
ids, err := age.ParseIdentities(f)
if err != nil {
return nil, fmt.Errorf("failed to parse input: %w", err)
}
keys := make([]*age.X25519Identity, 0, len(ids))
for _, id := range ids {
key, ok := ids[0].(*age.X25519Identity)
if !ok {
return nil, fmt.Errorf("internal error: unexpected identity type: %T", id)
}
keys = append(keys, key)
}
return keys, nil
}

View File

@ -1,56 +0,0 @@
package dagger
// Contents of an environment serialized to a file
type EnvironmentState struct {
// Globally unique environment ID
ID string `json:"id,omitempty"`
// Human-friendly environment name.
// A environment may have more than one name.
// FIXME: store multiple names?
Name string `json:"name,omitempty"`
// Cue module containing the environment plan
// The input's top-level artifact is used as a module directory.
PlanSource Input `json:"plan,omitempty"`
// User Inputs
Inputs []inputKV `json:"inputs,omitempty"`
// Computed values
Computed string `json:"output,omitempty"`
}
type inputKV struct {
Key string `json:"key,omitempty"`
Value Input `json:"value,omitempty"`
}
func (s *EnvironmentState) SetInput(key string, value Input) error {
for i, inp := range s.Inputs {
if inp.Key != key {
continue
}
// Remove existing inputs with the same key
s.Inputs = append(s.Inputs[:i], s.Inputs[i+1:]...)
}
s.Inputs = append(s.Inputs, inputKV{Key: key, Value: value})
return nil
}
// Remove all inputs at the given key, including sub-keys.
// For example RemoveInputs("foo.bar") will remove all inputs
// at foo.bar, foo.bar.baz, etc.
func (s *EnvironmentState) RemoveInputs(key string) error {
newInputs := make([]inputKV, 0, len(s.Inputs))
for _, i := range s.Inputs {
if i.Key == key {
continue
}
newInputs = append(newInputs, i)
}
s.Inputs = newInputs
return nil
}

View File

@ -1,10 +1,12 @@
package dagger package state
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"path"
"path/filepath" "path/filepath"
"strings"
"cuelang.org/go/cue" "cuelang.org/go/cue"
@ -23,64 +25,44 @@ import (
// Under the hood, an artifact is encoded as a LLB pipeline, and // Under the hood, an artifact is encoded as a LLB pipeline, and
// attached to the cue configuration as a // attached to the cue configuration as a
// //
type InputType string
const (
InputTypeDir InputType = "dir"
InputTypeGit InputType = "git"
InputTypeDocker InputType = "docker"
InputTypeText InputType = "text"
InputTypeJSON InputType = "json"
InputTypeYAML InputType = "yaml"
InputTypeFile InputType = "file"
InputTypeEmpty InputType = ""
)
type Input struct { type Input struct {
Type InputType `json:"type,omitempty"` Dir *dirInput `yaml:"dir,omitempty"`
Git *gitInput `yaml:"git,omitempty"`
Dir *dirInput `json:"dir,omitempty"` Docker *dockerInput `yaml:"docker,omitempty"`
Git *gitInput `json:"git,omitempty"` Secret *secretInput `yaml:"secret,omitempty"`
Docker *dockerInput `json:"docker,omitempty"` Text *textInput `yaml:"text,omitempty"`
Text *textInput `json:"text,omitempty"` JSON *jsonInput `yaml:"json,omitempty"`
JSON *jsonInput `json:"json,omitempty"` YAML *yamlInput `yaml:"yaml,omitempty"`
YAML *yamlInput `json:"yaml,omitempty"` File *fileInput `yaml:"file,omitempty"`
File *fileInput `json:"file,omitempty"`
} }
func (i Input) Compile() (*compiler.Value, error) { func (i Input) Compile(state *State) (*compiler.Value, error) {
switch i.Type { switch {
case InputTypeDir: case i.Dir != nil:
return i.Dir.Compile() return i.Dir.Compile(state)
case InputTypeGit: case i.Git != nil:
return i.Git.Compile() return i.Git.Compile(state)
case InputTypeDocker: case i.Docker != nil:
return i.Docker.Compile() return i.Docker.Compile(state)
case InputTypeText: case i.Text != nil:
return i.Text.Compile() return i.Text.Compile(state)
case InputTypeJSON: case i.Secret != nil:
return i.JSON.Compile() return i.Secret.Compile(state)
case InputTypeYAML: case i.JSON != nil:
return i.YAML.Compile() return i.JSON.Compile(state)
case InputTypeFile: case i.YAML != nil:
return i.File.Compile() return i.YAML.Compile(state)
case "": case i.File != nil:
return nil, fmt.Errorf("input has not been set") return i.File.Compile(state)
default: default:
return nil, fmt.Errorf("unsupported input type: %s", i.Type) return nil, fmt.Errorf("input has not been set")
} }
} }
// An input artifact loaded from a local directory // An input artifact loaded from a local directory
func DirInput(path string, include []string) Input { func DirInput(path string, include []string) Input {
// resolve absolute path
path, err := filepath.Abs(path)
if err != nil {
panic(err)
}
return Input{ return Input{
Type: InputTypeDir,
Dir: &dirInput{ Dir: &dirInput{
Path: path, Path: path,
Include: include, Include: include,
@ -93,7 +75,7 @@ type dirInput struct {
Include []string `json:"include,omitempty"` Include []string `json:"include,omitempty"`
} }
func (dir dirInput) Compile() (*compiler.Value, error) { func (dir dirInput) Compile(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
@ -106,9 +88,18 @@ func (dir dirInput) Compile() (*compiler.Value, error) {
return nil, err return nil, err
} }
} }
p := dir.Path
if !filepath.IsAbs(p) {
p = filepath.Clean(path.Join(state.Workspace, dir.Path))
}
if !strings.HasPrefix(p, state.Workspace) {
return nil, fmt.Errorf("%q is outside the workspace", dir.Path)
}
llb := fmt.Sprintf( llb := fmt.Sprintf(
`#up: [{do:"local",dir:"%s", include:%s}]`, `#up: [{do:"local",dir:"%s", include:%s}]`,
dir.Path, p,
includeLLB, includeLLB,
) )
return compiler.Compile("", llb) return compiler.Compile("", llb)
@ -123,7 +114,6 @@ type gitInput struct {
func GitInput(remote, ref, dir string) Input { func GitInput(remote, ref, dir string) Input {
return Input{ return Input{
Type: InputTypeGit,
Git: &gitInput{ Git: &gitInput{
Remote: remote, Remote: remote,
Ref: ref, Ref: ref,
@ -132,7 +122,7 @@ func GitInput(remote, ref, dir string) Input {
} }
} }
func (git gitInput) Compile() (*compiler.Value, error) { func (git gitInput) Compile(_ *State) (*compiler.Value, error) {
ref := "HEAD" ref := "HEAD"
if git.Ref != "" { if git.Ref != "" {
ref = git.Ref ref = git.Ref
@ -148,7 +138,6 @@ func (git gitInput) Compile() (*compiler.Value, error) {
// An input artifact loaded from a docker container // An input artifact loaded from a docker container
func DockerInput(ref string) Input { func DockerInput(ref string) Input {
return Input{ return Input{
Type: InputTypeDocker,
Docker: &dockerInput{ Docker: &dockerInput{
Ref: ref, Ref: ref,
}, },
@ -159,69 +148,68 @@ type dockerInput struct {
Ref string `json:"ref,omitempty"` Ref string `json:"ref,omitempty"`
} }
func (i dockerInput) Compile() (*compiler.Value, error) { func (i dockerInput) Compile(_ *State) (*compiler.Value, error) {
panic("NOT IMPLEMENTED") panic("NOT IMPLEMENTED")
} }
// An input value encoded as text // An input value encoded as text
func TextInput(data string) Input { func TextInput(data string) Input {
i := textInput(data)
return Input{ return Input{
Type: InputTypeText, Text: &i,
Text: &textInput{
Data: data,
},
} }
} }
type textInput struct { type textInput string
Data string `json:"data,omitempty"`
func (i textInput) Compile(_ *State) (*compiler.Value, error) {
return compiler.Compile("", fmt.Sprintf("%q", i))
} }
func (i textInput) Compile() (*compiler.Value, error) { // A secret input value
return compiler.Compile("", fmt.Sprintf("%q", i.Data)) func SecretInput(data string) Input {
i := secretInput(data)
return Input{
Secret: &i,
}
}
type secretInput string
func (i secretInput) Compile(_ *State) (*compiler.Value, error) {
return compiler.Compile("", fmt.Sprintf("%q", i))
} }
// An input value encoded as JSON // An input value encoded as JSON
func JSONInput(data string) Input { func JSONInput(data string) Input {
i := jsonInput(data)
return Input{ return Input{
Type: InputTypeJSON, JSON: &i,
JSON: &jsonInput{
Data: data,
},
} }
} }
type jsonInput struct { type jsonInput string
// Marshalled JSON data
Data string `json:"data,omitempty"`
}
func (i jsonInput) Compile() (*compiler.Value, error) { func (i jsonInput) Compile(_ *State) (*compiler.Value, error) {
return compiler.DecodeJSON("", []byte(i.Data)) return compiler.DecodeJSON("", []byte(i))
} }
// An input value encoded as YAML // An input value encoded as YAML
func YAMLInput(data string) Input { func YAMLInput(data string) Input {
i := yamlInput(data)
return Input{ return Input{
Type: InputTypeYAML, YAML: &i,
YAML: &yamlInput{
Data: data,
},
} }
} }
type yamlInput struct { type yamlInput string
// Marshalled YAML data
Data string `json:"data,omitempty"`
}
func (i yamlInput) Compile() (*compiler.Value, error) { func (i yamlInput) Compile(_ *State) (*compiler.Value, error) {
return compiler.DecodeYAML("", []byte(i.Data)) return compiler.DecodeYAML("", []byte(i))
} }
func FileInput(data string) Input { func FileInput(data string) Input {
return Input{ return Input{
Type: InputTypeFile,
File: &fileInput{ File: &fileInput{
Path: data, Path: data,
}, },
@ -232,7 +220,7 @@ type fileInput struct {
Path string `json:"data,omitempty"` Path string `json:"data,omitempty"`
} }
func (i fileInput) Compile() (*compiler.Value, error) { func (i fileInput) Compile(_ *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

46
dagger/state/state.go Normal file
View File

@ -0,0 +1,46 @@
package state
// Contents of an environment serialized to a file
type State struct {
// State path
Path string `yaml:"-"`
// Workspace path
Workspace string `yaml:"-"`
// Plan path
Plan string `yaml:"-"`
// Human-friendly environment name.
// A environment may have more than one name.
// FIXME: store multiple names?
Name string `yaml:"name,omitempty"`
// User Inputs
Inputs map[string]Input `yaml:"inputs,omitempty"`
// Computed values
Computed string `yaml:"-"`
}
// Cue module containing the environment plan
// The input's top-level artifact is used as a module directory.
func (s *State) PlanSource() Input {
return DirInput(s.Plan, []string{"*.cue", "cue.mod"})
}
func (s *State) SetInput(key string, value Input) error {
if s.Inputs == nil {
s.Inputs = make(map[string]Input)
}
s.Inputs[key] = value
return nil
}
// Remove all inputs at the given key, including sub-keys.
// For example RemoveInputs("foo.bar") will remove all inputs
// at foo.bar, foo.bar.baz, etc.
func (s *State) RemoveInputs(key string) error {
delete(s.Inputs, key)
return nil
}

270
dagger/state/workspace.go Normal file
View File

@ -0,0 +1,270 @@
package state
import (
"bytes"
"context"
"errors"
"fmt"
"os"
"path"
"path/filepath"
"dagger.io/go/dagger/keychain"
"github.com/rs/zerolog/log"
"gopkg.in/yaml.v3"
)
var (
ErrNotInit = errors.New("not initialized")
ErrAlreadyInit = errors.New("already initialized")
ErrNotExist = errors.New("environment doesn't exist")
ErrExist = errors.New("environment already exists")
)
const (
daggerDir = ".dagger"
envDir = "env"
stateDir = "state"
planDir = "plan"
manifestFile = "values.yaml"
computedFile = "computed.json"
)
type Workspace struct {
Path string
}
func Init(ctx context.Context, dir string) (*Workspace, error) {
root, err := filepath.Abs(dir)
if err != nil {
return nil, err
}
daggerRoot := path.Join(root, daggerDir)
if err := os.Mkdir(daggerRoot, 0755); err != nil {
if errors.Is(err, os.ErrExist) {
return nil, ErrAlreadyInit
}
return nil, err
}
if err := os.Mkdir(path.Join(daggerRoot, envDir), 0755); err != nil {
return nil, err
}
return &Workspace{
Path: root,
}, nil
}
func Open(ctx context.Context, dir string) (*Workspace, error) {
_, err := os.Stat(path.Join(dir, daggerDir))
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, ErrNotInit
}
return nil, err
}
root, err := filepath.Abs(dir)
if err != nil {
return nil, err
}
return &Workspace{
Path: root,
}, nil
}
func Current(ctx context.Context) (*Workspace, error) {
current, err := os.Getwd()
if err != nil {
return nil, err
}
// Walk every parent directory to find .dagger
for {
_, err := os.Stat(path.Join(current, daggerDir, envDir))
if err == nil {
return Open(ctx, current)
}
parent := filepath.Dir(current)
if parent == current {
break
}
current = parent
}
return nil, ErrNotInit
}
func (w *Workspace) envPath(name string) string {
return path.Join(w.Path, daggerDir, envDir, name)
}
func (w *Workspace) List(ctx context.Context) ([]*State, error) {
var (
environments = []*State{}
err error
)
files, err := os.ReadDir(path.Join(w.Path, daggerDir, envDir))
if err != nil {
return nil, err
}
for _, f := range files {
if !f.IsDir() {
continue
}
st, err := w.Get(ctx, f.Name())
if err != nil {
log.
Ctx(ctx).
Err(err).
Str("name", f.Name()).
Msg("failed to load environment")
continue
}
environments = append(environments, st)
}
return environments, nil
}
func (w *Workspace) Get(ctx context.Context, name string) (*State, error) {
envPath, err := filepath.Abs(w.envPath(name))
if err != nil {
return nil, err
}
if _, err := os.Stat(envPath); err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, ErrNotExist
}
return nil, err
}
manifest, err := os.ReadFile(path.Join(envPath, manifestFile))
if err != nil {
return nil, err
}
manifest, err = keychain.Decrypt(ctx, manifest)
if err != nil {
return nil, fmt.Errorf("unable to decrypt state: %w", err)
}
var st State
if err := yaml.Unmarshal(manifest, &st); err != nil {
return nil, err
}
st.Path = envPath
st.Plan = path.Join(envPath, planDir)
st.Workspace = w.Path
computed, err := os.ReadFile(path.Join(envPath, stateDir, computedFile))
if err == nil {
st.Computed = string(computed)
}
return &st, nil
}
func (w *Workspace) Save(ctx context.Context, st *State) error {
data, err := yaml.Marshal(st)
if err != nil {
return err
}
manifestPath := path.Join(st.Path, manifestFile)
currentEncrypted, err := os.ReadFile(manifestPath)
if err != nil {
return err
}
currentPlain, err := keychain.Decrypt(ctx, currentEncrypted)
if err != nil {
return fmt.Errorf("unable to decrypt state: %w", err)
}
// Only update the encrypted file if there were changes
if !bytes.Equal(data, currentPlain) {
encrypted, err := keychain.Reencrypt(ctx, manifestPath, data)
if err != nil {
return err
}
if err := os.WriteFile(manifestPath, encrypted, 0600); err != nil {
return err
}
}
if st.Computed != "" {
state := path.Join(st.Path, stateDir)
if err := os.MkdirAll(state, 0755); err != nil {
return err
}
err := os.WriteFile(
path.Join(state, "computed.json"),
[]byte(st.Computed),
0600)
if err != nil {
return err
}
}
return nil
}
func (w *Workspace) Create(ctx context.Context, name string) (*State, error) {
envPath, err := filepath.Abs(w.envPath(name))
if err != nil {
return nil, err
}
// Environment directory
if err := os.MkdirAll(envPath, 0755); err != nil {
if errors.Is(err, os.ErrExist) {
return nil, ErrExist
}
return nil, err
}
// Plan directory
if err := os.Mkdir(path.Join(envPath, planDir), 0755); err != nil {
if errors.Is(err, os.ErrExist) {
return nil, ErrExist
}
return nil, err
}
manifestPath := path.Join(envPath, manifestFile)
st := &State{
Path: envPath,
Workspace: w.Path,
Plan: path.Join(envPath, planDir),
Name: name,
}
data, err := yaml.Marshal(st)
if err != nil {
return nil, err
}
key, err := keychain.Default(ctx)
if err != nil {
return nil, err
}
encrypted, err := keychain.Encrypt(ctx, manifestPath, data, key)
if err != nil {
return nil, err
}
if err := os.WriteFile(manifestPath, encrypted, 0600); err != nil {
return nil, err
}
err = os.WriteFile(
path.Join(envPath, ".gitignore"),
[]byte("# dagger state\nstate/**\n"),
0600,
)
if err != nil {
return nil, err
}
return st, nil
}

View File

@ -0,0 +1,109 @@
package state
import (
"context"
"os"
"path"
"strings"
"testing"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
func TestWorkspace(t *testing.T) {
ctx := context.TODO()
root, err := os.MkdirTemp(os.TempDir(), "dagger-*")
require.NoError(t, err)
// Open should fail since the directory is not initialized
_, err = Open(ctx, root)
require.ErrorIs(t, ErrNotInit, err)
// Init
workspace, err := Init(ctx, root)
require.NoError(t, err)
require.Equal(t, root, workspace.Path)
// Create
st, err := workspace.Create(ctx, "test")
require.NoError(t, err)
require.Equal(t, "test", st.Name)
// Open
workspace, err = Open(ctx, root)
require.NoError(t, err)
require.Equal(t, root, workspace.Path)
// List
envs, err := workspace.List(ctx)
require.NoError(t, err)
require.Len(t, envs, 1)
require.Equal(t, "test", envs[0].Name)
// Get
env, err := workspace.Get(ctx, "test")
require.NoError(t, err)
require.Equal(t, "test", env.Name)
// Save
require.NoError(t, env.SetInput("foo", TextInput("bar")))
require.NoError(t, workspace.Save(ctx, env))
workspace, err = Open(ctx, root)
require.NoError(t, err)
env, err = workspace.Get(ctx, "test")
require.NoError(t, err)
require.Contains(t, env.Inputs, "foo")
}
func TestEncryption(t *testing.T) {
ctx := context.TODO()
readManifest := func(st *State) *State {
data, err := os.ReadFile(path.Join(st.Path, manifestFile))
require.NoError(t, err)
m := State{}
require.NoError(t, yaml.Unmarshal(data, &m))
return &m
}
root, err := os.MkdirTemp(os.TempDir(), "dagger-*")
require.NoError(t, err)
workspace, err := Init(ctx, root)
require.NoError(t, err)
_, err = workspace.Create(ctx, "test")
require.NoError(t, err)
// Set a plaintext input, make sure it is not encrypted
st, err := workspace.Get(ctx, "test")
require.NoError(t, err)
require.NoError(t, st.SetInput("plain", TextInput("plain")))
require.NoError(t, workspace.Save(ctx, st))
o := readManifest(st)
require.Contains(t, o.Inputs, "plain")
require.Equal(t, "plain", string(*o.Inputs["plain"].Text))
// Set a secret input, make sure it's encrypted
st, err = workspace.Get(ctx, "test")
require.NoError(t, err)
require.NoError(t, st.SetInput("secret", SecretInput("secret")))
require.NoError(t, workspace.Save(ctx, st))
o = readManifest(st)
require.Contains(t, o.Inputs, "secret")
secretValue := string(*o.Inputs["secret"].Secret)
require.NotEqual(t, "secret", secretValue)
require.True(t, strings.HasPrefix(secretValue, "ENC["))
// Change another input, make sure our secret didn't change
st, err = workspace.Get(ctx, "test")
require.NoError(t, err)
require.NoError(t, st.SetInput("plain", TextInput("different")))
require.NoError(t, workspace.Save(ctx, st))
o = readManifest(st)
require.Contains(t, o.Inputs, "plain")
require.Equal(t, "different", string(*o.Inputs["plain"].Text))
require.Contains(t, o.Inputs, "secret")
require.Equal(t, secretValue, string(*o.Inputs["secret"].Secret))
}

View File

@ -1,250 +0,0 @@
package dagger
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"path"
"sync"
"github.com/google/uuid"
)
var (
ErrEnvironmentExist = errors.New("environment already exists")
ErrEnvironmentNotExist = errors.New("environment doesn't exist")
)
const (
defaultStoreRoot = "$HOME/.dagger/store"
)
type Store struct {
root string
l sync.RWMutex
// ID -> Environment
environments map[string]*EnvironmentState
// Name -> Environment
environmentsByName map[string]*EnvironmentState
// Path -> (ID->Environment)
environmentsByPath map[string]map[string]*EnvironmentState
// ID -> (Path->{})
pathsByEnvironmentID map[string]map[string]struct{}
}
func NewStore(root string) (*Store, error) {
store := &Store{
root: root,
environments: make(map[string]*EnvironmentState),
environmentsByName: make(map[string]*EnvironmentState),
environmentsByPath: make(map[string]map[string]*EnvironmentState),
pathsByEnvironmentID: make(map[string]map[string]struct{}),
}
return store, store.loadAll()
}
func DefaultStore() (*Store, error) {
if root := os.Getenv("DAGGER_STORE"); root != "" {
return NewStore(root)
}
return NewStore(os.ExpandEnv(defaultStoreRoot))
}
func (s *Store) environmentPath(name string) string {
// FIXME: rename to environment.json ?
return path.Join(s.root, name, "deployment.json")
}
func (s *Store) loadAll() error {
files, err := os.ReadDir(s.root)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil
}
return err
}
for _, f := range files {
if !f.IsDir() {
continue
}
if err := s.loadEnvironment(f.Name()); err != nil {
return err
}
}
return nil
}
func (s *Store) loadEnvironment(name string) error {
data, err := os.ReadFile(s.environmentPath(name))
if err != nil {
return err
}
var st EnvironmentState
if err := json.Unmarshal(data, &st); err != nil {
return err
}
s.indexEnvironment(&st)
return nil
}
func (s *Store) syncEnvironment(r *EnvironmentState) error {
p := s.environmentPath(r.Name)
if err := os.MkdirAll(path.Dir(p), 0755); err != nil {
return err
}
data, err := json.MarshalIndent(r, "", " ")
if err != nil {
return err
}
if err := os.WriteFile(p, data, 0600); err != nil {
return err
}
s.reindexEnvironment(r)
return nil
}
func (s *Store) indexEnvironment(r *EnvironmentState) {
s.environments[r.ID] = r
s.environmentsByName[r.Name] = r
mapPath := func(i Input) {
if i.Type != InputTypeDir {
return
}
if s.environmentsByPath[i.Dir.Path] == nil {
s.environmentsByPath[i.Dir.Path] = make(map[string]*EnvironmentState)
}
s.environmentsByPath[i.Dir.Path][r.ID] = r
if s.pathsByEnvironmentID[r.ID] == nil {
s.pathsByEnvironmentID[r.ID] = make(map[string]struct{})
}
s.pathsByEnvironmentID[r.ID][i.Dir.Path] = struct{}{}
}
mapPath(r.PlanSource)
for _, i := range r.Inputs {
mapPath(i.Value)
}
}
func (s *Store) deindexEnvironment(id string) {
r, ok := s.environments[id]
if !ok {
return
}
delete(s.environments, r.ID)
delete(s.environmentsByName, r.Name)
for p := range s.pathsByEnvironmentID[r.ID] {
delete(s.environmentsByPath[p], r.ID)
}
delete(s.pathsByEnvironmentID, r.ID)
}
func (s *Store) reindexEnvironment(r *EnvironmentState) {
s.deindexEnvironment(r.ID)
s.indexEnvironment(r)
}
func (s *Store) CreateEnvironment(ctx context.Context, st *EnvironmentState) error {
s.l.Lock()
defer s.l.Unlock()
if _, ok := s.environmentsByName[st.Name]; ok {
return fmt.Errorf("%s: %w", st.Name, ErrEnvironmentExist)
}
st.ID = uuid.New().String()
return s.syncEnvironment(st)
}
type UpdateOpts struct{}
func (s *Store) UpdateEnvironment(ctx context.Context, r *EnvironmentState, o *UpdateOpts) error {
s.l.Lock()
defer s.l.Unlock()
return s.syncEnvironment(r)
}
type DeleteOpts struct{}
func (s *Store) DeleteEnvironment(ctx context.Context, r *EnvironmentState, o *DeleteOpts) error {
s.l.Lock()
defer s.l.Unlock()
if err := os.Remove(s.environmentPath(r.Name)); err != nil {
return err
}
s.deindexEnvironment(r.ID)
return nil
}
func (s *Store) LookupEnvironmentByID(ctx context.Context, id string) (*EnvironmentState, error) {
s.l.RLock()
defer s.l.RUnlock()
st, ok := s.environments[id]
if !ok {
return nil, fmt.Errorf("%s: %w", id, ErrEnvironmentNotExist)
}
return st, nil
}
func (s *Store) LookupEnvironmentByName(ctx context.Context, name string) (*EnvironmentState, error) {
s.l.RLock()
defer s.l.RUnlock()
st, ok := s.environmentsByName[name]
if !ok {
return nil, fmt.Errorf("%s: %w", name, ErrEnvironmentNotExist)
}
return st, nil
}
func (s *Store) LookupEnvironmentByPath(ctx context.Context, path string) ([]*EnvironmentState, error) {
s.l.RLock()
defer s.l.RUnlock()
res := []*EnvironmentState{}
environments, ok := s.environmentsByPath[path]
if !ok {
return res, nil
}
for _, d := range environments {
res = append(res, d)
}
return res, nil
}
func (s *Store) ListEnvironments(ctx context.Context) ([]*EnvironmentState, error) {
s.l.RLock()
defer s.l.RUnlock()
environments := make([]*EnvironmentState, 0, len(s.environments))
for _, st := range s.environments {
environments = append(environments, st)
}
return environments, nil
}

View File

@ -1,123 +0,0 @@
package dagger
import (
"context"
"errors"
"os"
"testing"
"github.com/stretchr/testify/require"
)
func TestStoreLoad(t *testing.T) {
ctx := context.TODO()
root, err := os.MkdirTemp(os.TempDir(), "dagger-*")
require.NoError(t, err)
store, err := NewStore(root)
require.NoError(t, err)
_, err = store.LookupEnvironmentByName(ctx, "notexist")
require.Error(t, err)
require.True(t, errors.Is(err, ErrEnvironmentNotExist))
st := &EnvironmentState{
Name: "test",
}
require.NoError(t, store.CreateEnvironment(ctx, st))
checkEnvironments := func(store *Store) {
r, err := store.LookupEnvironmentByID(ctx, st.ID)
require.NoError(t, err)
require.NotNil(t, r)
require.Equal(t, "test", r.Name)
r, err = store.LookupEnvironmentByName(ctx, "test")
require.NoError(t, err)
require.NotNil(t, r)
require.Equal(t, "test", r.Name)
environments, err := store.ListEnvironments(ctx)
require.NoError(t, err)
require.Len(t, environments, 1)
require.Equal(t, "test", environments[0].Name)
}
checkEnvironments(store)
// Reload the environments from disk and check again
newStore, err := NewStore(root)
require.NoError(t, err)
checkEnvironments(newStore)
}
func TestStoreLookupByPath(t *testing.T) {
ctx := context.TODO()
root, err := os.MkdirTemp(os.TempDir(), "dagger-*")
require.NoError(t, err)
store, err := NewStore(root)
require.NoError(t, err)
st := &EnvironmentState{
Name: "test",
}
require.NoError(t, st.SetInput("foo", DirInput("/test/path", []string{})))
require.NoError(t, store.CreateEnvironment(ctx, st))
// Lookup by path
environments, err := store.LookupEnvironmentByPath(ctx, "/test/path")
require.NoError(t, err)
require.Len(t, environments, 1)
require.Equal(t, st.ID, environments[0].ID)
// Add a new path
require.NoError(t, st.SetInput("bar", DirInput("/test/anotherpath", []string{})))
require.NoError(t, store.UpdateEnvironment(ctx, st, nil))
// Lookup by the previous path
environments, err = store.LookupEnvironmentByPath(ctx, "/test/path")
require.NoError(t, err)
require.Len(t, environments, 1)
require.Equal(t, st.ID, environments[0].ID)
// Lookup by the new path
environments, err = store.LookupEnvironmentByPath(ctx, "/test/anotherpath")
require.NoError(t, err)
require.Len(t, environments, 1)
require.Equal(t, st.ID, environments[0].ID)
// Remove a path
require.NoError(t, st.RemoveInputs("foo"))
require.NoError(t, store.UpdateEnvironment(ctx, st, nil))
// Lookup by the removed path should fail
environments, err = store.LookupEnvironmentByPath(ctx, "/test/path")
require.NoError(t, err)
require.Len(t, environments, 0)
// Lookup by the other path should still work
environments, err = store.LookupEnvironmentByPath(ctx, "/test/anotherpath")
require.NoError(t, err)
require.Len(t, environments, 1)
// Add another environment using the same path
otherSt := &EnvironmentState{
Name: "test2",
}
require.NoError(t, otherSt.SetInput("foo", DirInput("/test/anotherpath", []string{})))
require.NoError(t, store.CreateEnvironment(ctx, otherSt))
// Lookup by path should return both environments
environments, err = store.LookupEnvironmentByPath(ctx, "/test/anotherpath")
require.NoError(t, err)
require.Len(t, environments, 2)
// Remove the first environment. Lookup by path should still return the
// second environment.
require.NoError(t, store.DeleteEnvironment(ctx, st, nil))
environments, err = store.LookupEnvironmentByPath(ctx, "/test/anotherpath")
require.NoError(t, err)
require.Len(t, environments, 1)
require.Equal(t, otherSt.ID, environments[0].ID)
}

View File

@ -26,15 +26,19 @@ To get started with Cue, we recommend the following resources:
To create a Dagger plan: To create a Dagger plan:
1\. Create a new directory anywhere in your git repository. 1\. Initialize a Dagger workspace anywhere in your git repository.
For example: `mkdir staging`. `dagger init`.
2\. Create a new file with the *.cue* extension, and open it with any text editor or IDE. 2\. Create a new environment.
For example: `staging.cue`. For example: `dagger new staging`.
3\. Describe each relay in your plan as a field in the cue configuration. 3\. Create a new file with the *.cue* extension in `.dagger/env/staging/plan`, and open it with any text editor or IDE.
For example: `.dagger/env/staging/plan/staging.cue`.
4\. Describe each relay in your plan as a field in the cue configuration.
For example: For example:
@ -67,9 +71,9 @@ For more inspiration, see these examples:
* [Add HTTP monitoring to your application](https://github.com/dagger/dagger/blob/main/examples/README.md#add-http-monitoring-to-your-application) * [Add HTTP monitoring to your application](https://github.com/dagger/dagger/blob/main/examples/README.md#add-http-monitoring-to-your-application)
* [Deploy an application to your Kubernetes cluster](https://github.com/dagger/dagger/blob/main/examples/README.md#deploy-an-application-to-your-kubernetes-cluster) * [Deploy an application to your Kubernetes cluster](https://github.com/dagger/dagger/blob/main/examples/README.md#deploy-an-application-to-your-kubernetes-cluster)
4\. Extend your plan with relay definitions from [Dagger Universe](../stdlib), an encyclopedia of cue packages curated by the Dagger community. 5\. Extend your plan with relay definitions from [Dagger Universe](../stdlib), an encyclopedia of cue packages curated by the Dagger community.
5\. If you can't find the relay you need in the Universe, you can simply create your own. 6\. If you can't find the relay you need in the Universe, you can simply create your own.
For example: For example:

4
go.mod
View File

@ -3,13 +3,13 @@ module dagger.io/go
go 1.16 go 1.16
require ( require (
cuelang.org/go v0.4.0-rc.1 cuelang.org/go v0.4.0-beta.1
filippo.io/age v1.0.0-rc.1
github.com/HdrHistogram/hdrhistogram-go v1.1.0 // indirect github.com/HdrHistogram/hdrhistogram-go v1.1.0 // indirect
github.com/KromDaniel/jonson v0.0.0-20180630143114-d2f9c3c389db github.com/KromDaniel/jonson v0.0.0-20180630143114-d2f9c3c389db
github.com/containerd/console v1.0.2 github.com/containerd/console v1.0.2
github.com/docker/distribution v2.7.1+incompatible github.com/docker/distribution v2.7.1+incompatible
github.com/emicklei/proto v1.9.0 // indirect github.com/emicklei/proto v1.9.0 // indirect
github.com/google/uuid v1.2.0
github.com/hashicorp/go-version v1.3.0 github.com/hashicorp/go-version v1.3.0
github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea
github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-colorable v0.1.8 // indirect

9
go.sum
View File

@ -44,12 +44,13 @@ contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrL
contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw= contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw=
contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA= contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA=
cuelang.org/go v0.4.0-rc.1 h1:X8fsqVhLCvXFhsWMGbI8rjTal45YOgt+ko+m7rOCySM= cuelang.org/go v0.4.0-beta.1 h1:/YjeAmymfNdTLSA3jHXNrj8Q+5Zq9by7qNOssqUBM+c=
cuelang.org/go v0.4.0-rc.1/go.mod h1:tz/edkPi+T37AZcb5GlPY+WJkL6KiDlDVupKwL3vvjs= cuelang.org/go v0.4.0-beta.1/go.mod h1:tz/edkPi+T37AZcb5GlPY+WJkL6KiDlDVupKwL3vvjs=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/age v1.0.0-beta7 h1:RZiSK+N3KL2UwT82xiCavjYw8jJHzWMEUYePAukTpk0=
filippo.io/age v1.0.0-beta7/go.mod h1:chAuTrTb0FTTmKtvs6fQTGhYTvH9AigjN1uEUsvLdZ0= filippo.io/age v1.0.0-beta7/go.mod h1:chAuTrTb0FTTmKtvs6fQTGhYTvH9AigjN1uEUsvLdZ0=
filippo.io/age v1.0.0-rc.1 h1:jQ+dz16Xxx3W/WY+YS0J96nVAAidLHO3kfQe0eOmKgI=
filippo.io/age v1.0.0-rc.1/go.mod h1:Vvd9IlwNo4Au31iqNZeZVnYtGcOf/wT4mtvZQ2ODlSk=
filippo.io/edwards25519 v1.0.0-alpha.2/go.mod h1:X+pm78QAUPtFLi1z9PYIlS/bdDnvbCOGKtZ+ACWEf7o= filippo.io/edwards25519 v1.0.0-alpha.2/go.mod h1:X+pm78QAUPtFLi1z9PYIlS/bdDnvbCOGKtZ+ACWEf7o=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
@ -899,7 +900,6 @@ github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83/go.mod h1:vvbZ2Ae7A
github.com/securego/gosec v0.0.0-20200401082031-e946c8c39989/go.mod h1:i9l/TNj+yDFh9SZXUTvspXTjbFXgZGP/UvhU1S65A4A= github.com/securego/gosec v0.0.0-20200401082031-e946c8c39989/go.mod h1:i9l/TNj+yDFh9SZXUTvspXTjbFXgZGP/UvhU1S65A4A=
github.com/securego/gosec/v2 v2.3.0/go.mod h1:UzeVyUXbxukhLeHKV3VVqo7HdoQR9MrRfFmZYotn8ME= github.com/securego/gosec/v2 v2.3.0/go.mod h1:UzeVyUXbxukhLeHKV3VVqo7HdoQR9MrRfFmZYotn8ME=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc= github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc=
github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc=
@ -1084,6 +1084,7 @@ golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=

View File

@ -4,55 +4,45 @@ setup() {
common_setup common_setup
} }
@test "dagger list" { @test "dagger init" {
run "$DAGGER" list run "$DAGGER" init
assert_success assert_success
assert_output ""
"$DAGGER" new --plan-dir "$TESTDIR"/cli/simple simple
run "$DAGGER" list run "$DAGGER" list
assert_success assert_success
assert_output --partial "simple" refute_output
run "$DAGGER" init
assert_failure
} }
@test "dagger new --plan-dir" { @test "dagger new" {
run "$DAGGER" list run "$DAGGER" new "test"
assert_success
assert_output ""
"$DAGGER" new --plan-dir "$TESTDIR"/cli/simple simple
# duplicate name
run "$DAGGER" new --plan-dir "$TESTDIR"/cli/simple simple
assert_failure assert_failure
# verify the plan works run "$DAGGER" init
"$DAGGER" up -e "simple"
# verify we have the right plan
run "$DAGGER" query -f cue -e "simple" -c -f json
assert_success assert_success
assert_output --partial '{
"bar": "another value",
"computed": "test",
"foo": "value"
}'
}
@test "dagger new --plan-git" { run "$DAGGER" list
"$DAGGER" new --plan-git https://github.com/samalba/dagger-test.git simple
"$DAGGER" up -e "simple"
run "$DAGGER" query -f cue -e "simple" -c
assert_success assert_success
assert_output --partial '{ refute_output
foo: "value"
bar: "another value" run "$DAGGER" new "test"
}' assert_success
run "$DAGGER" list
assert_success
assert_output --partial "test"
run "$DAGGER" new "test"
assert_failure
} }
@test "dagger query" { @test "dagger query" {
"$DAGGER" new --plan-dir "$TESTDIR"/cli/simple simple "$DAGGER" init
dagger_new_with_plan simple "$TESTDIR"/cli/simple
run "$DAGGER" query -l error -e "simple" run "$DAGGER" query -l error -e "simple"
assert_success assert_success
assert_output '{ assert_output '{
@ -93,24 +83,10 @@ setup() {
}' }'
} }
@test "dagger plan" {
"$DAGGER" new --plan-dir "$TESTDIR"/cli/simple simple
# plan dir
"$DAGGER" -e "simple" plan dir "$TESTDIR"/cli/simple
run "$DAGGER" -e "simple" query
assert_success
assert_output --partial '"foo": "value"'
# plan git
"$DAGGER" -e "simple" plan git https://github.com/samalba/dagger-test.git
run "$DAGGER" -e "simple" query
assert_success
assert_output --partial '"foo": "value"'
}
@test "dagger input text" { @test "dagger input text" {
"$DAGGER" new --plan-dir "$TESTDIR"/cli/input/simple "input" "$DAGGER" init
dagger_new_with_plan input "$TESTDIR"/cli/input/simple
# simple input # simple input
"$DAGGER" input -e "input" text "input" "my input" "$DAGGER" input -e "input" text "input" "my input"
@ -176,7 +152,9 @@ setup() {
} }
@test "dagger input json" { @test "dagger input json" {
"$DAGGER" new --plan-dir "$TESTDIR"/cli/input/simple "input" "$DAGGER" init
dagger_new_with_plan input "$TESTDIR"/cli/input/simple
# simple json # simple json
"$DAGGER" input -e "input" json "structured" '{"a": "foo", "b": 42}' "$DAGGER" input -e "input" json "structured" '{"a": "foo", "b": 42}'
@ -214,7 +192,9 @@ setup() {
} }
@test "dagger input yaml" { @test "dagger input yaml" {
"$DAGGER" new --plan-dir "$TESTDIR"/cli/input/simple "input" "$DAGGER" init
dagger_new_with_plan input "$TESTDIR"/cli/input/simple
# simple yaml # simple yaml
"$DAGGER" input -e "input" yaml "structured" '{"a": "foo", "b": 42}' "$DAGGER" input -e "input" yaml "structured" '{"a": "foo", "b": 42}'
@ -252,10 +232,17 @@ setup() {
} }
@test "dagger input dir" { @test "dagger input dir" {
"$DAGGER" new --plan-dir "$TESTDIR"/cli/input/artifact "input" "$DAGGER" init
# input dir dagger_new_with_plan input "$TESTDIR"/cli/input/artifact
"$DAGGER" input -e "input" dir "source" "$TESTDIR"/cli/input/artifact/testdata
# input dir outside the workspace
run "$DAGGER" input -e "input" dir "source" /tmp
assert_failure
# input dir inside the workspace
cp -R "$TESTDIR"/cli/input/artifact/testdata/ "$DAGGER_WORKSPACE"/testdata
"$DAGGER" input -e "input" dir "source" "$DAGGER_WORKSPACE"/testdata
"$DAGGER" up -e "input" "$DAGGER" up -e "input"
run "$DAGGER" -l error query -e "input" run "$DAGGER" -l error query -e "input"
assert_success assert_success
@ -276,7 +263,9 @@ setup() {
} }
@test "dagger input git" { @test "dagger input git" {
"$DAGGER" new --plan-dir "$TESTDIR"/cli/input/artifact "input" "$DAGGER" init
dagger_new_with_plan input "$TESTDIR"/cli/input/artifact
# input git # input git
"$DAGGER" input -e "input" git "source" https://github.com/samalba/dagger-test-simple.git "$DAGGER" input -e "input" git "source" https://github.com/samalba/dagger-test-simple.git
@ -296,11 +285,3 @@ setup() {
"foo": "bar" "foo": "bar"
}' }'
} }
@test "dagger input scan" {
"$DAGGER" new --plan-dir "$TESTDIR"/cli/input/scan "scan"
# TODO "scan" option isn't implemented
run "$DAGGER" input scan -e "input"
assert_success
}

View File

@ -7,13 +7,13 @@ setup() {
@test "example: react" { @test "example: react" {
skip_unless_secrets_available "$TESTDIR"/examples/react/inputs.yaml skip_unless_secrets_available "$TESTDIR"/examples/react/inputs.yaml
"$DAGGER" new --plan-dir "$TESTDIR"/../examples/react react "$DAGGER" init
dagger_new_with_plan react "$TESTDIR"/../examples/react
sops -d "$TESTDIR"/examples/react/inputs.yaml | "$DAGGER" -e "react" input yaml "" -f - sops -d "$TESTDIR"/examples/react/inputs.yaml | "$DAGGER" -e "react" input yaml "" -f -
"$DAGGER" up -e "react" "$DAGGER" up -e "react"
# curl the URL we just deployed to check if it worked # curl the URL we just deployed to check if it worked
deployUrl=$("$DAGGER" query -l error -f text -e "react" www.deployUrl) deployUrl=$("$DAGGER" query -l error -f text -e "react" www.deployUrl)
echo "=>$deployUrl<="
run curl -sS "$deployUrl" run curl -sS "$deployUrl"
assert_success assert_success
assert_output --partial "Todo App" assert_output --partial "Todo App"

View File

@ -10,8 +10,18 @@ common_setup() {
DAGGER_LOG_FORMAT="pretty" DAGGER_LOG_FORMAT="pretty"
export DAGGER_LOG_FORMAT export DAGGER_LOG_FORMAT
DAGGER_STORE="$(mktemp -d -t dagger-store-XXXXXX)" DAGGER_WORKSPACE="$(mktemp -d -t dagger-workspace-XXXXXX)"
export DAGGER_STORE export DAGGER_WORKSPACE
}
dagger_new_with_plan() {
local name="$1"
local sourcePlan="$2"
local targetPlan="$DAGGER_WORKSPACE"/.dagger/env/"$name"/plan
"$DAGGER" new "$name"
rmdir "$targetPlan"
ln -s "$sourcePlan" "$targetPlan"
} }
skip_unless_secrets_available() { skip_unless_secrets_available() {

View File

@ -91,8 +91,11 @@ setup() {
@test "stdlib: terraform" { @test "stdlib: terraform" {
skip_unless_secrets_available "$TESTDIR"/stdlib/aws/inputs.yaml skip_unless_secrets_available "$TESTDIR"/stdlib/aws/inputs.yaml
"$DAGGER" new --plan-dir "$TESTDIR"/stdlib/terraform/s3 terraform "$DAGGER" init
"$DAGGER" -e terraform input dir TestData "$TESTDIR"/stdlib/terraform/s3/testdata dagger_new_with_plan terraform "$TESTDIR"/stdlib/terraform/s3
cp -R "$TESTDIR"/stdlib/terraform/s3/testdata "$DAGGER_WORKSPACE"/testdata
"$DAGGER" -e terraform input dir TestData "$DAGGER_WORKSPACE"/testdata
sops -d "$TESTDIR"/stdlib/aws/inputs.yaml | "$DAGGER" -e "terraform" input yaml "" -f - sops -d "$TESTDIR"/stdlib/aws/inputs.yaml | "$DAGGER" -e "terraform" input yaml "" -f -
# it must fail because of a missing var # it must fail because of a missing var