Skip to content

Commit

Permalink
feat(cli): enable extended client access (#285)
Browse files Browse the repository at this point in the history
This patch gets rid of the clientMixin in command structs in favor of passing a CmdOptions struct to the New (formerly Builder) function in CmdInfo.

This change allows the client to be extended as shown in the following example:

// Example command implementation

type cmdFoo struct {
        /* ... */
	client *MyClient
}

func init() {
	AddCommand(&CmdInfo{
		/* ... */
		New: func(opts *CmdOptions) flags.Commander {
			return &cmdFoo{client: &MyClient{opts.Client}}
		},
	})
}

func (cmd *cmdFoo) Execute(args []string) error {
        return cmd.client.Foo()
}

// Example client implementation

import "github.com/canonical/pebble/client"

type MyClient struct {
        *client.Client
}

func (c *MyClient) Foo() error {
        /* ... */
}
  • Loading branch information
anpep authored Aug 25, 2023
1 parent 76414a1 commit d594346
Show file tree
Hide file tree
Showing 24 changed files with 136 additions and 89 deletions.
35 changes: 9 additions & 26 deletions internals/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ const defaultPebbleDir = "/var/lib/pebble/default"
// ErrExtraArgs is returned if extra arguments to a command are found
var ErrExtraArgs = fmt.Errorf("too many arguments for command")

// CmdOptions exposes state made accessible during command execution.
type CmdOptions struct {
Client *client.Client
Parser *flags.Parser
}

// CmdInfo holds information needed by the CLI to execute commands and
// populate entries in the help manual.
type CmdInfo struct {
Expand All @@ -67,9 +73,8 @@ type CmdInfo struct {
// command, and in the Pebble man page.
Description string

// Builder is a function that creates a new instance of the command
// struct containing an Execute(args []string) implementation.
Builder func() flags.Commander
// New is a function that creates a new instance of the command.
New func(*CmdOptions) flags.Commander

// ArgsHelp (optional) contains help about the command-line arguments
// (including options) supported by the command.
Expand Down Expand Up @@ -139,22 +144,6 @@ func fixupArg(optName string) string {
return optName
}

type clientSetter interface {
setClient(*client.Client)
}

type clientMixin struct {
client *client.Client
}

func (ch *clientMixin) setClient(cli *client.Client) {
ch.client = cli
}

type parserSetter interface {
setParser(*flags.Parser)
}

type defaultOptions struct {
Version func() `long:"version" hidden:"yes" description:"Print the version and exit"`
}
Expand Down Expand Up @@ -192,13 +181,7 @@ func Parser(cli *client.Client) *flags.Parser {

// Add all commands
for _, c := range commands {
obj := c.Builder()
if x, ok := obj.(clientSetter); ok {
x.setClient(cli)
}
if x, ok := obj.(parserSetter); ok {
x.setParser(parser)
}
obj := c.New(&CmdOptions{Client: cli, Parser: parser})

var target *flags.Command
if c.Debug {
Expand Down
7 changes: 5 additions & 2 deletions internals/cli/cmd_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ label (or append if the label is not found).
`

type cmdAdd struct {
clientMixin
client *client.Client

Combine bool `long:"combine"`
Positional struct {
Label string `positional-arg-name:"<label>" required:"1"`
Expand All @@ -48,7 +49,9 @@ func init() {
ArgsHelp: map[string]string{
"--combine": "Combine the new layer with an existing layer that has the given label (default is to append)",
},
Builder: func() flags.Commander { return &cmdAdd{} },
New: func(opts *CmdOptions) flags.Commander {
return &cmdAdd{client: opts.Client}
},
})
}

Expand Down
8 changes: 6 additions & 2 deletions internals/cli/cmd_autostart.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ to start by default.
`

type cmdAutoStart struct {
client *client.Client

waitMixin
}

Expand All @@ -36,7 +38,9 @@ func init() {
Summary: cmdAutoStartSummary,
Description: cmdAutoStartDescription,
ArgsHelp: waitArgsHelp,
Builder: func() flags.Commander { return &cmdAutoStart{} },
New: func(opts *CmdOptions) flags.Commander {
return &cmdAutoStart{client: opts.Client}
},
})
}

Expand All @@ -51,7 +55,7 @@ func (cmd cmdAutoStart) Execute(args []string) error {
return err
}

if _, err := cmd.wait(changeID); err != nil {
if _, err := cmd.wait(cmd.client, changeID); err != nil {
if err == noWait {
return nil
}
Expand Down
15 changes: 11 additions & 4 deletions internals/cli/cmd_changes.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ The changes command displays a summary of system changes performed recently.
`

type cmdChanges struct {
clientMixin
client *client.Client

timeMixin
Positional struct {
Service string `positional-arg-name:"<service>"`
Expand All @@ -44,6 +45,8 @@ change that happened recently.
`

type cmdTasks struct {
client *client.Client

timeMixin
changeIDMixin
}
Expand All @@ -54,14 +57,18 @@ func init() {
Summary: cmdChangesSummary,
Description: cmdChangesDescription,
ArgsHelp: timeArgsHelp,
Builder: func() flags.Commander { return &cmdChanges{} },
New: func(opts *CmdOptions) flags.Commander {
return &cmdChanges{client: opts.Client}
},
})
AddCommand(&CmdInfo{
Name: "tasks",
Summary: cmdTasksSummary,
Description: cmdTasksDescription,
ArgsHelp: merge(changeIDMixinArgsHelp, timeArgsHelp),
Builder: func() flags.Commander { return &cmdTasks{} },
New: func(opts *CmdOptions) flags.Commander {
return &cmdTasks{client: opts.Client}
},
})
}

Expand Down Expand Up @@ -133,7 +140,7 @@ func (c *cmdChanges) Execute(args []string) error {
}

func (c *cmdTasks) Execute([]string) error {
chid, err := c.GetChangeID()
chid, err := c.GetChangeID(c.client)
if err != nil {
if err == noChangeFoundOK {
return nil
Expand Down
7 changes: 5 additions & 2 deletions internals/cli/cmd_checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ arguments.
`

type cmdChecks struct {
clientMixin
client *client.Client

Level string `long:"level"`
Positional struct {
Checks []string `positional-arg-name:"<check>"`
Expand All @@ -45,7 +46,9 @@ func init() {
ArgsHelp: map[string]string{
"--level": `Check level to filter for ("alive" or "ready")`,
},
Builder: func() flags.Commander { return &cmdChecks{} },
New: func(opts *CmdOptions) flags.Commander {
return &cmdChecks{client: opts.Client}
},
})
}

Expand Down
16 changes: 8 additions & 8 deletions internals/cli/cmd_enter.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (

"github.com/canonical/go-flags"

"github.com/canonical/pebble/client"
"github.com/canonical/pebble/internals/logger"
)

Expand Down Expand Up @@ -46,21 +47,24 @@ These subcommands are currently supported:
`

type cmdEnter struct {
clientMixin
client *client.Client
parser *flags.Parser

sharedRunEnterOpts
Run bool `long:"run"`
Positional struct {
Cmd []string `positional-arg-name:"<subcommand>"`
} `positional-args:"yes"`
parser *flags.Parser
}

func init() {
AddCommand(&CmdInfo{
Name: "enter",
Summary: cmdEnterSummary,
Description: cmdEnterDescription,
Builder: func() flags.Commander { return &cmdEnter{} },
New: func(opts *CmdOptions) flags.Commander {
return &cmdEnter{client: opts.Client, parser: opts.Parser}
},
ArgsHelp: merge(sharedRunEnterArgsHelp, map[string]string{
"--run": "Start default services before executing subcommand",
}),
Expand Down Expand Up @@ -104,8 +108,8 @@ func (cmd *cmdEnter) Execute(args []string) error {

runCmd := cmdRun{
sharedRunEnterOpts: cmd.sharedRunEnterOpts,
client: cmd.client,
}
runCmd.setClient(cmd.client)

if len(cmd.Positional.Cmd) == 0 {
runCmd.run(nil)
Expand Down Expand Up @@ -195,7 +199,3 @@ func (cmd *cmdEnter) Execute(args []string) error {

return err
}

func (cmd *cmdEnter) setParser(parser *flags.Parser) {
cmd.parser = parser
}
7 changes: 5 additions & 2 deletions internals/cli/cmd_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ pebble exec --timeout 10s -- echo -n foo bar
`

type cmdExec struct {
clientMixin
client *client.Client

WorkingDir string `short:"w"`
Env []string `long:"env"`
UserID *int `long:"uid"`
Expand Down Expand Up @@ -81,7 +82,9 @@ func init() {
"-I": "Disable interactive mode and use a pipe for stdin",
},
PassAfterNonOption: true,
Builder: func() flags.Commander { return &cmdExec{} },
New: func(opts *CmdOptions) flags.Commander {
return &cmdExec{client: opts.Client}
},
})
}

Expand Down
7 changes: 5 additions & 2 deletions internals/cli/cmd_help.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ The help command displays information about commands.
`

type cmdHelp struct {
parser *flags.Parser

All bool `long:"all"`
Manpage bool `long:"man" hidden:"true"`
Positional struct {
Subs []string `positional-arg-name:"<command>"`
} `positional-args:"yes"`
parser *flags.Parser
}

func init() {
Expand All @@ -48,7 +49,9 @@ func init() {
"--all": "Show a short summary of all commands",
"--man": "Generate the manpage",
},
Builder: func() flags.Commander { return &cmdHelp{} },
New: func(opts *CmdOptions) flags.Commander {
return &cmdHelp{parser: opts.Parser}
},
})
}

Expand Down
7 changes: 5 additions & 2 deletions internals/cli/cmd_logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ if none are specified) and displays them in chronological order.
`

type cmdLogs struct {
clientMixin
client *client.Client

Follow bool `short:"f" long:"follow"`
Format string `long:"format"`
N string `short:"n"`
Expand All @@ -53,7 +54,9 @@ func init() {
"--format": "Output format: \"text\" (default) or \"json\" (JSON lines).",
"-n": "Number of logs to show (before following); defaults to 30.\nIf 'all', show all buffered logs.",
},
Builder: func() flags.Commander { return &cmdLogs{} },
New: func(opts *CmdOptions) flags.Commander {
return &cmdLogs{client: opts.Client}
},
})
}

Expand Down
9 changes: 5 additions & 4 deletions internals/cli/cmd_ls.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,11 @@ may be specified for the last path element.
`

type cmdLs struct {
clientMixin
timeMixin
client *client.Client

timeMixin
Directory bool `short:"d"`
LongFormat bool `short:"l"`

Positional struct {
Path string `positional-arg-name:"<path>"`
} `positional-args:"yes" required:"yes"`
Expand All @@ -53,7 +52,9 @@ func init() {
"-d": "List matching entries themselves, not directory contents",
"-l": "Use a long listing format",
}),
Builder: func() flags.Commander { return &cmdLs{} },
New: func(opts *CmdOptions) flags.Commander {
return &cmdLs{client: opts.Client}
},
})
}

Expand Down
9 changes: 5 additions & 4 deletions internals/cli/cmd_mkdir.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,15 @@ The mkdir command creates the specified directory.
`

type cmdMkdir struct {
clientMixin
client *client.Client

MakeParents bool `short:"p"`
Permissions string `short:"m"`
UserID *int `long:"uid"`
User string `long:"user"`
GroupID *int `long:"gid"`
Group string `long:"group"`

Positional struct {
Positional struct {
Path string `positional-arg-name:"<path>"`
} `positional-args:"yes" required:"yes"`
}
Expand All @@ -49,7 +48,6 @@ func init() {
Name: "mkdir",
Summary: cmdMkdirSummary,
Description: cmdMkdirDescription,
Builder: func() flags.Commander { return &cmdMkdir{} },
ArgsHelp: map[string]string{
"-p": "Create parent directories as needed",
"-m": "Set permissions (e.g. 0644)",
Expand All @@ -58,6 +56,9 @@ func init() {
"--gid": "Use specified group ID",
"--group": "Use specified group name",
},
New: func(opts *CmdOptions) flags.Commander {
return &cmdMkdir{client: opts.Client}
},
})
}

Expand Down
6 changes: 4 additions & 2 deletions internals/cli/cmd_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@ format. Layers are combined according to the override rules defined in them.
`

type cmdPlan struct {
clientMixin
client *client.Client
}

func init() {
AddCommand(&CmdInfo{
Name: "plan",
Summary: cmdPlanSummary,
Description: cmdPlanDescription,
Builder: func() flags.Commander { return &cmdPlan{} },
New: func(opts *CmdOptions) flags.Commander {
return &cmdPlan{client: opts.Client}
},
})
}

Expand Down
Loading

0 comments on commit d594346

Please sign in to comment.