Skip to content

Commit

Permalink
Add support for service's to configure working directory (#245)
Browse files Browse the repository at this point in the history
This adds support for a "working-dir" option in the layered service
configuration, for starting a service in a specified working directory
(the default is still to start it in Pebble's working directory).

Fixes #158
  • Loading branch information
benhoyt committed Jun 26, 2023
1 parent 8902cbe commit a807026
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 6 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,10 @@ services:
# group and group-id are specified, the group's GID must match group-id.
group-id: <gid>
# (Optional) Working directory to run command in. By default, the
# command is run in the service manager's current directory.
working-dir: <directory>
# (Optional) Defines what happens when the service exits with a zero
# exit code. Possible values are: "restart" (default) which restarts
# the service after the backoff delay, "shutdown" which shuts down and
Expand Down Expand Up @@ -653,7 +657,8 @@ checks:
# match group-id.
group-id: <gid>
# (Optional) Working directory to run command in.
# (Optional) Working directory to run command in. By default, the
# command is run in the service manager's current directory.
working-dir: <directory>
```

Expand Down
2 changes: 2 additions & 0 deletions internals/overlord/servstate/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,8 @@ func (s *serviceData) startInternal() error {
environment[k] = v
}

s.cmd.Dir = s.config.WorkingDir

// Start as another user if specified in plan.
uid, gid, err := osutil.NormalizeUidGid(s.config.UserID, s.config.GroupID, s.config.User, s.config.Group)
if err != nil {
Expand Down
59 changes: 59 additions & 0 deletions internals/overlord/servstate/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1763,3 +1763,62 @@ type fakeLogManager struct{}
func (f fakeLogManager) ServiceStarted(serviceName string, logs *servicelog.RingBuffer) {
// no-op
}

func (s *S) TestNoWorkingDir(c *C) {
// Service command should run in current directory (package directory)
// if "working-dir" config option not set.
dir := c.MkDir()
err := os.Mkdir(filepath.Join(dir, "layers"), 0755)
c.Assert(err, IsNil)
manager, err := servstate.NewManager(s.st, s.runner, dir, nil, nil, fakeLogManager{})
c.Assert(err, IsNil)
defer manager.Stop()

outputPath := filepath.Join(dir, "output")
layer := parseLayer(c, 0, "layer", fmt.Sprintf(`
services:
nowrkdir:
override: replace
command: /bin/sh -c "pwd >%s; sleep %g"
`, outputPath, shortOkayDelay.Seconds()+0.01))
err = manager.AppendLayer(layer)
c.Assert(err, IsNil)

chg := s.startServices(c, []string{"nowrkdir"}, 1)
s.st.Lock()
c.Assert(chg.Err(), IsNil)
s.st.Unlock()

output, err := ioutil.ReadFile(outputPath)
c.Assert(err, IsNil)
c.Check(string(output), Matches, ".*/overlord/servstate\n")
}

func (s *S) TestWorkingDir(c *C) {
dir := c.MkDir()
err := os.Mkdir(filepath.Join(dir, "layers"), 0755)
c.Assert(err, IsNil)
manager, err := servstate.NewManager(s.st, s.runner, dir, nil, nil, fakeLogManager{})
c.Assert(err, IsNil)
defer manager.Stop()

outputPath := filepath.Join(dir, "output")
layer := parseLayer(c, 0, "layer", fmt.Sprintf(`
services:
wrkdir:
override: replace
command: /bin/sh -c "pwd >%s; sleep %g"
working-dir: %s
`, outputPath, shortOkayDelay.Seconds()+0.01, dir))
err = manager.AppendLayer(layer)
c.Assert(err, IsNil)

chg := s.startServices(c, []string{"wrkdir"}, 1)
s.st.Lock()
c.Assert(chg.Err(), IsNil)
s.st.Unlock()

output, err := ioutil.ReadFile(outputPath)
c.Assert(err, IsNil)
c.Check(string(output), Equals, dir+"\n")
}
4 changes: 4 additions & 0 deletions internals/plan/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ type Service struct {
User string `yaml:"user,omitempty"`
GroupID *int `yaml:"group-id,omitempty"`
Group string `yaml:"group,omitempty"`
WorkingDir string `yaml:"working-dir,omitempty"`

// Auto-restart and backoff functionality
OnSuccess ServiceAction `yaml:"on-success,omitempty"`
Expand Down Expand Up @@ -154,6 +155,9 @@ func (s *Service) Merge(other *Service) {
if other.Group != "" {
s.Group = other.Group
}
if other.WorkingDir != "" {
s.WorkingDir = other.WorkingDir
}
s.After = append(s.After, other.After...)
s.Before = append(s.Before, other.Before...)
s.Requires = append(s.Requires, other.Requires...)
Expand Down
17 changes: 12 additions & 5 deletions internals/plan/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,14 @@ var planTests = []planTest{{
backoff-delay: 1s
backoff-factor: 1.5
backoff-limit: 10s
working-dir: /workdir/srv1
srv2:
override: replace
startup: enabled
command: cmd
before:
- srv3
working-dir: /workdir/srv2
srv3:
override: replace
command: cmd
Expand All @@ -131,6 +133,7 @@ var planTests = []planTest{{
- srv4
before:
- srv5
working-dir: /workdir/srv1/override
srv2:
override: replace
startup: disabled
Expand Down Expand Up @@ -173,16 +176,18 @@ var planTests = []planTest{{
"var0": "val0",
"var2": "val2",
},
WorkingDir: "/workdir/srv1",
BackoffDelay: plan.OptionalDuration{Value: time.Second, IsSet: true},
BackoffFactor: plan.OptionalFloat{Value: 1.5, IsSet: true},
BackoffLimit: plan.OptionalDuration{Value: 10 * time.Second, IsSet: true},
},
"srv2": {
Name: "srv2",
Override: "replace",
Command: "cmd",
Startup: plan.StartupEnabled,
Before: []string{"srv3"},
Name: "srv2",
Override: "replace",
Command: "cmd",
WorkingDir: "/workdir/srv2",
Startup: plan.StartupEnabled,
Before: []string{"srv3"},
},
"srv3": {
Name: "srv3",
Expand Down Expand Up @@ -213,6 +218,7 @@ var planTests = []planTest{{
Environment: map[string]string{
"var3": "val3",
},
WorkingDir: "/workdir/srv1/override",
},
"srv2": {
Name: "srv2",
Expand Down Expand Up @@ -269,6 +275,7 @@ var planTests = []planTest{{
"var2": "val2",
"var3": "val3",
},
WorkingDir: "/workdir/srv1/override",
BackoffDelay: plan.OptionalDuration{Value: time.Second, IsSet: true},
BackoffFactor: plan.OptionalFloat{Value: 1.5, IsSet: true},
BackoffLimit: plan.OptionalDuration{Value: 10 * time.Second, IsSet: true},
Expand Down

0 comments on commit a807026

Please sign in to comment.