commit
93d7bb08e5
44
.dagger/env/dev-stdlib/adhoc.cue
vendored
44
.dagger/env/dev-stdlib/adhoc.cue
vendored
@ -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
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
69
.dagger/env/dev/main.cue
vendored
69
.dagger/env/dev/main.cue
vendored
@ -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
|
|
||||||
}
|
|
1
.dagger/env/hello-world
vendored
1
.dagger/env/hello-world
vendored
@ -1 +0,0 @@
|
|||||||
../../examples/hello-world
|
|
21
.dagger/env/sandbox/sandbox.cue
vendored
21
.dagger/env/sandbox/sandbox.cue
vendored
@ -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
|
|
@ -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)
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
@ -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
49
cmd/dagger/cmd/init.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -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]))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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{}))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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])),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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])),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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])),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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"
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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")
|
|
||||||
}
|
|
@ -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()
|
||||||
|
@ -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,
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
24
dagger/environment_test.go
Normal file
24
dagger/environment_test.go
Normal 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")
|
||||||
|
}
|
@ -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
159
dagger/keychain/encrypt.go
Normal 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
96
dagger/keychain/keys.go
Normal 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
|
||||||
|
}
|
@ -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
|
|
||||||
}
|
|
@ -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
46
dagger/state/state.go
Normal 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
270
dagger/state/workspace.go
Normal 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
|
||||||
|
}
|
109
dagger/state/workspace_test.go
Normal file
109
dagger/state/workspace_test.go
Normal 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))
|
||||||
|
}
|
250
dagger/store.go
250
dagger/store.go
@ -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
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
@ -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
4
go.mod
@ -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
9
go.sum
@ -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=
|
||||||
|
113
tests/cli.bats
113
tests/cli.bats
@ -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
|
|
||||||
}
|
|
||||||
|
@ -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"
|
||||||
|
@ -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() {
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user