Skip to content

Commit 8fa9bc1

Browse files
authored
[refactor] Move config to own package. Don't warn if overwriting defa… (#1045)
## Summary Stacked on #1043 This is mostly a straight refactor. Just moved everything to new package, renamed a few methods/constants if it made sense. Only added functionality is `isModifiedConfig` in pullbox that doesn't return error if config that is being overwritten is a default config. (we need this because configs are created automatically so we always get the prompt even if it's empty) ## How was it tested? Ran `devbox global pull https://fleekgen.fly.dev/high` with global directory: * Empty * Only with unmodified config * Only with modified config * With other files.
1 parent 595ba9c commit 8fa9bc1

File tree

11 files changed

+228
-155
lines changed

11 files changed

+228
-155
lines changed

devbox.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"context"
99
"io"
1010

11+
"go.jetpack.io/devbox/internal/devconfig"
1112
"go.jetpack.io/devbox/internal/impl"
1213
"go.jetpack.io/devbox/internal/planner/plansdk"
1314
"go.jetpack.io/devbox/internal/services"
@@ -19,7 +20,7 @@ type Devbox interface {
1920
// environment. It validates that the Nix packages exist, and install them.
2021
// Adding duplicate packages is a no-op.
2122
Add(ctx context.Context, pkgs ...string) error
22-
Config() *impl.Config
23+
Config() *devconfig.Config
2324
ProjectDir() string
2425
// Generate creates the directory of Nix files and the Dockerfile that define
2526
// the devbox environment.
@@ -60,7 +61,7 @@ func Open(dir string, writer io.Writer) (Devbox, error) {
6061

6162
// InitConfig creates a default devbox config file if one doesn't already exist.
6263
func InitConfig(dir string, writer io.Writer) (bool, error) {
63-
return impl.InitConfig(dir, writer)
64+
return devconfig.Init(dir, writer)
6465
}
6566

6667
func GlobalDataPath() (string, error) {

internal/boxcli/global.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,10 @@ func pullGlobalCmdFunc(
112112
if err = survey.AskOne(prompt, &overwrite); err != nil {
113113
return errors.WithStack(err)
114114
}
115-
if overwrite {
116-
err = box.PullGlobal(cmd.Context(), overwrite, args[0])
115+
if !overwrite {
116+
return nil
117117
}
118+
err = box.PullGlobal(cmd.Context(), overwrite, args[0])
118119
}
119120
if err != nil {
120121
return err

internal/impl/config.go renamed to internal/devconfig/config.go

Lines changed: 30 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
// Copyright 2023 Jetpack Technologies Inc and contributors. All rights reserved.
22
// Use of this source code is governed by the license in the LICENSE file.
33

4-
package impl
4+
package devconfig
55

66
import (
77
"io"
88
"net/http"
99
"net/url"
10-
"os"
1110
"path/filepath"
1211
"regexp"
1312
"strings"
@@ -16,12 +15,12 @@ import (
1615

1716
"go.jetpack.io/devbox/internal/boxcli/usererr"
1817
"go.jetpack.io/devbox/internal/cuecfg"
19-
"go.jetpack.io/devbox/internal/debug"
20-
"go.jetpack.io/devbox/internal/fileutil"
2118
"go.jetpack.io/devbox/internal/impl/shellcmd"
2219
"go.jetpack.io/devbox/internal/planner/plansdk"
2320
)
2421

22+
const DefaultName = "devbox.json"
23+
2524
// Config defines a devbox environment as JSON.
2625
type Config struct {
2726
// Packages is the slice of Nix packages that devbox makes available in
@@ -59,8 +58,9 @@ type Stage struct {
5958
Command string `cue:"string" json:"command"`
6059
}
6160

62-
func defaultConfig() *Config {
61+
func DefaultConfig() *Config {
6362
return &Config{
63+
Packages: []string{}, // initialize to empty slice instead of nil for consistent marshalling
6464
Shell: &shellConfig{
6565
Scripts: map[string]*shellcmd.Commands{
6666
"test": {
@@ -80,6 +80,12 @@ func (c *Config) Hash() (string, error) {
8080
return cuecfg.Hash(c)
8181
}
8282

83+
func (c *Config) Equals(other *Config) bool {
84+
hash1, _ := c.Hash()
85+
hash2, _ := other.Hash()
86+
return hash1 == hash2
87+
}
88+
8389
func (c *Config) NixPkgsCommitHash() string {
8490
if c == nil || c.Nixpkgs == nil || c.Nixpkgs.Commit == "" {
8591
return plansdk.DefaultNixpkgsCommit
@@ -101,21 +107,27 @@ func (c *Config) InitHook() *shellcmd.Commands {
101107
return c.Shell.InitHook
102108
}
103109

110+
// SaveTo writes the config to a file.
111+
func (c *Config) SaveTo(path string) error {
112+
cfgPath := filepath.Join(path, DefaultName)
113+
return cuecfg.WriteFile(cfgPath, c)
114+
}
115+
104116
func readConfig(path string) (*Config, error) {
105117
cfg := &Config{}
106118
return cfg, errors.WithStack(cuecfg.ParseFile(path, cfg))
107119
}
108120

109-
// ReadConfig reads a devbox config file, and validates it.
110-
func ReadConfig(path string) (*Config, error) {
121+
// Load reads a devbox config file, and validates it.
122+
func Load(path string) (*Config, error) {
111123
cfg, err := readConfig(path)
112124
if err != nil {
113125
return nil, err
114126
}
115127
return cfg, validateConfig(cfg)
116128
}
117129

118-
func readConfigFromURL(url *url.URL) (*Config, error) {
130+
func LoadConfigFromURL(url *url.URL) (*Config, error) {
119131
res, err := http.Get(url.String())
120132
if err != nil {
121133
return nil, errors.WithStack(err)
@@ -130,7 +142,10 @@ func readConfigFromURL(url *url.URL) (*Config, error) {
130142
if !cuecfg.IsSupportedExtension(ext) {
131143
ext = ".json"
132144
}
133-
return cfg, cuecfg.Unmarshal(data, ext, cfg)
145+
if err = cuecfg.Unmarshal(data, ext, cfg); err != nil {
146+
return nil, errors.WithStack(err)
147+
}
148+
return cfg, validateConfig(cfg)
134149
}
135150

136151
// WriteConfig saves a devbox config file.
@@ -142,96 +157,9 @@ func WriteConfig(path string, cfg *Config) error {
142157
return cuecfg.WriteFile(path, cfg)
143158
}
144159

145-
// findProjectDir walks up the directory tree looking for a devbox.json
146-
// and upon finding it, will return the directory-path.
147-
//
148-
// If it doesn't find any devbox.json, then an error is returned.
149-
func findProjectDir(path string) (string, error) {
150-
debug.Log("findProjectDir: path is %s\n", path)
151-
152-
// Sanitize the directory and use the absolute path as canonical form
153-
absPath, err := filepath.Abs(path)
154-
if err != nil {
155-
return "", errors.WithStack(err)
156-
}
157-
158-
// If the path is specified, then we check directly for a config.
159-
// Otherwise, we search the parent directories.
160-
if path != "" {
161-
return findProjectDirAtPath(absPath)
162-
}
163-
return findProjectDirFromParentDirSearch("/" /*root*/, absPath)
164-
}
165-
166-
func findProjectDirAtPath(absPath string) (string, error) {
167-
fi, err := os.Stat(absPath)
168-
if err != nil {
169-
return "", err
170-
}
171-
172-
switch mode := fi.Mode(); {
173-
case mode.IsDir():
174-
if !fileutil.Exists(filepath.Join(absPath, configFilename)) {
175-
return "", missingConfigError(absPath, false /*didCheckParents*/)
176-
}
177-
return absPath, nil
178-
default: // assumes 'file' i.e. mode.IsRegular()
179-
if !fileutil.Exists(filepath.Clean(absPath)) {
180-
return "", missingConfigError(absPath, false /*didCheckParents*/)
181-
}
182-
// we return a directory from this function
183-
return filepath.Dir(absPath), nil
184-
}
185-
}
186-
187-
func findProjectDirFromParentDirSearch(root string, absPath string) (string, error) {
188-
cur := absPath
189-
// Search parent directories for a devbox.json
190-
for cur != root {
191-
debug.Log("finding %s in dir: %s\n", configFilename, cur)
192-
if fileutil.Exists(filepath.Join(cur, configFilename)) {
193-
return cur, nil
194-
}
195-
cur = filepath.Dir(cur)
196-
}
197-
if fileutil.Exists(filepath.Join(cur, configFilename)) {
198-
return cur, nil
199-
}
200-
return "", missingConfigError(absPath, true /*didCheckParents*/)
201-
}
202-
203-
func missingConfigError(path string, didCheckParents bool) error {
204-
var workingDir string
205-
wd, err := os.Getwd()
206-
if err == nil {
207-
workingDir = wd
208-
}
209-
// We try to prettify the `path` before printing
210-
if path == "." || path == "" || workingDir == path {
211-
path = "this directory"
212-
} else {
213-
// Instead of a long absolute directory, print the relative directory
214-
215-
// if an error occurs, then just use `path`
216-
if workingDir != "" {
217-
relDir, err := filepath.Rel(workingDir, path)
218-
if err == nil {
219-
path = relDir
220-
}
221-
}
222-
}
223-
224-
parentDirCheckAddendum := ""
225-
if didCheckParents {
226-
parentDirCheckAddendum = ", or any parent directories"
227-
}
228-
229-
return usererr.New("No devbox.json found in %s%s. Did you run `devbox init` yet?", path, parentDirCheckAddendum)
230-
}
231-
232160
func validateConfig(cfg *Config) error {
233161
fns := []func(cfg *Config) error{
234-
validateNixpkg,
162+
ValidateNixpkg,
235163
validateScripts,
236164
}
237165

@@ -252,16 +180,18 @@ func validateScripts(cfg *Config) error {
252180
return errors.New("cannot have script with empty name in devbox.json")
253181
}
254182
if whitespace.MatchString(k) {
255-
return errors.Errorf("cannot have script name with whitespace in devbox.json: %s", k)
183+
return errors.Errorf(
184+
"cannot have script name with whitespace in devbox.json: %s", k)
256185
}
257186
if strings.TrimSpace(scripts[k].String()) == "" {
258-
return errors.Errorf("cannot have an empty script body in devbox.json: %s", k)
187+
return errors.Errorf(
188+
"cannot have an empty script body in devbox.json: %s", k)
259189
}
260190
}
261191
return nil
262192
}
263193

264-
func validateNixpkg(cfg *Config) error {
194+
func ValidateNixpkg(cfg *Config) error {
265195
hash := cfg.NixPkgsCommitHash()
266196
if hash == "" {
267197
return nil

internal/devconfig/init.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2023 Jetpack Technologies Inc and contributors. All rights reserved.
2+
// Use of this source code is governed by the license in the LICENSE file.
3+
4+
package devconfig
5+
6+
import (
7+
"fmt"
8+
"io"
9+
"path/filepath"
10+
"strings"
11+
12+
"github.com/fatih/color"
13+
"go.jetpack.io/devbox/internal/cuecfg"
14+
"go.jetpack.io/devbox/internal/initrec"
15+
)
16+
17+
func Init(dir string, writer io.Writer) (created bool, err error) {
18+
cfgPath := filepath.Join(dir, DefaultName)
19+
20+
config := DefaultConfig()
21+
22+
// package suggestion
23+
pkgsToSuggest, err := initrec.Get(dir)
24+
if err != nil {
25+
return false, err
26+
}
27+
if len(pkgsToSuggest) > 0 {
28+
s := fmt.Sprintf("devbox add %s", strings.Join(pkgsToSuggest, " "))
29+
fmt.Fprintf(
30+
writer,
31+
"We detected extra packages you may need. To install them, run `%s`\n",
32+
color.HiYellowString(s),
33+
)
34+
}
35+
36+
return cuecfg.InitFile(cfgPath, config)
37+
}

internal/impl/devbox.go

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"text/tabwriter"
1919
"text/template"
2020

21-
"github.com/fatih/color"
2221
"github.com/pkg/errors"
2322
"github.com/samber/lo"
2423
"golang.org/x/exp/slices"
@@ -30,9 +29,9 @@ import (
3029
"go.jetpack.io/devbox/internal/conf"
3130
"go.jetpack.io/devbox/internal/cuecfg"
3231
"go.jetpack.io/devbox/internal/debug"
32+
"go.jetpack.io/devbox/internal/devconfig"
3333
"go.jetpack.io/devbox/internal/envir"
3434
"go.jetpack.io/devbox/internal/fileutil"
35-
"go.jetpack.io/devbox/internal/initrec"
3635
"go.jetpack.io/devbox/internal/lock"
3736
"go.jetpack.io/devbox/internal/nix"
3837
"go.jetpack.io/devbox/internal/planner/plansdk"
@@ -46,8 +45,6 @@ import (
4645
)
4746

4847
const (
49-
// configFilename is name of the JSON file that defines a devbox environment.
50-
configFilename = "devbox.json"
5148

5249
// shellHistoryFile keeps the history of commands invoked inside devbox shell
5350
shellHistoryFile = ".devbox/shell_history"
@@ -59,30 +56,8 @@ const (
5956
arbitraryCmdFilename = ".cmd"
6057
)
6158

62-
func InitConfig(dir string, writer io.Writer) (created bool, err error) {
63-
cfgPath := filepath.Join(dir, configFilename)
64-
65-
config := defaultConfig()
66-
67-
// package suggestion
68-
pkgsToSuggest, err := initrec.Get(dir)
69-
if err != nil {
70-
return false, err
71-
}
72-
if len(pkgsToSuggest) > 0 {
73-
s := fmt.Sprintf("devbox add %s", strings.Join(pkgsToSuggest, " "))
74-
fmt.Fprintf(
75-
writer,
76-
"We detected extra packages you may need. To install them, run `%s`\n",
77-
color.HiYellowString(s),
78-
)
79-
}
80-
81-
return cuecfg.InitFile(cfgPath, config)
82-
}
83-
8459
type Devbox struct {
85-
cfg *Config
60+
cfg *devconfig.Config
8661
lockfile *lock.File
8762
nix nix.Nixer
8863
projectDir string
@@ -100,9 +75,9 @@ func Open(path string, writer io.Writer) (*Devbox, error) {
10075
if err != nil {
10176
return nil, err
10277
}
103-
cfgPath := filepath.Join(projectDir, configFilename)
78+
cfgPath := filepath.Join(projectDir, devconfig.DefaultName)
10479

105-
cfg, err := ReadConfig(cfgPath)
80+
cfg, err := devconfig.Load(cfgPath)
10681
if err != nil {
10782
return nil, errors.WithStack(err)
10883
}
@@ -130,7 +105,7 @@ func (d *Devbox) ProjectDir() string {
130105
return d.projectDir
131106
}
132107

133-
func (d *Devbox) Config() *Config {
108+
func (d *Devbox) Config() *devconfig.Config {
134109
return d.cfg
135110
}
136111

@@ -451,8 +426,7 @@ func (d *Devbox) GenerateEnvrcFile(force bool) error {
451426

452427
// saveCfg writes the config file to the devbox directory.
453428
func (d *Devbox) saveCfg() error {
454-
cfgPath := filepath.Join(d.projectDir, configFilename)
455-
return cuecfg.WriteFile(cfgPath, d.cfg)
429+
return d.cfg.SaveTo(d.ProjectDir())
456430
}
457431

458432
func (d *Devbox) Services() (services.Services, error) {

0 commit comments

Comments
 (0)