gitflow: multi env support
Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
This commit is contained in:
parent
f0156f449f
commit
90abadf0de
@ -9,13 +9,37 @@ import (
|
|||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetCurrentEnvironmentState(ctx context.Context) *state.State {
|
func CurrentWorkspace(ctx context.Context) *state.Workspace {
|
||||||
lg := log.Ctx(ctx)
|
lg := log.Ctx(ctx)
|
||||||
|
|
||||||
// If no environment name has been given, look for the current environment
|
if workspacePath := viper.GetString("workspace"); workspacePath != "" {
|
||||||
environment := viper.GetString("environment")
|
workspace, err := state.Open(ctx, workspacePath)
|
||||||
if environment == "" {
|
if err != nil {
|
||||||
st, err := state.Current(ctx)
|
lg.
|
||||||
|
Fatal().
|
||||||
|
Err(err).
|
||||||
|
Str("path", workspacePath).
|
||||||
|
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 {
|
if err != nil {
|
||||||
lg.
|
lg.
|
||||||
Fatal().
|
Fatal().
|
||||||
@ -25,38 +49,33 @@ func GetCurrentEnvironmentState(ctx context.Context) *state.State {
|
|||||||
return st
|
return st
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point, it must be an environment name
|
environments, err := workspace.List(ctx)
|
||||||
workspace := viper.GetString("workspace")
|
|
||||||
var err error
|
|
||||||
if workspace == "" {
|
|
||||||
workspace, err = state.CurrentWorkspace(ctx)
|
|
||||||
if err != nil {
|
|
||||||
lg.
|
|
||||||
Fatal().
|
|
||||||
Err(err).
|
|
||||||
Msg("failed to determine current workspace")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
environments, err := state.List(ctx, workspace)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lg.
|
lg.
|
||||||
Fatal().
|
Fatal().
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("failed to list environments")
|
Msg("failed to list environments")
|
||||||
}
|
}
|
||||||
for _, e := range environments {
|
|
||||||
if e.Name == environment {
|
if len(environments) == 0 {
|
||||||
return e
|
lg.
|
||||||
}
|
Fatal().
|
||||||
|
Msg("no environments")
|
||||||
}
|
}
|
||||||
|
|
||||||
lg.
|
if len(environments) > 1 {
|
||||||
Fatal().
|
envNames := []string{}
|
||||||
Str("environment", environment).
|
for _, e := range environments {
|
||||||
Msg("environment not found")
|
envNames = append(envNames, e.Name)
|
||||||
|
}
|
||||||
|
lg.
|
||||||
|
Fatal().
|
||||||
|
Err(err).
|
||||||
|
Strs("environments", envNames).
|
||||||
|
Msg("multiple environments available in the workspace, select one with `--environment`")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return environments[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-compute an environment (equivalent to `dagger up`).
|
// Re-compute an environment (equivalent to `dagger up`).
|
||||||
|
@ -2,7 +2,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"dagger.io/go/cmd/dagger/logger"
|
"dagger.io/go/cmd/dagger/logger"
|
||||||
"dagger.io/go/dagger/state"
|
"dagger.io/go/dagger/state"
|
||||||
@ -24,7 +23,7 @@ var initCmd = &cobra.Command{
|
|||||||
lg := logger.New()
|
lg := logger.New()
|
||||||
ctx := lg.WithContext(cmd.Context())
|
ctx := lg.WithContext(cmd.Context())
|
||||||
|
|
||||||
dir := viper.GetString("environment")
|
dir := viper.GetString("workspace")
|
||||||
if dir == "" {
|
if dir == "" {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -36,29 +35,13 @@ var initCmd = &cobra.Command{
|
|||||||
dir = cwd
|
dir = cwd
|
||||||
}
|
}
|
||||||
|
|
||||||
var name string
|
_, err := state.Init(ctx, dir)
|
||||||
if len(args) > 0 {
|
|
||||||
name = args[0]
|
|
||||||
} else {
|
|
||||||
name = getNewEnvironmentName(dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := state.Init(ctx, dir, name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lg.Fatal().Err(err).Msg("failed to initialize")
|
lg.Fatal().Err(err).Msg("failed to initialize workspace")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNewEnvironmentName(dir string) string {
|
|
||||||
dirName := filepath.Base(dir)
|
|
||||||
if dirName == "/" {
|
|
||||||
return "root"
|
|
||||||
}
|
|
||||||
|
|
||||||
return dirName
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if err := viper.BindPFlags(initCmd.Flags()); err != nil {
|
if err := viper.BindPFlags(initCmd.Flags()); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
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/state"
|
"dagger.io/go/dagger/state"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -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], state.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{}))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,8 @@ var listCmd = &cobra.Command{
|
|||||||
lg := logger.New()
|
lg := logger.New()
|
||||||
ctx := lg.WithContext(cmd.Context())
|
ctx := lg.WithContext(cmd.Context())
|
||||||
|
|
||||||
environment := common.GetCurrentEnvironmentState(ctx)
|
workspace := common.CurrentWorkspace(ctx)
|
||||||
|
environment := common.CurrentEnvironmentState(ctx, workspace)
|
||||||
|
|
||||||
lg = lg.With().
|
lg = lg.With().
|
||||||
Str("environment", environment.Name).
|
Str("environment", environment.Name).
|
||||||
|
@ -35,10 +35,11 @@ func init() {
|
|||||||
func updateEnvironmentInput(ctx context.Context, target string, input state.Input) {
|
func updateEnvironmentInput(ctx context.Context, target string, input state.Input) {
|
||||||
lg := log.Ctx(ctx)
|
lg := log.Ctx(ctx)
|
||||||
|
|
||||||
st := common.GetCurrentEnvironmentState(ctx)
|
workspace := common.CurrentWorkspace(ctx)
|
||||||
|
st := common.CurrentEnvironmentState(ctx, workspace)
|
||||||
st.SetInput(target, input)
|
st.SetInput(target, input)
|
||||||
|
|
||||||
if err := state.Save(ctx, st); err != nil {
|
if err := workspace.Save(ctx, st); err != nil {
|
||||||
lg.Fatal().Err(err).Str("environment", st.Name).Msg("cannot update environment")
|
lg.Fatal().Err(err).Str("environment", st.Name).Msg("cannot update environment")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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/state"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
@ -23,10 +22,11 @@ var unsetCmd = &cobra.Command{
|
|||||||
lg := logger.New()
|
lg := logger.New()
|
||||||
ctx := lg.WithContext(cmd.Context())
|
ctx := lg.WithContext(cmd.Context())
|
||||||
|
|
||||||
st := common.GetCurrentEnvironmentState(ctx)
|
workspace := common.CurrentWorkspace(ctx)
|
||||||
|
st := common.CurrentEnvironmentState(ctx, workspace)
|
||||||
st.RemoveInputs(args[0])
|
st.RemoveInputs(args[0])
|
||||||
|
|
||||||
if err := state.Save(ctx, st); err != nil {
|
if err := workspace.Save(ctx, st); err != nil {
|
||||||
lg.Fatal().Err(err).Str("environment", st.Name).Msg("cannot update environment")
|
lg.Fatal().Err(err).Str("environment", st.Name).Msg("cannot update environment")
|
||||||
}
|
}
|
||||||
lg.Info().Str("environment", st.Name).Msg("updated environment")
|
lg.Info().Str("environment", st.Name).Msg("updated environment")
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
@ -10,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/state"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
@ -32,21 +29,8 @@ var listCmd = &cobra.Command{
|
|||||||
lg := logger.New()
|
lg := logger.New()
|
||||||
ctx := lg.WithContext(cmd.Context())
|
ctx := lg.WithContext(cmd.Context())
|
||||||
|
|
||||||
var (
|
workspace := common.CurrentWorkspace(ctx)
|
||||||
workspace = viper.GetString("workspace")
|
environments, err := workspace.List(ctx)
|
||||||
err error
|
|
||||||
)
|
|
||||||
if workspace == "" {
|
|
||||||
workspace, err = state.CurrentWorkspace(ctx)
|
|
||||||
if err != nil {
|
|
||||||
lg.
|
|
||||||
Fatal().
|
|
||||||
Err(err).
|
|
||||||
Msg("failed to determine current workspace")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
environments, err := state.List(ctx, workspace)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lg.
|
lg.
|
||||||
Fatal().
|
Fatal().
|
||||||
@ -54,34 +38,15 @@ var listCmd = &cobra.Command{
|
|||||||
Msg("cannot list environments")
|
Msg("cannot list environments")
|
||||||
}
|
}
|
||||||
|
|
||||||
environmentPath := getCurrentEnvironmentPath(ctx)
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.TabIndent)
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.TabIndent)
|
||||||
defer w.Flush()
|
defer w.Flush()
|
||||||
for _, e := range environments {
|
for _, e := range environments {
|
||||||
line := fmt.Sprintf("%s\t%s\t", e.Name, formatPath(e.Path))
|
line := fmt.Sprintf("%s\t%s\t", e.Name, formatPath(e.Path))
|
||||||
if e.Path == environmentPath {
|
|
||||||
line = fmt.Sprintf("%s- active environment", line)
|
|
||||||
}
|
|
||||||
fmt.Fprintln(w, line)
|
fmt.Fprintln(w, line)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCurrentEnvironmentPath(ctx context.Context) string {
|
|
||||||
lg := log.Ctx(ctx)
|
|
||||||
|
|
||||||
st, err := state.Current(ctx)
|
|
||||||
if err != nil {
|
|
||||||
// Ignore error if not initialized
|
|
||||||
if errors.Is(err, state.ErrNotInit) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
lg.Fatal().Err(err).Msg("failed to load current environment")
|
|
||||||
}
|
|
||||||
|
|
||||||
return st.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatPath(p string) string {
|
func formatPath(p string) string {
|
||||||
usr, err := user.Current()
|
usr, err := user.Current()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
42
cmd/dagger/cmd/new.go
Normal file
42
cmd/dagger/cmd/new.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"dagger.io/go/cmd/dagger/cmd/common"
|
||||||
|
"dagger.io/go/cmd/dagger/logger"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
var newCmd = &cobra.Command{
|
||||||
|
Use: "new",
|
||||||
|
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())
|
||||||
|
|
||||||
|
workspace := common.CurrentWorkspace(ctx)
|
||||||
|
|
||||||
|
if viper.GetString("environment") != "" {
|
||||||
|
lg.
|
||||||
|
Fatal().
|
||||||
|
Msg("cannot use option -e,--environment for this command")
|
||||||
|
}
|
||||||
|
name := args[0]
|
||||||
|
if _, err := workspace.Create(ctx, name); err != nil {
|
||||||
|
lg.Fatal().Err(err).Msg("failed to create environment")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if err := viper.BindPFlags(newCmd.Flags()); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
@ -30,7 +30,8 @@ var queryCmd = &cobra.Command{
|
|||||||
|
|
||||||
cueOpts := parseQueryFlags()
|
cueOpts := parseQueryFlags()
|
||||||
|
|
||||||
state := common.GetCurrentEnvironmentState(ctx)
|
workspace := common.CurrentWorkspace(ctx)
|
||||||
|
state := common.CurrentEnvironmentState(ctx, workspace)
|
||||||
|
|
||||||
lg = lg.With().
|
lg = lg.With().
|
||||||
Str("environment", state.Name).
|
Str("environment", state.Name).
|
||||||
|
@ -34,6 +34,7 @@ func init() {
|
|||||||
|
|
||||||
rootCmd.AddCommand(
|
rootCmd.AddCommand(
|
||||||
initCmd,
|
initCmd,
|
||||||
|
newCmd,
|
||||||
computeCmd,
|
computeCmd,
|
||||||
listCmd,
|
listCmd,
|
||||||
queryCmd,
|
queryCmd,
|
||||||
|
@ -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/state"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@ -24,11 +23,12 @@ var upCmd = &cobra.Command{
|
|||||||
lg := logger.New()
|
lg := logger.New()
|
||||||
ctx := lg.WithContext(cmd.Context())
|
ctx := lg.WithContext(cmd.Context())
|
||||||
|
|
||||||
st := common.GetCurrentEnvironmentState(ctx)
|
workspace := common.CurrentWorkspace(ctx)
|
||||||
|
st := common.CurrentEnvironmentState(ctx, workspace)
|
||||||
result := common.EnvironmentUp(ctx, st, viper.GetBool("no-cache"))
|
result := common.EnvironmentUp(ctx, st, viper.GetBool("no-cache"))
|
||||||
|
|
||||||
st.Computed = result.Computed().JSON().PrettyString()
|
st.Computed = result.Computed().JSON().PrettyString()
|
||||||
if err := state.Save(ctx, st); err != nil {
|
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")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"cuelang.org/go/cue"
|
"cuelang.org/go/cue"
|
||||||
|
|
||||||
@ -90,7 +91,10 @@ func (dir dirInput) Compile(state *State) (*compiler.Value, error) {
|
|||||||
|
|
||||||
p := dir.Path
|
p := dir.Path
|
||||||
if !filepath.IsAbs(p) {
|
if !filepath.IsAbs(p) {
|
||||||
p = filepath.Clean(path.Join(state.Path, 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(
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
package state
|
package state
|
||||||
|
|
||||||
|
import "path"
|
||||||
|
|
||||||
// Contents of an environment serialized to a file
|
// Contents of an environment serialized to a file
|
||||||
type State struct {
|
type State struct {
|
||||||
// State path
|
// State path
|
||||||
Path string `yaml:"-"`
|
Path string `yaml:"-"`
|
||||||
|
|
||||||
|
// Workspace path
|
||||||
|
Workspace string `yaml:"-"`
|
||||||
|
|
||||||
// Human-friendly environment name.
|
// Human-friendly environment name.
|
||||||
// A environment may have more than one name.
|
// A environment may have more than one name.
|
||||||
// FIXME: store multiple names?
|
// FIXME: store multiple names?
|
||||||
@ -20,7 +25,7 @@ type State struct {
|
|||||||
// Cue module containing the environment plan
|
// Cue module containing the environment plan
|
||||||
// The input's top-level artifact is used as a module directory.
|
// The input's top-level artifact is used as a module directory.
|
||||||
func (s *State) PlanSource() Input {
|
func (s *State) PlanSource() Input {
|
||||||
return DirInput(s.Path, []string{"*.cue", "cue.mod"})
|
return DirInput(path.Join(s.Path, planDir), []string{"*.cue", "cue.mod"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) SetInput(key string, value Input) error {
|
func (s *State) SetInput(key string, value Input) error {
|
||||||
|
@ -7,26 +7,32 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"dagger.io/go/dagger/keychain"
|
"dagger.io/go/dagger/keychain"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrNotInit = errors.New("not initialized")
|
ErrNotInit = errors.New("not initialized")
|
||||||
ErrAlreadyInit = errors.New("already initialized")
|
ErrAlreadyInit = errors.New("already initialized")
|
||||||
ErrNoCurrentWorkspace = errors.New("not in a git directory")
|
ErrNotExist = errors.New("environment doesn't exist")
|
||||||
|
ErrExist = errors.New("environment already exists")
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
daggerDir = ".dagger"
|
daggerDir = ".dagger"
|
||||||
|
envDir = "env"
|
||||||
stateDir = "state"
|
stateDir = "state"
|
||||||
|
planDir = "plan"
|
||||||
manifestFile = "values.yaml"
|
manifestFile = "values.yaml"
|
||||||
computedFile = "computed.json"
|
computedFile = "computed.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init(ctx context.Context, dir, name string) (*State, error) {
|
type Workspace struct {
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(ctx context.Context, dir string) (*Workspace, error) {
|
||||||
root := path.Join(dir, daggerDir)
|
root := path.Join(dir, daggerDir)
|
||||||
if err := os.Mkdir(root, 0755); err != nil {
|
if err := os.Mkdir(root, 0755); err != nil {
|
||||||
if errors.Is(err, os.ErrExist) {
|
if errors.Is(err, os.ErrExist) {
|
||||||
@ -34,41 +40,31 @@ func Init(ctx context.Context, dir, name string) (*State, error) {
|
|||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
manifestPath := path.Join(dir, daggerDir, manifestFile)
|
return &Workspace{
|
||||||
|
Path: root,
|
||||||
st := &State{
|
}, nil
|
||||||
Path: dir,
|
|
||||||
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(root, ".gitignore"),
|
|
||||||
[]byte("# dagger state\nstate/**\n"),
|
|
||||||
0600,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return st, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Current(ctx context.Context) (*State, error) {
|
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()
|
current, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -90,36 +86,63 @@ func Current(ctx context.Context) (*State, error) {
|
|||||||
return nil, ErrNotInit
|
return nil, ErrNotInit
|
||||||
}
|
}
|
||||||
|
|
||||||
func Open(ctx context.Context, dir string) (*State, error) {
|
func (w *Workspace) envPath(name string) string {
|
||||||
_, err := os.Stat(path.Join(dir, daggerDir))
|
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 {
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, f := range files {
|
||||||
|
if !f.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
st, err := w.Get(ctx, f.Name())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
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) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
return nil, ErrNotInit
|
return nil, ErrNotExist
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
root, err := filepath.Abs(dir)
|
manifest, err := os.ReadFile(path.Join(envPath, manifestFile))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
manifest, err = keychain.Decrypt(ctx, manifest)
|
||||||
data, err := os.ReadFile(path.Join(root, daggerDir, manifestFile))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data, err = keychain.Decrypt(ctx, data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to decrypt state: %w", err)
|
return nil, fmt.Errorf("unable to decrypt state: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var st State
|
var st State
|
||||||
if err := yaml.Unmarshal(data, &st); err != nil {
|
if err := yaml.Unmarshal(manifest, &st); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
st.Path = root
|
st.Path = envPath
|
||||||
|
st.Workspace = w.Path
|
||||||
|
|
||||||
computed, err := os.ReadFile(path.Join(root, daggerDir, stateDir, computedFile))
|
computed, err := os.ReadFile(path.Join(envPath, stateDir, computedFile))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
st.Computed = string(computed)
|
st.Computed = string(computed)
|
||||||
}
|
}
|
||||||
@ -127,13 +150,13 @@ func Open(ctx context.Context, dir string) (*State, error) {
|
|||||||
return &st, nil
|
return &st, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Save(ctx context.Context, st *State) error {
|
func (w *Workspace) Save(ctx context.Context, st *State) error {
|
||||||
data, err := yaml.Marshal(st)
|
data, err := yaml.Marshal(st)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestPath := path.Join(st.Path, daggerDir, manifestFile)
|
manifestPath := path.Join(st.Path, manifestFile)
|
||||||
|
|
||||||
encrypted, err := keychain.Reencrypt(ctx, manifestPath, data)
|
encrypted, err := keychain.Reencrypt(ctx, manifestPath, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -144,7 +167,7 @@ func Save(ctx context.Context, st *State) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if st.Computed != "" {
|
if st.Computed != "" {
|
||||||
state := path.Join(st.Path, daggerDir, stateDir)
|
state := path.Join(st.Path, stateDir)
|
||||||
if err := os.MkdirAll(state, 0755); err != nil {
|
if err := os.MkdirAll(state, 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -160,74 +183,59 @@ func Save(ctx context.Context, st *State) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CurrentWorkspace(ctx context.Context) (string, error) {
|
func (w *Workspace) Create(ctx context.Context, name string) (*State, error) {
|
||||||
current, err := os.Getwd()
|
envPath, err := filepath.Abs(w.envPath(name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk every parent directory to find .dagger
|
// Environment directory
|
||||||
for {
|
if err := os.MkdirAll(envPath, 0755); err != nil {
|
||||||
_, err := os.Stat(path.Join(current, ".git"))
|
if errors.Is(err, os.ErrExist) {
|
||||||
if err == nil {
|
return nil, ErrExist
|
||||||
return current, nil
|
|
||||||
}
|
}
|
||||||
parent := filepath.Dir(current)
|
return nil, err
|
||||||
if parent == current {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
current = parent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", ErrNoCurrentWorkspace
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
func List(ctx context.Context, workspace string) ([]*State, error) {
|
manifestPath := path.Join(envPath, manifestFile)
|
||||||
var (
|
|
||||||
environments = []*State{}
|
st := &State{
|
||||||
err error
|
Path: envPath,
|
||||||
|
Workspace: w.Path,
|
||||||
|
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,
|
||||||
)
|
)
|
||||||
|
|
||||||
workspace, err = filepath.Abs(workspace)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = filepath.WalkDir(workspace, func(p string, info os.DirEntry, err error) error {
|
return st, nil
|
||||||
// Ignore errors while we walk
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip regular files
|
|
||||||
if !info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip non-dagger directories
|
|
||||||
if info.Name() != daggerDir {
|
|
||||||
// Caveat: limit traversal to a depth of 10 (arbitrary)
|
|
||||||
relPath := strings.TrimPrefix(p, workspace)
|
|
||||||
if strings.Count(relPath, string(os.PathSeparator)) > 10 {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, continue traversing
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
st, err := Open(ctx, filepath.Dir(p))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
environments = append(environments, st)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return environments, nil
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user