Skip to content

Commit f77daf6

Browse files
authored
[services] MVP process manager (#659)
## Summary Very simple implementation of using a third party process manager to launch and manage services. Only implemented for nginx, but trivial to add new services. It first installs `process-compose` as a global package (if necessary). If any of the installed services contain a process-compose.yaml it references it and starts the manager. TODO: - [ ] Doesn't work with auto-forwarding ## How was it tested? `devbox services manager` <img width="1398" alt="image" src="https://user-images.githubusercontent.com/544948/220255227-91bcd48e-faf1-4319-ac7c-312f4fab043f.png">
1 parent 94aa7d4 commit f77daf6

File tree

9 files changed

+184
-15
lines changed

9 files changed

+184
-15
lines changed

devbox.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type Devbox interface {
4848
// ShellPlan creates a plan of the actions that devbox will take to generate its
4949
// shell environment.
5050
ShellPlan() (*plansdk.ShellPlan, error)
51+
StartProcessManager(ctx context.Context) error
5152
StartServices(ctx context.Context, services ...string) error
5253
StopServices(ctx context.Context, services ...string) error
5354
}

internal/boxcli/services.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,17 @@ func ServicesCmd() *cobra.Command {
5353
},
5454
}
5555

56+
processManagerCommand := &cobra.Command{
57+
Use: "manager",
58+
Short: "Starts process manager with all supported services",
59+
RunE: func(cmd *cobra.Command, args []string) error {
60+
return startProcessManager(cmd, flags)
61+
},
62+
}
63+
5664
flags.config.register(servicesCommand)
5765
servicesCommand.AddCommand(lsCommand)
66+
servicesCommand.AddCommand(processManagerCommand)
5867
servicesCommand.AddCommand(restartCommand)
5968
servicesCommand.AddCommand(startCommand)
6069
servicesCommand.AddCommand(stopCommand)
@@ -132,3 +141,11 @@ func restartServices(
132141
_ = stopServices(cmd, services, flags)
133142
return startServices(cmd, services, flags)
134143
}
144+
145+
func startProcessManager(cmd *cobra.Command, flags servicesCmdFlags) error {
146+
box, err := devbox.Open(flags.config.path, cmd.ErrOrStderr())
147+
if err != nil {
148+
return errors.WithStack(err)
149+
}
150+
return box.StartProcessManager(cmd.Context())
151+
}

internal/impl/devbox.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,34 @@ func (d *Devbox) StartServices(ctx context.Context, serviceNames ...string) erro
611611
return services.Start(ctx, d.packages(), serviceNames, d.projectDir, d.writer)
612612
}
613613

