Skip to content
This repository was archived by the owner on Jan 21, 2020. It is now read-only.

Commit d4de67a

Browse files
author
David Chung
authored
CLI command to offer info on Plugin API and template functions (#387)
Signed-off-by: David Chung <[email protected]>
1 parent ea9c59d commit d4de67a

File tree

18 files changed

+553
-304
lines changed

18 files changed

+553
-304
lines changed

cmd/cli/info.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
"github.com/docker/infrakit/pkg/discovery"
8+
"github.com/docker/infrakit/pkg/plugin"
9+
"github.com/docker/infrakit/pkg/rpc/client"
10+
"github.com/docker/infrakit/pkg/template"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
// infoCommand creates a cobra Command that prints build version information.
15+
func infoCommand(plugins func() discovery.Plugins) *cobra.Command {
16+
cmd := &cobra.Command{
17+
Use: "info",
18+
Short: "print plugin info",
19+
PersistentPreRunE: func(c *cobra.Command, args []string) error {
20+
// Cobra doesn't go past one level of PersistentRunE
21+
return upTree(c, func(x *cobra.Command, argv []string) error {
22+
if x.PersistentPreRunE != nil {
23+
return x.PersistentPreRunE(x, argv)
24+
}
25+
return nil
26+
})
27+
},
28+
}
29+
name := cmd.PersistentFlags().String("name", "", "Name of plugin")
30+
raw := cmd.PersistentFlags().Bool("raw", false, "True to show raw data")
31+
32+
api := &cobra.Command{
33+
Use: "api",
34+
Short: "Show api / RPC interface supported by the plugin of the given name",
35+
}
36+
37+
templateFuncs := &cobra.Command{
38+
Use: "template",
39+
Short: "Show template functions supported by the plugin, if the plugin uses template for configuration.",
40+
}
41+
cmd.AddCommand(api, templateFuncs)
42+
43+
api.RunE = func(cmd *cobra.Command, args []string) error {
44+
endpoint, err := plugins().Find(plugin.Name(*name))
45+
if err != nil {
46+
return err
47+
}
48+
49+
infoClient := client.NewPluginInfoClient(endpoint.Address)
50+
info, err := infoClient.GetInfo()
51+
if err != nil {
52+
return err
53+
}
54+
55+
if *raw {
56+
buff, err := json.MarshalIndent(info, "", " ")
57+
if err != nil {
58+
return err
59+
}
60+
61+
fmt.Println(string(buff))
62+
return nil
63+
}
64+
// render a view using template
65+
renderer, err := template.NewTemplate("str://"+apiViewTemplate, template.Options{})
66+
if err != nil {
67+
return err
68+
}
69+
70+
view, err := renderer.AddDef("plugin", *name).Render(info)
71+
if err != nil {
72+
return err
73+
}
74+
75+
fmt.Print(view)
76+
return nil
77+
}
78+
79+
templateFuncs.RunE = func(cmd *cobra.Command, args []string) error {
80+
endpoint, err := plugins().Find(plugin.Name(*name))
81+
if err != nil {
82+
return err
83+
}
84+
85+
infoClient := client.NewPluginInfoClient(endpoint.Address)
86+
info, err := infoClient.GetFunctions()
87+
if err != nil {
88+
return err
89+
}
90+
91+
if *raw {
92+
buff, err := json.MarshalIndent(info, "", " ")
93+
if err != nil {
94+
return err
95+
}
96+
97+
fmt.Println(string(buff))
98+
return nil
99+
}
100+
// render a view using template
101+
renderer, err := template.NewTemplate("str://"+funcsViewTemplate, template.Options{})
102+
if err != nil {
103+
return err
104+
}
105+
106+
view, err := renderer.AddDef("plugin", *name).Render(info)
107+
if err != nil {
108+
return err
109+
}
110+
111+
fmt.Print(view)
112+
return nil
113+
}
114+
115+
return cmd
116+
}
117+
118+
const (
119+
apiViewTemplate = `
120+
Plugin: {{ref "plugin"}}
121+
Implements: {{range $spi := .Implements}}{{$spi.Name}}/{{$spi.Version}} {{end}}
122+
Interfaces: {{range $iface := .Interfaces}}
123+
SPI: {{$iface.Name}}/{{$iface.Version}}
124+
RPC: {{range $method := $iface.Methods}}
125+
Method: {{$method.Request | q "method" }}
126+
Request:
127+
{{$method.Request | to_json_format " " " "}}
128+
129+
Response:
130+
{{$method.Response | to_json_format " " " "}}
131+
132+
-------------------------
133+
{{end}}
134+
{{end}}
135+
`
136+
137+
funcsViewTemplate = `
138+
{{range $category, $functions := .}}
139+
{{ref "plugin"}}/{{$category}} _________________________________________________________________________________________
140+
{{range $f := $functions}}
141+
Name: {{$f.Name}}
142+
Description: {{join "\n " $f.Description}}
143+
Function: {{$f.Function}}
144+
Usage: {{$f.Usage}}
145+
146+
-------------------------
147+
{{end}}
148+
149+
{{end}}
150+
`
151+
)

cmd/cli/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ func main() {
3939
return d
4040
}
4141

42-
cmd.AddCommand(cli.VersionCommand(), cli.InfoCommand(f))
43-
42+
cmd.AddCommand(cli.VersionCommand())
43+
cmd.AddCommand(infoCommand(f))
4444
cmd.AddCommand(templateCommand(f))
4545
cmd.AddCommand(managerCommand(f))
4646
cmd.AddCommand(pluginCommand(f), instancePluginCommand(f), groupPluginCommand(f), flavorPluginCommand(f))

examples/flavor/swarm/flavor.go

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -306,28 +306,20 @@ type templateContext struct {
306306
func (c *templateContext) Funcs() []template.Function {
307307
return []template.Function{
308308
{
309-
Name: "SWARM_CONNECT_RETRIES",
310-
Description: "Connect to the swarm manager",
311-
Func: func(retries int, wait string) interface{} {
312-
c.retries = retries
313-
poll, err := time.ParseDuration(wait)
314-
if err != nil {
315-
poll = 1 * time.Minute
316-
}
317-
c.poll = poll
318-
return ""
309+
Name: "SPEC",
310+
Description: []string{
311+
"The flavor spec as found in Properties field of the config JSON",
319312
},
320-
},
321-
{
322-
Name: "SPEC",
323-
Description: "The flavor spec as found in Properties field of the config JSON",
324313
Func: func() interface{} {
325314
return c.flavorSpec
326315
},
327316
},
328317
{
329-
Name: "INSTANCE_LOGICAL_ID",
330-
Description: "The logical id for the instance being prepared; can be empty if no logical ids are set (cattle).",
318+
Name: "INSTANCE_LOGICAL_ID",
319+
Description: []string{
320+
"The logical id for the instance being prepared.",
321+
"For cattle (instances with no logical id in allocations), this is empty.",
322+
},
331323
Func: func() string {
332324
if c.instanceSpec.LogicalID != nil {
333325
return string(*c.instanceSpec.LogicalID)
@@ -337,21 +329,21 @@ func (c *templateContext) Funcs() []template.Function {
337329
},
338330
{
339331
Name: "ALLOCATIONS",
340-
Description: "The allocations contain fields such as the size of the group or the list of logical ids.",
332+
Description: []string{"The allocations contain fields such as the size of the group or the list of logical ids."},
341333
Func: func() interface{} {
342334
return c.allocation
343335
},
344336
},
345337
{
346338
Name: "INFRAKIT_LABELS",
347-
Description: "The label name to use for linking an InfraKit managed resource somewhere else.",
339+
Description: []string{"The Docker engine labels to be applied for linking the Docker engine to this instance."},
348340
Func: func() []string {
349341
return c.link.KVPairs()
350342
},
351343
},
352344
{
353345
Name: "SWARM_MANAGER_IP",
354-
Description: "The label name to use for linking an InfraKit managed resource somewhere else.",
346+
Description: []string{"IP of the Swarm manager / leader"},
355347
Func: func() (string, error) {
356348
if c.nodeInfo == nil {
357349
return "", fmt.Errorf("cannot prepare: no node info")
@@ -364,7 +356,7 @@ func (c *templateContext) Funcs() []template.Function {
364356
},
365357
{
366358
Name: "SWARM_INITIALIZED",
367-
Description: "Returns true if the swarm has been initialized.",
359+
Description: []string{"Returns true if the swarm has been initialized."},
368360
Func: func() bool {
369361
if c.nodeInfo == nil {
370362
return false
@@ -374,7 +366,7 @@ func (c *templateContext) Funcs() []template.Function {
374366
},
375367
{
376368
Name: "SWARM_JOIN_TOKENS",
377-
Description: "Returns the swarm JoinTokens object, with either .Manager or .Worker fields",
369+
Description: []string{"Returns the swarm JoinTokens object, with either .Manager or .Worker fields"},
378370
Func: func() (interface{}, error) {
379371
if c.swarmStatus == nil {
380372
return nil, fmt.Errorf("cannot prepare: no swarm status")
@@ -384,7 +376,7 @@ func (c *templateContext) Funcs() []template.Function {
384376
},
385377
{
386378
Name: "SWARM_CLUSTER_ID",
387-
Description: "Returns the swarm cluster UUID",
379+
Description: []string{"Returns the swarm cluster UUID"},
388380
Func: func() (interface{}, error) {
389381
if c.swarmStatus == nil {
390382
return nil, fmt.Errorf("cannot prepare: no swarm status")

pkg/cli/info.go

Lines changed: 0 additions & 41 deletions
This file was deleted.

pkg/plugin/plugin.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package plugin
22

33
import (
44
"github.com/docker/infrakit/pkg/spi"
5+
"github.com/docker/infrakit/pkg/template"
56
"github.com/docker/infrakit/pkg/types"
67
)
78

@@ -18,8 +19,11 @@ type Spec struct {
1819
// Informer is the interface that gives information about the plugin such as version and interface methods
1920
type Informer interface {
2021

21-
// GetMeta returns metadata about the plugin
22+
// GetInfo returns metadata about the plugin
2223
GetInfo() (Info, error)
24+
25+
// GetFunctions returns metadata about the plugin's template functions, if the plugin supports templating.
26+
GetFunctions() (map[string][]template.Function, error)
2327
}
2428

2529
// Info is metadata for the plugin

pkg/rpc/client/info.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/docker/infrakit/pkg/plugin"
99
"github.com/docker/infrakit/pkg/rpc"
10+
"github.com/docker/infrakit/pkg/template"
1011
)
1112

1213
// NewPluginInfoClient returns a plugin informer that can give metadata about a plugin
@@ -33,3 +34,15 @@ func (i *InfoClient) GetInfo() (plugin.Info, error) {
3334
err = json.NewDecoder(resp.Body).Decode(&meta)
3435
return meta, err
3536
}
37+
38+
// GetFunctions returns metadata about the plugin's template functions, if the plugin supports templating.
39+
func (i *InfoClient) GetFunctions() (map[string][]template.Function, error) {
40+
meta := map[string][]template.Function{}
41+
resp, err := i.client.Get("http://d" + rpc.FunctionsURL)
42+
if err != nil {
43+
return meta, err
44+
}
45+
defer resp.Body.Close()
46+
err = json.NewDecoder(resp.Body).Decode(&meta)
47+
return meta, err
48+
}

0 commit comments

Comments
 (0)