Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion devbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type Devbox interface {
PrintEnv(ctx context.Context, includeHooks bool) (string, error)
PrintGlobalList() error
PrintEnvrcContent(w io.Writer) error
PullGlobal(ctx context.Context, overwrite bool, path string) error
Pull(ctx context.Context, overwrite bool, path string) error
// Remove removes Nix packages from the config so that it no longer exists in
// the devbox environment.
Remove(ctx context.Context, pkgs ...string) error
Expand Down
63 changes: 1 addition & 62 deletions internal/boxcli/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,13 @@ package boxcli

import (
"fmt"
"io/fs"

"github.com/AlecAivazis/survey/v2"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"go.jetpack.io/devbox"
"go.jetpack.io/devbox/internal/ux"
)

type globalPullCmdFlags struct {
force bool
}

func globalCmd() *cobra.Command {

globalCmd := &cobra.Command{}
Expand All @@ -31,6 +25,7 @@ func globalCmd() *cobra.Command {

addCommandAndHideConfigFlag(globalCmd, addCmd())
addCommandAndHideConfigFlag(globalCmd, installCmd())
addCommandAndHideConfigFlag(globalCmd, pullCmd())
addCommandAndHideConfigFlag(globalCmd, removeCmd())
addCommandAndHideConfigFlag(globalCmd, runCmd())
addCommandAndHideConfigFlag(globalCmd, servicesCmd())
Expand All @@ -39,7 +34,6 @@ func globalCmd() *cobra.Command {

// Create list for non-global? Mike: I want it :)
globalCmd.AddCommand(globalListCmd())
globalCmd.AddCommand(globalPullCmd())

return globalCmd
}
Expand All @@ -59,27 +53,6 @@ func globalListCmd() *cobra.Command {
}
}

func globalPullCmd() *cobra.Command {
flags := globalPullCmdFlags{}
cmd := &cobra.Command{
Use: "pull <file> | <url>",
Short: "Pull a global config from a file or URL",
Long: "Pull a global config from a file or URL. URLs must be prefixed with 'http://' or 'https://'.",
PreRunE: ensureNixInstalled,
RunE: func(cmd *cobra.Command, args []string) error {
return pullGlobalCmdFunc(cmd, args, flags.force)
},
Args: cobra.ExactArgs(1),
}

cmd.Flags().BoolVarP(
&flags.force, "force", "f", false,
"Force overwrite of existing global config files",
)

return cmd
}

func listGlobalCmdFunc(cmd *cobra.Command, args []string) error {
path, err := ensureGlobalConfig(cmd)
if err != nil {
Expand All @@ -93,40 +66,6 @@ func listGlobalCmdFunc(cmd *cobra.Command, args []string) error {
return box.PrintGlobalList()
}

func pullGlobalCmdFunc(
cmd *cobra.Command,
args []string,
overwrite bool,
) error {
path, err := ensureGlobalConfig(cmd)
if err != nil {
return errors.WithStack(err)
}

box, err := devbox.Open(path, cmd.ErrOrStderr())
if err != nil {
return errors.WithStack(err)
}
err = box.PullGlobal(cmd.Context(), overwrite, args[0])
if errors.Is(err, fs.ErrExist) {
prompt := &survey.Confirm{
Message: "File(s) already exists. Overwrite?",
}
if err = survey.AskOne(prompt, &overwrite); err != nil {
return errors.WithStack(err)
}
if !overwrite {
return nil
}
err = box.PullGlobal(cmd.Context(), overwrite, args[0])
}
if err != nil {
return err
}

return installCmdFunc(cmd, runCmdFlags{config: configFlags{path: path}})
}

var globalConfigPath string

func ensureGlobalConfig(cmd *cobra.Command) (string, error) {
Expand Down
95 changes: 95 additions & 0 deletions internal/boxcli/pull.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2023 Jetpack Technologies Inc and contributors. All rights reserved.
// Use of this source code is governed by the license in the LICENSE file.

package boxcli

import (
"io/fs"
"os"
"path/filepath"

"github.com/AlecAivazis/survey/v2"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"go.jetpack.io/devbox"
)

type pullCmdFlags struct {
config configFlags
force bool
}

func pullCmd() *cobra.Command {
flags := pullCmdFlags{}
cmd := &cobra.Command{
Use: "pull <file> | <url>",
Short: "Pull a config from a file or URL",
Long: "Pull a config from a file or URL. URLs must be prefixed with 'http://' or 'https://'.",
PreRunE: ensureNixInstalled,
RunE: func(cmd *cobra.Command, args []string) error {
return pullCmdFunc(cmd, args[0], &flags)
},
Args: cobra.ExactArgs(1),
}

cmd.Flags().BoolVarP(
&flags.force, "force", "f", false,
"Force overwrite of existing [global] config files",
)

flags.config.register(cmd)

return cmd
}

func pullCmdFunc(
cmd *cobra.Command,
url string,
flags *pullCmdFlags,
) error {
box, err := devbox.Open(flags.config.path, cmd.ErrOrStderr())
if err != nil {
return errors.WithStack(err)
}

pullPath, err := absolutizeIfLocal(url)
if err != nil {
return errors.WithStack(err)
}

err = box.Pull(cmd.Context(), flags.force, pullPath)
if prompt := pullErrorPrompt(err); prompt != "" {
prompt := &survey.Confirm{Message: prompt}
if err = survey.AskOne(prompt, &flags.force); err != nil {
return errors.WithStack(err)
}
if !flags.force {
return nil
}
err = box.Pull(cmd.Context(), flags.force, pullPath)
}
if err != nil {
return err
}

return installCmdFunc(
cmd,
runCmdFlags{config: configFlags{path: flags.config.path}},
)
}

func pullErrorPrompt(err error) string {
switch {
case errors.Is(err, fs.ErrExist):
return "Global profile already exists. Overwrite?"
default:
return ""
}
}

func absolutizeIfLocal(path string) (string, error) {
if _, err := os.Stat(path); err == nil {
return filepath.Abs(path)
}
return path, nil
}
7 changes: 3 additions & 4 deletions internal/devconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package devconfig
import (
"io"
"net/http"
"net/url"
"path/filepath"
"regexp"
"strings"
Expand Down Expand Up @@ -127,8 +126,8 @@ func Load(path string) (*Config, error) {
return cfg, validateConfig(cfg)
}

func LoadConfigFromURL(url *url.URL) (*Config, error) {
res, err := http.Get(url.String())
func LoadConfigFromURL(url string) (*Config, error) {
res, err := http.Get(url)
if err != nil {
return nil, errors.WithStack(err)
}
Expand All @@ -138,7 +137,7 @@ func LoadConfigFromURL(url *url.URL) (*Config, error) {
if err != nil {
return nil, errors.WithStack(err)
}
ext := filepath.Ext(url.Path)
ext := filepath.Ext(url)
if !cuecfg.IsSupportedExtension(ext) {
ext = ".json"
}
Expand Down
65 changes: 3 additions & 62 deletions internal/impl/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,25 @@ import (
"context"
"fmt"
"io/fs"
"net/url"
"os"
"path/filepath"
"strings"

"github.com/pkg/errors"
"github.com/samber/lo"

"go.jetpack.io/devbox/internal/devconfig"
"go.jetpack.io/devbox/internal/pullbox"
"go.jetpack.io/devbox/internal/xdg"
)

// In the future we will support multiple global profiles
const currentGlobalProfile = "default"

func (d *Devbox) PullGlobal(
func (d *Devbox) Pull(
ctx context.Context,
force bool,
path string,
) error {
u, err := url.Parse(path)
if err == nil && u.Scheme != "" {
return d.pullGlobalFromURL(ctx, force, u)
}
return d.pullGlobalFromPath(ctx, path)
fmt.Fprintf(d.writer, "Pulling global config from %s\n", path)
return pullbox.New(d, path, force).Pull()
}

func (d *Devbox) PrintGlobalList() error {
Expand All @@ -42,58 +35,6 @@ func (d *Devbox) PrintGlobalList() error {
return nil
}

func (d *Devbox) pullGlobalFromURL(
ctx context.Context,
overwrite bool,
configURL *url.URL,
) error {
fmt.Fprintf(d.writer, "Pulling global config from %s\n", configURL)
puller := pullbox.New()
if ok, err := puller.URLIsArchive(configURL.String()); ok {
fmt.Fprintf(
d.writer,
"%s is an archive, extracting to %s\n",
configURL,
d.ProjectDir(),
)
return puller.DownloadAndExtract(
overwrite,
configURL.String(),
d.projectDir,
)
} else if err != nil {
return err
}
cfg, err := devconfig.LoadConfigFromURL(configURL)
if err != nil {
return err
}
return d.addFromPull(ctx, cfg)
}

func (d *Devbox) pullGlobalFromPath(ctx context.Context, path string) error {
fmt.Fprintf(d.writer, "Pulling global config from %s\n", path)
cfg, err := devconfig.Load(path)
if err != nil {
return err
}
return d.addFromPull(ctx, cfg)
}

func (d *Devbox) addFromPull(ctx context.Context, cfg *devconfig.Config) error {
diff, _ := lo.Difference(cfg.Packages, d.cfg.Packages)
if len(diff) == 0 {
fmt.Fprint(d.writer, "No new packages to install\n")
return nil
}
fmt.Fprintf(
d.writer,
"Installing the following packages: %s\n",
strings.Join(diff, ", "),
)
return d.Add(ctx, diff...)
}

func GlobalDataPath() (string, error) {
path := xdg.DataSubpath(filepath.Join("devbox/global", currentGlobalProfile))
if err := os.MkdirAll(path, 0755); err != nil {
Expand Down
50 changes: 50 additions & 0 deletions internal/pullbox/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2023 Jetpack Technologies Inc and contributors. All rights reserved.
// Use of this source code is governed by the license in the LICENSE file.

package pullbox

import (
"net/url"
"os"
"path/filepath"

"github.com/pkg/errors"
"go.jetpack.io/devbox/internal/cuecfg"
"go.jetpack.io/devbox/internal/devconfig"
)

func (p *pullbox) IsTextDevboxConfig() bool {
if u, err := url.Parse(p.url); err == nil {
ext := filepath.Ext(u.Path)
return cuecfg.IsSupportedExtension(ext)
}
// For invalid URLS, just look at the extension
ext := filepath.Ext(p.url)
return cuecfg.IsSupportedExtension(ext)
}

func (p *pullbox) pullTextDevboxConfig() error {
if p.isLocalConfig() {
return p.copy(p.overwrite, p.url, p.ProjectDir())
}

cfg, err := devconfig.LoadConfigFromURL(p.url)
if err != nil {
return err
}

tmpDir, err := os.MkdirTemp("", "devbox")
if err != nil {
return errors.WithStack(err)
}
if err = cfg.SaveTo(tmpDir); err != nil {
return err
}

return p.copy(p.overwrite, tmpDir, p.ProjectDir())
}

func (p *pullbox) isLocalConfig() bool {
_, err := os.Stat(p.url)
return err == nil
}
Loading