614+
func (d *Devbox) StartProcessManager(ctx context.Context) error {
615+
svcs, err := d.Services()
616+
if err != nil {
617+
return err
618+
}
619+
hasServiceWithProcessCompose := false
620+
for _, s := range svcs {
621+
if _, hasComposeYaml := s.ProcessComposeYaml(); hasComposeYaml {
622+
hasServiceWithProcessCompose = true
623+
break
624+
}
625+
}
626+
if !hasServiceWithProcessCompose {
627+
return usererr.New("No services with process-compose.yaml found")
628+
}
629+
processComposePath, err := utilityLookPath("process-compose")
630+
if err != nil {
631+
if err = addDevboxUtilityPackage("process-compose"); err != nil {
632+
return err
633+
}
634+
}
635+
if !IsDevboxShellEnabled() {
636+
return d.Exec("devbox", "services", "manager")
637+
}
638+
639+
return services.StartProcessManager(ctx, processComposePath, svcs)
640+
}
641+
614642
func (d *Devbox) StopServices(ctx context.Context, serviceNames ...string) error {
615643
if !IsDevboxShellEnabled() {
616644
return d.Exec(append([]string{"devbox", "services", "stop"}, serviceNames...)...)

internal/impl/global.go

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ import (
1919
"go.jetpack.io/devbox/internal/xdg"
2020
)
2121

22+
var warningNotInPath = usererr.NewWarning(`the devbox global profile is not in your $PATH.
23+
24+
Add the following line to your shell's rcfile (e.g., ~/.bashrc or ~/.zshrc)
25+
and restart your shell to fix this:
26+
27+
eval "$(devbox global shellenv)"
28+
`)
29+
2230
// In the future we will support multiple global profiles
2331
const currentGlobalProfile = "default"
2432

@@ -143,6 +151,20 @@ func GlobalNixProfilePath() (string, error) {
143151
return filepath.Join(path, "profile"), nil
144152
}
145153

154+
func globalBinPath() (string, error) {
155+
nixProfilePath, err := GlobalNixProfilePath()
156+
if err != nil {
157+
return "", err
158+
}
159+
currentPath := xdg.DataSubpath("devbox/global/current")
160+
// For now default is always current. In the future we will support multiple
161+
// and allow user to switch.
162+
if err := os.Symlink(nixProfilePath, currentPath); err != nil && !os.IsExist(err) {
163+
return "", errors.WithStack(err)
164+
}
165+
return filepath.Join(currentPath, "bin"), nil
166+
}
167+
146168
// GenerateShellEnv generates shell commands that configure the user's shell
147169
// environment to work with Devbox packages. Most notably, it adds Devbox
148170
// packages to the user's PATH. The commands are intended to be evaluated in
@@ -163,25 +185,12 @@ func GenerateShellEnv() string {
163185

164186
// Checks if the global profile is in the path
165187
func ensureGlobalProfileInPath() error {
166-
nixProfilePath, err := GlobalNixProfilePath()
188+
binPath, err := globalBinPath()
167189
if err != nil {
168190
return err
169191
}
170-
currentPath := xdg.DataSubpath("devbox/global/current")
171-
// For now default is always current. In the future we will support multiple
172-
// and allow user to switch.
173-
if err := os.Symlink(nixProfilePath, currentPath); err != nil && !os.IsExist(err) {
174-
return errors.WithStack(err)
175-
}
176-
binPath := filepath.Join(currentPath, "bin")
177192
if !strings.Contains(os.Getenv("PATH"), binPath) {
178-
return usererr.NewWarning(`the devbox global profile is not in your $PATH.
179-
180-
Add the following line to your shell's rcfile (e.g., ~/.bashrc or ~/.zshrc)
181-
and restart your shell to fix this:
182-
183-
eval "$(devbox global shellenv)"
184-
`)
193+
return warningNotInPath
185194
}
186195
return nil
187196
}

internal/impl/util.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package impl
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
7+
"github.com/pkg/errors"
8+
"go.jetpack.io/devbox/internal/nix"
9+
"go.jetpack.io/devbox/internal/planner/plansdk"
10+
"go.jetpack.io/devbox/internal/xdg"
11+
)
12+
13+
// addDevboxUtilityPackage adds a package to the devbox utility profile.
14+
// It's used to install applications devbox might need, like process-compose
15+
// This is an alternative to a global install which would modify a user's
16+
// environment.
17+
func addDevboxUtilityPackage(pkg string) error {
18+
profilePath, err := utilityNixProfilePath()
19+
if err != nil {
20+
return err
21+
}
22+
return nix.ProfileInstall(profilePath, plansdk.DefaultNixpkgsCommit, pkg)
23+
}
24+
25+
func utilityLookPath(binName string) (string, error) {
26+
binPath, err := utilityBinPath()
27+
if err != nil {
28+
return "", err
29+
}
30+
absPath := filepath.Join(binPath, binName)
31+
if _, err := os.Stat(absPath); os.IsNotExist(err) {
32+
return "", err
33+
}
34+
return absPath, nil
35+
}
36+
37+
func utilityDataPath() (string, error) {
38+
path := xdg.DataSubpath("devbox/util")
39+
if err := os.MkdirAll(path, 0755); err != nil {
40+
return "", errors.WithStack(err)
41+
}
42+
return path, nil
43+
}
44+
45+
func utilityNixProfilePath() (string, error) {
46+
path, err := utilityDataPath()
47+
if err != nil {
48+
return "", err
49+
}
50+
return filepath.Join(path, "profile"), nil
51+
}
52+
53+
func utilityBinPath() (string, error) {
54+
nixProfilePath, err := utilityNixProfilePath()
55+
if err != nil {
56+
return "", err
57+
}
58+
return filepath.Join(nixProfilePath, "bin"), nil
59+
}

internal/plugin/services.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ package plugin
22

33
import (
44
"encoding/json"
5+
"strings"
56

67
"github.com/a8m/envsubst"
78
)
89

910
type Services map[string]service
1011

1112
type service struct {
13+
config *config
1214
Name string `json:"name"`
1315
RawPort string `json:"port"`
1416
Start string `json:"start"`
@@ -22,6 +24,15 @@ func (s *service) Port() (string, error) {
2224
return envsubst.String(s.RawPort)
2325
}
2426

27+
func (s *service) ProcessComposeYaml() (string, bool) {
28+
for file := range s.config.CreateFiles {
29+
if strings.HasSuffix(file, "process-compose.yaml") || strings.HasSuffix(file, "process-compose.yml") {
30+
return file, true
31+
}
32+
}
33+
return "", false
34+
}
35+
2536
func GetServices(pkgs []string, projectDir string) (Services, error) {
2637
services := map[string]service{}
2738
for _, pkg := range pkgs {
@@ -34,6 +45,7 @@ func GetServices(pkgs []string, projectDir string) (Services, error) {
3445
}
3546
for name, svc := range c.Services {
3647
svc.Name = name
48+
svc.config = c
3749
services[name] = svc
3850
}
3951
}

internal/services/manager.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package services
2+
3+
import (
4+
"context"
5+
"os"
6+
"os/exec"
7+
8+
"go.jetpack.io/devbox/internal/plugin"
9+
)
10+
11+
func StartProcessManager(
12+
ctx context.Context,
13+
processComposePath string,
14+
services plugin.Services,
15+
) error {
16+
flags := []string{"-p", "8280"}
17+
for _, s := range services {
18+
if file, hasComposeYaml := s.ProcessComposeYaml(); hasComposeYaml {
19+
flags = append(flags, "-f", file)
20+
}
21+
}
22+
cmd := exec.Command(processComposePath, flags...)
23+
cmd.Stdout = os.Stdout
24+
cmd.Stderr = os.Stderr
25+
cmd.Stdin = os.Stdin
26+
return cmd.Run()
27+
}

plugins/nginx.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
},
1010
"create_files": {
1111
"{{ .Virtenv }}/temp": "",
12+
"{{ .Virtenv }}/process-compose.yaml": "nginx/process-compose.yaml",
1213
"{{ .DevboxDir }}/nginx.conf": "nginx/nginx.conf",
1314
"{{ .DevboxDir }}/fastcgi.conf": "nginx/fastcgi.conf",
1415
"{{ .DevboxDirRoot }}/web/index.html": "web/index.html"

plugins/nginx/process-compose.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
version: "0.5"
2+
3+
processes:
4+
nginx:
5+
command: "nginx -p $NGINX_PATH_PREFIX -c $NGINX_CONFDIR -e error.log -g \"pid nginx.pid;daemon off;\""
6+
availability:
7+
restart: "always"
8+
nginx-error:
9+
command: "tail -f $NGINX_PATH_PREFIX/error.log"
10+
availability:
11+
restart: "always"
12+
nginx-access:
13+
command: "tail -f $NGINX_PATH_PREFIX/access.log"
14+
availability:
15+
restart: "always"

0 commit comments

Comments
 (0)