-
Notifications
You must be signed in to change notification settings - Fork 264
First pass at devbox scripts implementation #254
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
6b28661
to
6b642c0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Approving to unblock but interested in discussion on the comments below
Use: "run <script>", | ||
Hidden: true, | ||
Short: "Starts a new devbox shell and runs the target script", | ||
Long: "Starts a new interactive shell and runs your target script in it. The shell will exit once your target script is completed or when it is terminated via CTRL-C. Scripts can be defined in your `devbox.json`", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we're running a script command, does the shell need to be interactive?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the point is that you can start a long-running process, like a server, and it's stays in the foreground so you can Ctrl+C out of it when you want.
boxcli/run.go
Outdated
script := "" | ||
if len(args) == 1 { | ||
script = args[0] | ||
} else if len(args) > 1 { | ||
return "", "", errors.Errorf("too many arguments (expected 1, got %d)", len(args)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead, would it make sense to set Args: cobra.MaximumNArgs(1),
above when defining the cobra command?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I guess.. the error messages from the cobra validators are pretty bad though, but I'll see if this is reasonable.
} | ||
|
||
if devbox.IsDevboxShellEnabled() { | ||
return errors.New("You are already in an active devbox shell.\nRun 'exit' before calling devbox run again. Shell inception is not supported yet.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like how you added yet
compared to the same error in shell.go
:)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lol yeah
@@ -37,7 +37,8 @@ type Config struct { | |||
// Shell configures the devbox shell environment. | |||
Shell struct { | |||
// InitHook contains commands that will run at shell startup. | |||
InitHook ConfigShellCmds `json:"init_hook,omitempty"` | |||
InitHook ConfigShellCmds `json:"init_hook,omitempty"` | |||
Scripts map[string]*ConfigShellCmds `json:"scripts,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in launchpad.yaml
, we had a similar undeterministic-ordering issue with the Services. We handled it by:
- defining it as
Scripts []ConfigShellCmds
and adding the "name" as a field inConfigShellCmds
- adding custom marshal and unmarshal functions to have it print as a map with the key
Would you wanna try that? Could do as a followup. I feel it would be really nice to ensure determinism. With determinism, we won't get random diffs of devbox.json when we write to it for (other unrelated) reasons.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh interesting, i'll see about it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll do as a follow-up
config.go
Outdated
func (c *Config) validate() error { | ||
for k := range c.Shell.Scripts { | ||
if k == "" { | ||
return errors.New("cannot have script with empty name in devbox.json") | ||
} | ||
} | ||
return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh boy, I ended up adding another validate function today, so will need to merge it. It should be fairly straightforward.
@loreto had requested me to see if we can use the cue
validation instead, which I am yet to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I actually attempted to use cue
for validation, but I couldn't find what I needed quickly enough so I gave up and added the validation function. I can merge it with yours
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
devbox.go
Outdated
if err := d.ensurePackagesAreInstalled(install); err != nil { | ||
return err | ||
} | ||
|
||
plan := d.ShellPlan() | ||
|
||
profileDir, err := d.profileDir() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
nixShellFilePath := filepath.Join(d.srcDir, ".devbox/gen/shell.nix") | ||
script := d.cfg.Shell.Scripts[scriptName] | ||
if script == nil { | ||
return errors.Errorf("unable to find a script with name %s", scriptName) | ||
} | ||
|
||
shell, err := nix.DetectShell( | ||
nix.WithPlanInitHook(strings.Join(plan.ShellInitHook, "\n")), | ||
nix.WithProfile(profileDir), | ||
nix.WithHistoryFile(filepath.Join(d.srcDir, shellHistoryFile)), | ||
nix.WithUserScript(scriptName, script.String())) | ||
|
||
if err != nil { | ||
fmt.Print(err) | ||
shell = &nix.Shell{} | ||
} | ||
|
||
shell.UserInitHook = d.cfg.Shell.InitHook.String() | ||
return shell.Run(nixShellFilePath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh boyo boy. I thought this would reuse the implementation of box.Exec
....
but I agree with your implementation more (as in, we should strive to emulate the devbox shell
environment which box.Exec
doesn't seem to).
So, there are three different implementations now:
- this
RunScript
box.Exec
- the
box.Shell
(except this doesn't run a specific command, but starts an interactive shell)
I feel:
box.Exec
andRunScript
should use the exact same implementationbox.Shell
should also be refactored to use the same core implementation asbox.Exec
andRunScript
(which is your TODO above)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm ok I'm not too familiar with box.Exec
, I can look into it. I'm new to this code, so this type of feedback is really useful.
0498ec1
to
4a5bea0
Compare
Summary
Devbox scripts allows you to define scripts to run inside a devbox shell. You can add them to your devbox.json like so:
And then you can run with
devbox run <script>
. For example:Note that the shell's init hook will always run.
How was it tested?
With a devbox.json as written above and running
devbox run <script>
with no script, invalid script, valid script (single command and multi-command).