diff --git a/cmd/dagger/cmd/init.go b/cmd/dagger/cmd/init.go index ca6a4f84..f16975ba 100644 --- a/cmd/dagger/cmd/init.go +++ b/cmd/dagger/cmd/init.go @@ -36,12 +36,10 @@ var initCmd = &cobra.Command{ dir = cwd } - ws, err := state.Init(ctx, dir) + _, err := state.Init(ctx, dir) if err != nil { lg.Fatal().Err(err).Msg("failed to initialize workspace") } - - lg.Info().Str("path", ws.DaggerDir()).Msg("initialized new empty workspace") }, } diff --git a/cmd/dagger/cmd/list.go b/cmd/dagger/cmd/list.go index ea29b2a5..e19589cf 100644 --- a/cmd/dagger/cmd/list.go +++ b/cmd/dagger/cmd/list.go @@ -3,11 +3,11 @@ package cmd import ( "fmt" "os" - "os/user" "path" "strings" "text/tabwriter" + "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" "go.dagger.io/dagger/cmd/dagger/cmd/common" @@ -48,14 +48,12 @@ var listCmd = &cobra.Command{ } func formatPath(p string) string { - usr, err := user.Current() + dir, err := homedir.Dir() if err != nil { // Ignore error return p } - dir := usr.HomeDir - if strings.HasPrefix(p, dir) { return path.Join("~", p[len(dir):]) } diff --git a/cmd/dagger/cmd/new.go b/cmd/dagger/cmd/new.go index ae065ab8..70ece746 100644 --- a/cmd/dagger/cmd/new.go +++ b/cmd/dagger/cmd/new.go @@ -1,10 +1,6 @@ package cmd import ( - "fmt" - "path/filepath" - "strings" - "github.com/spf13/cobra" "github.com/spf13/viper" "go.dagger.io/dagger/cmd/dagger/cmd/common" @@ -36,41 +32,16 @@ var newCmd = &cobra.Command{ } name := args[0] - module := viper.GetString("module") - if module != "" { - p, err := filepath.Abs(module) - if err != nil { - lg.Fatal().Err(err).Str("path", module).Msg("unable to resolve path") - } - - if !strings.HasPrefix(p, workspace.Path) { - lg.Fatal().Err(err).Str("path", module).Msg("module is outside the workspace") - } - p, err = filepath.Rel(workspace.Path, p) - if err != nil { - lg.Fatal().Err(err).Str("path", module).Msg("unable to resolve path") - } - if !strings.HasPrefix(p, ".") { - p = "./" + p - } - module = p - } - - ws, err := workspace.Create(ctx, name, state.CreateOpts{ - Module: module, + _, err := workspace.Create(ctx, name, state.Plan{ Package: viper.GetString("package"), }) if err != nil { lg.Fatal().Err(err).Msg("failed to create environment") } - - lg.Info().Str("name", name).Msg("created new empty environment") - lg.Info().Str("name", name).Msg(fmt.Sprintf("to add code to the plan, copy or create cue files under: %s", ws.Plan.Module)) }, } func init() { - newCmd.Flags().StringP("module", "m", "", "references the local path of the cue module to use as a plan, relative to the workspace root") newCmd.Flags().StringP("package", "p", "", "references the name of the Cue package within the module to use as a plan. Default: defer to cue loader") if err := viper.BindPFlags(newCmd.Flags()); err != nil { panic(err) diff --git a/environment/environment.go b/environment/environment.go index 59dc671c..34ef1fc2 100644 --- a/environment/environment.go +++ b/environment/environment.go @@ -82,7 +82,7 @@ func (e *Environment) LoadPlan(ctx context.Context, s solver.Solver) error { span, ctx := opentracing.StartSpanFromContext(ctx, "environment.LoadPlan") defer span.Finish() - planSource, err := e.state.Plan.Source().Compile("", e.state) + planSource, err := e.state.Source().Compile("", e.state) if err != nil { return err } @@ -157,7 +157,7 @@ func (e *Environment) LocalDirs() map[string]string { } // 2. Scan the plan - plan, err := e.state.Plan.Source().Compile("", e.state) + plan, err := e.state.Source().Compile("", e.state) if err != nil { panic(err) } diff --git a/state/state.go b/state/state.go index 67637322..b8c7a87a 100644 --- a/state/state.go +++ b/state/state.go @@ -9,7 +9,7 @@ type State struct { Workspace string `yaml:"-"` // Plan - Plan Plan `yaml:"plan"` + Plan Plan `yaml:"plan,omitempty"` // Human-friendly environment name. // A environment may have more than one name. @@ -23,17 +23,21 @@ type State struct { Computed string `yaml:"-"` } +// Cue module containing the environment plan +func (s *State) Source() Input { + w := s.Workspace + // FIXME: backward compatibility + if mod := s.Plan.Module; mod != "" { + w = mod + } + return DirInput(w, []string{}, []string{}) +} + type Plan struct { Module string `yaml:"module,omitempty"` Package string `yaml:"package,omitempty"` } -// Cue module containing the environment plan -// The input's top-level artifact is used as a module directory. -func (p *Plan) Source() Input { - return DirInput(p.Module, []string{}, []string{}) -} - func (s *State) SetInput(key string, value Input) error { if s.Inputs == nil { s.Inputs = make(map[string]Input) diff --git a/state/workspace.go b/state/workspace.go index 3e71ceb0..dc7c4993 100644 --- a/state/workspace.go +++ b/state/workspace.go @@ -8,6 +8,7 @@ import ( "os" "path" "path/filepath" + "strings" "github.com/rs/zerolog/log" "go.dagger.io/dagger/keychain" @@ -161,18 +162,16 @@ func (w *Workspace) Get(ctx context.Context, name string) (*State, error) { return nil, err } st.Path = envPath - // Backward compat: if no plan module has been provided, - // use `.dagger/env//plan` + // FIXME: Backward compat: Support for old-style `.dagger/env//plan` if st.Plan.Module == "" { planPath := path.Join(envPath, planDir) - if _, err := os.Stat(planPath); err != nil { - return nil, fmt.Errorf("missing plan information for %q", name) + if _, err := os.Stat(planPath); err == nil { + planRelPath, err := filepath.Rel(w.Path, planPath) + if err != nil { + return nil, err + } + st.Plan.Module = planRelPath } - planRelPath, err := filepath.Rel(w.Path, planPath) - if err != nil { - return nil, err - } - st.Plan.Module = planRelPath } st.Workspace = w.Path @@ -229,12 +228,16 @@ func (w *Workspace) Save(ctx context.Context, st *State) error { return nil } -type CreateOpts struct { - Module string - Package string -} +func (w *Workspace) Create(ctx context.Context, name string, plan Plan) (*State, error) { + if _, err := w.Get(ctx, name); err == nil { + return nil, ErrExist + } + + pkg, err := w.cleanPackageName(ctx, plan.Package) + if err != nil { + return nil, err + } -func (w *Workspace) Create(ctx context.Context, name string, opts CreateOpts) (*State, error) { envPath, err := filepath.Abs(w.envPath(name)) if err != nil { return nil, err @@ -242,36 +245,16 @@ func (w *Workspace) Create(ctx context.Context, name string, opts CreateOpts) (* // Environment directory if err := os.MkdirAll(envPath, 0755); err != nil { - if errors.Is(err, os.ErrExist) { - return nil, ErrExist - } return nil, err } manifestPath := path.Join(envPath, manifestFile) - // Backward compat: if no plan module has been provided, - // use `.dagger/env//plan` - module := opts.Module - if module == "" { - planPath := path.Join(envPath, planDir) - if err := os.Mkdir(planPath, 0755); err != nil { - return nil, err - } - - planRelPath, err := filepath.Rel(w.Path, planPath) - if err != nil { - return nil, err - } - module = planRelPath - } - st := &State{ Path: envPath, Workspace: w.Path, Plan: Plan{ - Module: module, - Package: opts.Package, + Package: pkg, }, Name: name, } @@ -304,6 +287,51 @@ func (w *Workspace) Create(ctx context.Context, name string, opts CreateOpts) (* return st, nil } -func (w *Workspace) DaggerDir() string { - return path.Join(w.Path, daggerDir) +func (w *Workspace) cleanPackageName(ctx context.Context, pkg string) (string, error) { + lg := log. + Ctx(ctx). + With(). + Str("package", pkg). + Logger() + + if pkg == "" { + return pkg, nil + } + + // If the package is not a path, then it must be a domain (e.g. foo.bar/mypackage) + if _, err := os.Stat(pkg); err != nil { + if !errors.Is(err, os.ErrNotExist) { + return "", err + } + + // Make sure the domain is in the correct form + if !strings.Contains(pkg, ".") || !strings.Contains(pkg, "/") { + return "", fmt.Errorf("invalid package %q", pkg) + } + + return pkg, nil + } + + p, err := filepath.Abs(pkg) + if err != nil { + lg.Error().Err(err).Msg("unable to resolve path") + return "", err + } + + if !strings.HasPrefix(p, w.Path) { + lg.Fatal().Err(err).Msg("package is outside the workspace") + return "", err + } + + p, err = filepath.Rel(w.Path, p) + if err != nil { + lg.Fatal().Err(err).Msg("unable to resolve path") + return "", err + } + + if !strings.HasPrefix(p, ".") { + p = "./" + p + } + + return p, nil } diff --git a/state/workspace_test.go b/state/workspace_test.go index bd2f7a13..28b86810 100644 --- a/state/workspace_test.go +++ b/state/workspace_test.go @@ -30,7 +30,9 @@ func TestWorkspace(t *testing.T) { require.Equal(t, root, workspace.Path) // Create - st, err := workspace.Create(ctx, "test", CreateOpts{}) + st, err := workspace.Create(ctx, "test", Plan{ + Module: ".", + }) require.NoError(t, err) require.Equal(t, "test", st.Name) @@ -78,7 +80,9 @@ func TestEncryption(t *testing.T) { workspace, err := Init(ctx, root) require.NoError(t, err) - _, err = workspace.Create(ctx, "test", CreateOpts{}) + _, err = workspace.Create(ctx, "test", Plan{ + Module: ".", + }) require.NoError(t, err) // Set a plaintext input, make sure it is not encrypted diff --git a/tests/cli.bats b/tests/cli.bats index 044377fa..b696c8ac 100644 --- a/tests/cli.bats +++ b/tests/cli.bats @@ -42,10 +42,10 @@ setup() { @test "dagger new: modules" { "$DAGGER" init - ln -s "$TESTDIR"/cli/input/simple "$DAGGER_WORKSPACE"/plan + cp -a "$TESTDIR"/cli/input/simple/* "$DAGGER_WORKSPACE" - "$DAGGER" new "a" --module "$DAGGER_WORKSPACE"/plan - "$DAGGER" new "b" --module "$DAGGER_WORKSPACE"/plan + "$DAGGER" new "a" + "$DAGGER" new "b" "$DAGGER" input -e "a" text "input" "a" "$DAGGER" input -e "b" text "input" "b" @@ -60,6 +60,9 @@ setup() { run "$DAGGER" query -l error -e "b" input -f text assert_success assert_output "b" + + # run ls -la "$DAGGER_WORKSPACE" + # assert_failure } # create different environments from the same module, @@ -67,10 +70,10 @@ setup() { @test "dagger new: packages" { "$DAGGER" init - ln -s "$TESTDIR"/cli/packages "$DAGGER_WORKSPACE"/plan + cp -a "$TESTDIR"/cli/packages/* "$DAGGER_WORKSPACE" - "$DAGGER" new "a" --module "$DAGGER_WORKSPACE"/plan --package alpha.dagger.io/test/a - "$DAGGER" new "b" --module "$DAGGER_WORKSPACE"/plan --package alpha.dagger.io/test/b + "$DAGGER" new "a" --package alpha.dagger.io/test/a + "$DAGGER" new "b" --package alpha.dagger.io/test/b "$DAGGER" up -e "a" "$DAGGER" up -e "b" diff --git a/tests/helpers.bash b/tests/helpers.bash index e4f231c9..9bbd96e0 100644 --- a/tests/helpers.bash +++ b/tests/helpers.bash @@ -20,11 +20,10 @@ common_setup() { dagger_new_with_plan() { local name="$1" local sourcePlan="$2" - local targetPlan="$DAGGER_WORKSPACE"/"$name" - ln -s "$sourcePlan" "$targetPlan" - "$DAGGER" new "$name" --module "$targetPlan" + cp -a "$sourcePlan"/* "$DAGGER_WORKSPACE" + "$DAGGER" new "$name" } # dagger helper to execute the right binary