Skip to content

Commit 4f0ea60

Browse files
feat: Support --args functionality in CLI
Arguments to a service can now be passed by using the following syntax: pebble run [opts..] --args <svc> arguments.. ; [opts..] Tests are failing right now, will be fixed soon.
1 parent 177790a commit 4f0ea60

File tree

5 files changed

+111
-17
lines changed

5 files changed

+111
-17
lines changed

client/services.go

+23-10
Original file line numberDiff line numberDiff line change
@@ -25,50 +25,63 @@ import (
2525

2626
type ServiceOptions struct {
2727
Names []string
28+
Args map[string][]string
2829
}
2930

3031
// AutoStart starts the services makes as "startup: enabled". opts.Names must
3132
// be empty for this call.
3233
func (client *Client) AutoStart(opts *ServiceOptions) (changeID string, err error) {
33-
_, changeID, err = client.doMultiServiceAction("autostart", opts.Names)
34+
_, changeID, err = client.doMultiServiceAction("autostart", &multiActionOptions{services: opts.Names})
3435
return changeID, err
3536
}
3637

3738
// Start starts the services named in opts.Names in dependency order.
3839
func (client *Client) Start(opts *ServiceOptions) (changeID string, err error) {
39-
_, changeID, err = client.doMultiServiceAction("start", opts.Names)
40+
_, changeID, err = client.doMultiServiceAction("start", &multiActionOptions{services: opts.Names})
4041
return changeID, err
4142
}
4243

4344
// Stop stops the services named in opts.Names in dependency order.
4445
func (client *Client) Stop(opts *ServiceOptions) (changeID string, err error) {
45-
_, changeID, err = client.doMultiServiceAction("stop", opts.Names)
46+
_, changeID, err = client.doMultiServiceAction("stop", &multiActionOptions{services: opts.Names})
4647
return changeID, err
4748
}
4849

4950
// Restart stops and then starts the services named in opts.Names in
5051
// dependency order.
5152
func (client *Client) Restart(opts *ServiceOptions) (changeID string, err error) {
52-
_, changeID, err = client.doMultiServiceAction("restart", opts.Names)
53+
_, changeID, err = client.doMultiServiceAction("restart", &multiActionOptions{services: opts.Names})
5354
return changeID, err
5455
}
5556

5657
// Replan stops and (re)starts the services whose configuration has changed
5758
// since they were started. opts.Names must be empty for this call.
5859
func (client *Client) Replan(opts *ServiceOptions) (changeID string, err error) {
59-
_, changeID, err = client.doMultiServiceAction("replan", opts.Names)
60+
_, changeID, err = client.doMultiServiceAction("replan", &multiActionOptions{services: opts.Names})
6061
return changeID, err
6162
}
6263

64+
func (client *Client) PassServiceArgs(opts *ServiceOptions) (changeID string, err error) {
65+
_, changeID, err = client.doMultiServiceAction("pass-args", &multiActionOptions{serviceArgs: opts.Args})
66+
return changeID, err
67+
}
68+
69+
type multiActionOptions struct {
70+
services []string
71+
serviceArgs map[string][]string
72+
}
73+
6374
type multiActionData struct {
64-
Action string `json:"action"`
65-
Services []string `json:"services"`
75+
Action string `json:"action"`
76+
Services []string `json:"services"`
77+
ServiceArgs map[string][]string `json:"service-args"`
6678
}
6779

68-
func (client *Client) doMultiServiceAction(actionName string, services []string) (result json.RawMessage, changeID string, err error) {
80+
func (client *Client) doMultiServiceAction(actionName string, opts *multiActionOptions) (result json.RawMessage, changeID string, err error) {
6981
action := multiActionData{
70-
Action: actionName,
71-
Services: services,
82+
Action: actionName,
83+
Services: opts.services,
84+
ServiceArgs: opts.serviceArgs,
7285
}
7386
data, err := json.Marshal(&action)
7487
if err != nil {

cmd/pebble/cmd_run.go

+38-4
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,11 @@ The run command starts pebble and runs the configured environment.
3939
type cmdRun struct {
4040
clientMixin
4141

42-
CreateDirs bool `long:"create-dirs"`
43-
Hold bool `long:"hold"`
44-
HTTP string `long:"http"`
45-
Verbose bool `short:"v" long:"verbose"`
42+
CreateDirs bool `long:"create-dirs"`
43+
Hold bool `long:"hold"`
44+
HTTP string `long:"http"`
45+
Verbose bool `short:"v" long:"verbose"`
46+
Args [][]string `long:"args" terminator:";"`
4647
}
4748

4849
func init() {
@@ -52,6 +53,7 @@ func init() {
5253
"hold": "Do not start default services automatically",
5354
"http": `Start HTTP API listening on this address (e.g., ":4000")`,
5455
"verbose": "Log all output from services to stdout",
56+
"args": "Pass terminated arguments",
5557
}, nil)
5658
}
5759

@@ -166,6 +168,20 @@ func runDaemon(rcmd *cmdRun, ch chan os.Signal) error {
166168

167169
logger.Debugf("activation done in %v", time.Now().Truncate(time.Millisecond).Sub(t0))
168170

171+
if rcmd.Args != nil {
172+
mappedArgs, err := convertArgs(rcmd.Args)
173+
if err != nil {
174+
logger.Noticef("cannot parse service arguments: %v", err)
175+
}
176+
servopts := client.ServiceOptions{Args: mappedArgs}
177+
changeID, err := rcmd.client.PassServiceArgs(&servopts)
178+
if err != nil {
179+
return fmt.Errorf("cannot pass arguments to services: %v", err)
180+
} else {
181+
logger.Noticef("Passed arguments to services with change %s.", changeID)
182+
}
183+
}
184+
169185
if !rcmd.Hold {
170186
servopts := client.ServiceOptions{}
171187
changeID, err := rcmd.client.AutoStart(&servopts)
@@ -199,3 +215,21 @@ out:
199215

200216
return d.Stop(ch)
201217
}
218+
219+
func convertArgs(args [][]string) (map[string][]string, error) {
220+
mappedArgs := make(map[string][]string)
221+
222+
for _, arg := range args {
223+
if len(arg) < 2 {
224+
continue
225+
}
226+
227+
name := arg[0]
228+
if _, ok := mappedArgs[name]; ok {
229+
return nil, fmt.Errorf("Passing args twice to a service is not supported, in --args %s", name)
230+
}
231+
mappedArgs[name] = append([]string(nil), arg[1:]...)
232+
}
233+
234+
return mappedArgs, nil
235+
}

internal/daemon/api_services.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ func v1GetServices(c *Command, r *http.Request, _ *userState) Response {
6161

6262
func v1PostServices(c *Command, r *http.Request, _ *userState) Response {
6363
var payload struct {
64-
Action string `json:"action"`
65-
Services []string `json:"services"`
64+
Action string `json:"action"`
65+
Services []string `json:"services"`
66+
ServiceArgs map[string][]string `json:"service-args"`
6667
}
6768

6869
decoder := json.NewDecoder(r.Body)
@@ -93,6 +94,10 @@ func v1PostServices(c *Command, r *http.Request, _ *userState) Response {
9394
})
9495
}
9596
payload.Services = services
97+
case "pass-args":
98+
if len(payload.Services) != 0 {
99+
return statusBadRequest("%s accepts no service names", payload.Action)
100+
}
96101
default:
97102
if len(payload.Services) == 0 {
98103
return statusBadRequest("no services to %s provided", payload.Action)
@@ -176,6 +181,11 @@ func v1PostServices(c *Command, r *http.Request, _ *userState) Response {
176181
}
177182
sort.Strings(services)
178183
payload.Services = services
184+
case "pass-args":
185+
err = servmgr.SetServiceArgs(payload.ServiceArgs)
186+
if err != nil {
187+
break
188+
}
179189
default:
180190
return statusBadRequest("action %q is unsupported", payload.Action)
181191
}
@@ -187,7 +197,7 @@ func v1PostServices(c *Command, r *http.Request, _ *userState) Response {
187197
// resolved one. But do use the resolved set for the count.
188198
var summary string
189199
switch {
190-
case len(taskSet.Tasks()) == 0:
200+
case taskSet == nil || len(taskSet.Tasks()) == 0:
191201
// Can happen with a replan that has no services to stop/start. A
192202
// change with no tasks needs to be marked Done manually (normally a
193203
// change is marked Done when its last task is finished).

internal/overlord/servstate/manager.go

+10
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,16 @@ func (m *ServiceManager) StopOrder(services []string) ([]string, error) {
356356
return m.plan.StopOrder(services)
357357
}
358358

359+
func (m *ServiceManager) SetServiceArgs(serviceArgs map[string][]string) error {
360+
releasePlan, err := m.acquirePlan()
361+
if err != nil {
362+
return err
363+
}
364+
defer releasePlan()
365+
366+
return m.plan.SetServiceArgs(serviceArgs)
367+
}
368+
359369
// ServiceLogs returns iterators to the provided services. If last is negative,
360370
// return tail iterators; if last is zero or positive, return head iterators
361371
// going back last elements. Each iterator must be closed via the Close method.

internal/plan/plan.go

+27
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,11 @@ func (s *Service) GetCommand() ([]string, error) {
211211
return fargs, nil
212212
}
213213

214+
func (s *Service) setArgs(args []string) error {
215+
s.cmdArgs = append([]string(nil), args...)
216+
return nil
217+
}
218+
214219
func (s *Service) checkCommand() error {
215220
args, err := shlex.Split(s.Command)
216221
if err != nil {
@@ -672,6 +677,28 @@ func (p *Plan) StopOrder(names []string) ([]string, error) {
672677
return order(p.Services, names, true)
673678
}
674679

680+
func (p *Plan) SetServiceArgs(serviceArgs map[string][]string) error {
681+
for svcName, svcArgs := range serviceArgs {
682+
service, ok := p.Services[svcName]
683+
if !ok {
684+
return fmt.Errorf("Service %s does not exist in the plan (arguments passed via --args)", svcName)
685+
}
686+
if err := service.setArgs(svcArgs); err != nil {
687+
return err
688+
}
689+
for i := len(p.Layers) - 1; i >= 0; i-- {
690+
layerService, ok := p.Layers[i].Services[svcName]
691+
if ok {
692+
if err := layerService.setArgs(svcArgs); err != nil {
693+
return err
694+
}
695+
break
696+
}
697+
}
698+
}
699+
return nil
700+
}
701+
675702
func order(services map[string]*Service, names []string, stop bool) ([]string, error) {
676703
// For stop, create a list of reversed dependencies.
677704
predecessors := map[string][]string(nil)

0 commit comments

Comments
 (0)