1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
4
// The listplans package contains implementation of the command that
5
// can be used to list plans that are available for a charm.
16
"github.com/gosuri/uitable"
18
"github.com/juju/errors"
19
"github.com/juju/juju/cmd/modelcmd"
20
"gopkg.in/macaroon-bakery.v1/httpbakery"
22
"launchpad.net/gnuflag"
24
api "github.com/juju/romulus/api/plan"
25
rcmd "github.com/juju/romulus/cmd"
26
wireformat "github.com/juju/romulus/wireformat/plan"
29
// apiClient defines the interface of the plan api client need by this command.
30
type apiClient interface {
31
// GetAssociatedPlans returns the plans associated with the charm.
32
GetAssociatedPlans(charmURL string) ([]wireformat.Plan, error)
35
var newClient = func(client *httpbakery.Client) (apiClient, error) {
36
return api.NewClient(api.HTTPClient(client))
39
const listPlansDoc = `
40
List plans available for the specified charm.
46
// ListPlansCommand retrieves plans that are available for the specified charm
47
type ListPlansCommand struct {
48
modelcmd.JujuCommandBase
53
CharmResolver rcmd.CharmResolver
56
// NewListPlansCommand creates a new ListPlansCommand.
57
func NewListPlansCommand() modelcmd.CommandBase {
58
return &ListPlansCommand{
59
CharmResolver: rcmd.NewCharmStoreResolver(),
63
// Info implements Command.Info.
64
func (c *ListPlansCommand) Info() *cmd.Info {
68
Purpose: "List plans.",
70
Aliases: []string{"list-plans"},
74
// Init reads and verifies the cli arguments for the ListPlansCommand
75
func (c *ListPlansCommand) Init(args []string) error {
77
return errors.New("missing arguments")
79
charmURL, args := args[0], args[1:]
80
if err := cmd.CheckEmpty(args); err != nil {
81
return errors.Errorf("unknown command line arguments: " + strings.Join(args, ","))
87
// SetFlags implements Command.SetFlags.
88
func (c *ListPlansCommand) SetFlags(f *gnuflag.FlagSet) {
89
c.JujuCommandBase.SetFlags(f)
90
defaultFormat := "tabular"
91
c.out.AddFlags(f, defaultFormat, map[string]cmd.Formatter{
92
"yaml": cmd.FormatYaml,
93
"json": cmd.FormatJson,
94
"smart": cmd.FormatSmart,
95
"summary": formatSummary,
96
"tabular": formatTabular,
100
// Run implements Command.Run.
101
// Retrieves the plan from the plans service. The set of plans to be
102
// retrieved can be limited using the plan and isv flags.
103
func (c *ListPlansCommand) Run(ctx *cmd.Context) (rErr error) {
104
client, err := c.BakeryClient()
106
return errors.Annotate(err, "failed to create an http client")
109
resolvedUrl, err := c.CharmResolver.Resolve(client.VisitWebPage, client.Client, c.CharmURL)
111
return errors.Annotatef(err, "failed to resolve charmURL %v", c.CharmURL)
113
c.CharmURL = resolvedUrl
115
apiClient, err := newClient(client)
117
return errors.Annotate(err, "failed to create a plan API client")
120
plans, err := apiClient.GetAssociatedPlans(c.CharmURL)
122
return errors.Annotate(err, "failed to retrieve plans")
125
output := make([]plan, len(plans))
126
for i, p := range plans {
130
def, err := readPlan(bytes.NewBufferString(p.Definition))
132
return errors.Annotate(err, "failed to parse plan definition")
134
if def.Description != nil {
135
outputPlan.Price = def.Description.Price
136
outputPlan.Description = def.Description.Text
138
output[i] = outputPlan
140
err = c.out.Write(ctx, output)
142
return errors.Trace(err)
149
URL string `json:"plan" yaml:"plan"`
150
Price string `json:"price" yaml:"price"`
151
Description string `json:"description" yaml:"description"`
154
// formatSummary returns a summary of available plans.
155
func formatSummary(value interface{}) ([]byte, error) {
156
plans, ok := value.([]plan)
158
return nil, errors.Errorf("expected value of type %T, got %T", plans, value)
161
tw := tabwriter.NewWriter(&out, 0, 1, 1, ' ', 0)
162
p := func(values ...interface{}) {
163
for _, v := range values {
164
fmt.Fprintf(tw, "%s\t", v)
169
for _, plan := range plans {
170
p(plan.URL, plan.Price)
174
return nil, errors.Trace(err)
177
return out.Bytes(), nil
180
// formatTabular returns a tabular summary of available plans.
181
func formatTabular(value interface{}) ([]byte, error) {
182
plans, ok := value.([]plan)
184
return nil, errors.Errorf("expected value of type %T, got %T", plans, value)
187
table := uitable.New()
188
table.MaxColWidth = 50
191
table.AddRow("PLAN", "PRICE", "DESCRIPTION")
192
for _, plan := range plans {
193
table.AddRow(plan.URL, plan.Price, plan.Description)
196
return []byte(table.String()), nil
199
type planModel struct {
200
Description *descriptionModel `json:"description,omitempty"`
203
// descriptionModel provides a human readable description of the plan.
204
type descriptionModel struct {
205
Price string `json:"price,omitempty"`
206
Text string `json:"text,omitempty"`
209
// readPlan reads, parses and returns a planModel struct representation.
210
func readPlan(r io.Reader) (plan *planModel, err error) {
211
data, err := ioutil.ReadAll(r)
217
err = yaml.Unmarshal(data, &doc)