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
23 changes: 23 additions & 0 deletions internal/boxcli/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@ func servicesCmd(persistentPreRunE ...cobraFunc) *cobra.Command {
},
}

attachCommand := &cobra.Command{
Use: "attach",
Short: "Attach to a running process-compose for the current project",
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
return attachServices(cmd, flags)
},
}

lsCommand := &cobra.Command{
Use: "ls",
Short: "List available services",
Expand Down Expand Up @@ -123,6 +132,7 @@ func servicesCmd(persistentPreRunE ...cobraFunc) *cobra.Command {
servicesCommand.Flag("run-in-current-shell").Hidden = true
serviceUpFlags.register(upCommand)
serviceStopFlags.register(stopCommand)
servicesCommand.AddCommand(attachCommand)
servicesCommand.AddCommand(lsCommand)
servicesCommand.AddCommand(upCommand)
servicesCommand.AddCommand(restartCommand)
Expand All @@ -131,6 +141,19 @@ func servicesCmd(persistentPreRunE ...cobraFunc) *cobra.Command {
return servicesCommand
}

func attachServices(cmd *cobra.Command, flags servicesCmdFlags) error {
box, err := devbox.Open(&devopt.Opts{
Dir: flags.config.path,
Environment: flags.config.environment,
Stderr: cmd.ErrOrStderr(),
})
if err != nil {
return errors.WithStack(err)
}

return box.AttachToProcessManager(cmd.Context())
}

func listServices(cmd *cobra.Command, flags servicesCmdFlags) error {
box, err := devbox.Open(&devopt.Opts{
Dir: flags.config.path,
Expand Down
25 changes: 25 additions & 0 deletions internal/devbox/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,31 @@ func (d *Devbox) RestartServices(
return nil
}

func (d *Devbox) AttachToProcessManager(ctx context.Context) error {
if !services.ProcessManagerIsRunning(d.projectDir) {
return usererr.New("Process manager is not running. Run `devbox services up` to start it.")
}

err := initDevboxUtilityProject(ctx, d.stderr)
if err != nil {
return err
}

processComposeBinPath, err := utilityLookPath("process-compose")
if err != nil {
return err
}

return services.AttachToProcessManager(
ctx,
d.stderr,
d.projectDir,
services.ProcessComposeOpts{
BinPath: processComposeBinPath,
},
)
}

func (d *Devbox) StartProcessManager(
ctx context.Context,
runInCurrentShell bool,
Expand Down
38 changes: 36 additions & 2 deletions internal/services/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func StartProcessManager(
if processComposeConfig.Background {
flags = append(flags, "-t=false")
cmd := exec.Command(processComposeConfig.BinPath, flags...)
return runProcessManagerInBackground(cmd, config, port, projectDir)
return runProcessManagerInBackground(cmd, config, port, projectDir, w)
}

cmd := exec.Command(processComposeConfig.BinPath, flags...)
Expand Down Expand Up @@ -206,7 +206,7 @@ func runProcessManagerInForeground(cmd *exec.Cmd, config *globalProcessComposeCo
return writeGlobalProcessComposeJSON(config, configFile)
}

func runProcessManagerInBackground(cmd *exec.Cmd, config *globalProcessComposeConfig, port int, projectDir string) error {
func runProcessManagerInBackground(cmd *exec.Cmd, config *globalProcessComposeConfig, port int, projectDir string, w io.Writer) error {
logdir := filepath.Join(projectDir, processComposeLogfile)
logfile, err := os.OpenFile(logdir, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_TRUNC, 0o664)
if err != nil {
Expand All @@ -216,10 +216,20 @@ func runProcessManagerInBackground(cmd *exec.Cmd, config *globalProcessComposeCo
cmd.Stdout = logfile
cmd.Stderr = logfile

// These attributes set the process group ID to the process ID of process-compose
// Starting in it's own process group means it won't be terminated if the shell crashes
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
Pgid: 0,
}

if err := cmd.Start(); err != nil {
return fmt.Errorf("failed to start process-compose: %w", err)
}

fmt.Fprintf(w, "Process-compose is now running on port %d\n", port)
fmt.Fprintf(w, "To stop your services, run `devbox services stop`\n")

projectConfig := instance{
Pid: cmd.Process.Pid,
Port: port,
Expand Down Expand Up @@ -293,6 +303,30 @@ func StopAllProcessManagers(ctx context.Context, w io.Writer) error {
return nil
}

func AttachToProcessManager(ctx context.Context, w io.Writer, projectDir string, processComposeConfig ProcessComposeOpts) error {
configFile, err := openGlobalConfigFile()
if err != nil {
return err
}

defer configFile.Close()
config := readGlobalProcessComposeJSON(configFile)

project, ok := config.Instances[projectDir]
if !ok {
return fmt.Errorf("process-compose is not running for this project. To start it, run `devbox services up`")
}

flags := []string{"attach", "-p", strconv.Itoa(project.Port)}
cmd := exec.Command(processComposeConfig.BinPath, flags...)

cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin

return cmd.Run()
}

func ProcessManagerIsRunning(projectDir string) bool {
configFile, err := openGlobalConfigFile()
if err != nil {
Expand Down
Loading