Skip to content

Commit 177790a

Browse files
Add optional arguments in Pebble layer schema
The goal is to introduce the concept of optional and overrideable arguments for a Pebble service, but we must ensure that the layering properties of Pebble are preserved and respected. The proposed syntax re-uses therefore the existing service “command” attribute: services: myservice: command: myservice --db=/var/db [ --quiet ] The markers [ and ] are suggested because these are typical annotations for optional parameters and are rarely seen naked because they are interpreted by shells.
1 parent 054e5fb commit 177790a

File tree

2 files changed

+54
-6
lines changed

2 files changed

+54
-6
lines changed

internal/overlord/servstate/handlers.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -325,11 +325,9 @@ func logError(err error) {
325325
// command. It assumes the caller has ensures the service is in a valid state,
326326
// and it sets s.cmd and other relevant fields.
327327
func (s *serviceData) startInternal() error {
328-
args, err := shlex.Split(s.config.Command)
328+
args, err := s.config.GetCommand()
329329
if err != nil {
330-
// Shouldn't happen as it should have failed on parsing, but
331-
// it does not hurt to double check and report.
332-
return fmt.Errorf("cannot parse service command: %s", err)
330+
return err
333331
}
334332
s.cmd = exec.Command(args[0], args[1:]...)
335333
s.cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
@@ -386,7 +384,7 @@ func (s *serviceData) startInternal() error {
386384
s.cmd.Stderr = logWriter
387385

388386
// Start the process!
389-
logger.Noticef("Service %q starting: %s", serviceName, s.config.Command)
387+
logger.Noticef("Service %q starting: %q", serviceName, args)
390388
err = reaper.StartCommand(s.cmd)
391389
if err != nil {
392390
if outputIterator != nil {

internal/plan/plan.go

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ type Service struct {
6565
Startup ServiceStartup `yaml:"startup,omitempty"`
6666
Override Override `yaml:"override,omitempty"`
6767
Command string `yaml:"command,omitempty"`
68+
cmdArgs []string
6869

6970
// Service dependencies
7071
After []string `yaml:"after,omitempty"`
@@ -90,6 +91,7 @@ type Service struct {
9091
// Copy returns a deep copy of the service.
9192
func (s *Service) Copy() *Service {
9293
copied := *s
94+
copied.cmdArgs = append([]string(nil), s.cmdArgs...)
9395
copied.After = append([]string(nil), s.After...)
9496
copied.Before = append([]string(nil), s.Before...)
9597
copied.Requires = append([]string(nil), s.Requires...)
@@ -129,6 +131,7 @@ func (s *Service) Merge(other *Service) {
129131
}
130132
if other.Command != "" {
131133
s.Command = other.Command
134+
s.cmdArgs = append([]string(nil), other.cmdArgs...)
132135
}
133136
if other.UserID != nil {
134137
userID := *other.UserID
@@ -184,6 +187,53 @@ func (s *Service) Equal(other *Service) bool {
184187
return reflect.DeepEqual(s, other)
185188
}
186189

190+
// Returns the command as a stream of strings.
191+
// Filters "[", "]" (if present, denoting optional arguments).
192+
func (s *Service) GetCommand() ([]string, error) {
193+
args, err := shlex.Split(s.Command)
194+
if err != nil {
195+
return nil, fmt.Errorf("cannot parse service %q command: %s", s.Name, err)
196+
}
197+
fargs := make([]string, 0)
198+
for _, arg := range args {
199+
if arg == "[" || arg == "]" {
200+
if len(s.cmdArgs) > 0 {
201+
break
202+
} else {
203+
continue
204+
}
205+
}
206+
fargs = append(fargs, arg)
207+
}
208+
for _, arg := range s.cmdArgs {
209+
fargs = append(fargs, arg)
210+
}
211+
return fargs, nil
212+
}
213+
214+
func (s *Service) checkCommand() error {
215+
args, err := shlex.Split(s.Command)
216+
if err != nil {
217+
return err
218+
}
219+
leftCnt := 0
220+
rightCnt := 0
221+
for _, arg := range args {
222+
if arg == "[" {
223+
leftCnt++
224+
}
225+
if arg == "]" {
226+
rightCnt++
227+
}
228+
}
229+
if leftCnt > 0 || rightCnt > 0 {
230+
if leftCnt != 1 || rightCnt != 1 || args[len(args)-1] != "]" {
231+
return fmt.Errorf("bad syntax regarding optional/overridable arguments")
232+
}
233+
}
234+
return nil
235+
}
236+
187237
type ServiceStartup string
188238

189239
const (
@@ -483,7 +533,7 @@ func CombineLayers(layers ...*Layer) (*Layer, error) {
483533
Message: fmt.Sprintf(`plan must define "command" for service %q`, name),
484534
}
485535
}
486-
_, err := shlex.Split(service.Command)
536+
err := service.checkCommand()
487537
if err != nil {
488538
return nil, &FormatError{
489539
Message: fmt.Sprintf("plan service %q command invalid: %v", name, err),

0 commit comments

Comments
 (0)