Skip to content

Commit 2ce087f

Browse files
authored
[init-hooks] Prevent init hook recursion (#1709)
## Summary Fixes #1651 ## How was it tested? Added a `devbox run echo foobar` into our init hook. Tested with ``` devbox run echo 123 SHELL=fish devbox run echo 123 ```
1 parent dc390b4 commit 2ce087f

File tree

6 files changed

+43
-11
lines changed

6 files changed

+43
-11
lines changed

internal/devbox/devbox.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ func (d *Devbox) EnvVars(ctx context.Context) ([]string, error) {
338338

339339
func (d *Devbox) shellEnvHashKey() string {
340340
// Don't make this a const so we don't use it by itself accidentally
341-
return "__DEVBOX_SHELLENV_HASH_" + d.projectDirHash()
341+
return "__DEVBOX_SHELLENV_HASH_" + d.ProjectDirHash()
342342
}
343343

344344
func (d *Devbox) Info(ctx context.Context, pkg string, markdown bool) (string, error) {
@@ -949,7 +949,7 @@ func (d *Devbox) computeEnv(ctx context.Context, usePrintDevEnvCache bool) (map[
949949
devboxEnvPath = envpath.JoinPathLists(devboxEnvPath, runXPaths)
950950

951951
pathStack := envpath.Stack(env, originalEnv)
952-
pathStack.Push(env, d.projectDirHash(), devboxEnvPath, d.preservePathStack)
952+
pathStack.Push(env, d.ProjectDirHash(), devboxEnvPath, d.preservePathStack)
953953
env["PATH"] = pathStack.Path(env)
954954
debug.Log("New path stack is: %s", pathStack)
955955

@@ -1170,7 +1170,7 @@ func (d *Devbox) setCommonHelperEnvVars(env map[string]string) {
11701170
env["LIBRARY_PATH"] = envpath.JoinPathLists(profileLibDir, env["LIBRARY_PATH"])
11711171
}
11721172

1173-
func (d *Devbox) projectDirHash() string {
1173+
func (d *Devbox) ProjectDirHash() string {
11741174
h, _ := cachehash.Bytes([]byte(d.projectDir))
11751175
return h
11761176
}

internal/devbox/devbox_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func TestComputeDevboxPathIsIdempotent(t *testing.T) {
8888
t.Setenv("PATH", path)
8989
t.Setenv(envpath.InitPathEnv, env[envpath.InitPathEnv])
9090
t.Setenv(envpath.PathStackEnv, env[envpath.PathStackEnv])
91-
t.Setenv(envpath.Key(devbox.projectDirHash()), env[envpath.Key(devbox.projectDirHash())])
91+
t.Setenv(envpath.Key(devbox.ProjectDirHash()), env[envpath.Key(devbox.ProjectDirHash())])
9292

9393
env, err = devbox.computeEnv(ctx, false /*use cache*/)
9494
require.NoError(t, err, "computeEnv should not fail")
@@ -110,7 +110,7 @@ func TestComputeDevboxPathWhenRemoving(t *testing.T) {
110110
t.Setenv("PATH", path)
111111
t.Setenv(envpath.InitPathEnv, env[envpath.InitPathEnv])
112112
t.Setenv(envpath.PathStackEnv, env[envpath.PathStackEnv])
113-
t.Setenv(envpath.Key(devbox.projectDirHash()), env[envpath.Key(devbox.projectDirHash())])
113+
t.Setenv(envpath.Key(devbox.ProjectDirHash()), env[envpath.Key(devbox.ProjectDirHash())])
114114

115115
devbox.nix.(*testNix).path = ""
116116
env, err = devbox.computeEnv(ctx, false /*use cache*/)

internal/devbox/envvars.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,5 @@ func (d *Devbox) IsEnvEnabled() bool {
7676
fakeEnv := map[string]string{}
7777
// the Stack is initialized in the fakeEnv, from the state in the real os.Environ
7878
pathStack := envpath.Stack(fakeEnv, envir.PairsToMap(os.Environ()))
79-
return pathStack.Has(d.projectDirHash())
79+
return pathStack.Has(d.ProjectDirHash())
8080
}

internal/devbox/refresh.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func (d *Devbox) isRefreshAliasSet() bool {
1515
}
1616

1717
func (d *Devbox) refreshAliasEnvVar() string {
18-
return "DEVBOX_REFRESH_ALIAS_" + d.projectDirHash()
18+
return "DEVBOX_REFRESH_ALIAS_" + d.ProjectDirHash()
1919
}
2020

2121
func (d *Devbox) isGlobal() bool {

internal/shellgen/scripts.go

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import (
55
"os"
66
"path/filepath"
77
"strings"
8+
"text/template"
9+
10+
_ "embed"
811

912
"github.com/pkg/errors"
1013
"go.jetpack.io/devbox/internal/boxcli/featureflag"
@@ -15,6 +18,9 @@ import (
1518
"go.jetpack.io/devbox/internal/plugin"
1619
)
1720

21+
//go:embed tmpl/init-hook.tmpl
22+
var initHookTmpl string
23+
1824
const scriptsDir = ".devbox/gen/scripts"
1925

2026
// HooksFilename is the name of the file that contains the project's init-hooks and plugin hooks
@@ -27,6 +33,7 @@ type devboxer interface {
2733
InstallablePackages() []*devpkg.Package
2834
PluginManager() *plugin.Manager
2935
ProjectDir() string
36+
ProjectDirHash() string
3037
}
3138

3239
// WriteScriptsToFiles writes scripts defined in devbox.json into files inside .devbox/gen/scripts.
@@ -55,7 +62,7 @@ func WriteScriptsToFiles(devbox devboxer) error {
5562
}
5663
hooks := strings.Join(append(pluginHooks, devbox.Config().InitHook().String()), "\n\n")
5764
// always write it, even if there are no hooks, because scripts will source it.
58-
err = writeHookFile(devbox, hooks)
65+
err = writeInitHookFile(devbox, hooks)
5966
if err != nil {
6067
return errors.WithStack(err)
6168
}
@@ -84,15 +91,25 @@ func WriteScriptsToFiles(devbox devboxer) error {
8491
return nil
8592
}
8693

87-
func writeHookFile(devbox devboxer, body string) (err error) {
94+
func writeInitHookFile(devbox devboxer, body string) (err error) {
8895
script, err := createScriptFile(devbox, HooksFilename)
8996
if err != nil {
9097
return errors.WithStack(err)
9198
}
9299
defer script.Close() // best effort: close file
93100

94-
_, err = script.WriteString(body)
95-
return errors.WithStack(err)
101+
t, err := template.New("init-hook-template").Parse(initHookTmpl)
102+
if err != nil {
103+
return errors.WithStack(err)
104+
}
105+
106+
return t.Execute(script, map[string]any{
107+
"Body": body,
108+
"InitHookHash": "__DEVBOX_INIT_HOOK_" + devbox.ProjectDirHash(),
109+
// TODO put IsFish() in common place so we can call here and in devbox package
110+
// without adding more stuff to interface
111+
"IsFish": filepath.Base(os.Getenv("SHELL")) == "fish",
112+
})
96113
}
97114

98115
func WriteScriptFile(devbox devboxer, name, body string) (err error) {

internal/shellgen/tmpl/init-hook.tmpl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
{{/* if hash is set, we're in recursive loop, just exit */ -}}
3+
if [ -n "${{ .InitHookHash }}" ]; then
4+
return
5+
fi
6+
7+
export {{ .InitHookHash }}=true
8+
9+
{{ .Body }}
10+
11+
{{ if .IsFish }}
12+
set -e {{ .InitHookHash }}
13+
{{ else }}
14+
unset {{ .InitHookHash }}
15+
{{ end }}

0 commit comments

Comments
 (0)