~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/romulus/cmd/listplans/list_plans.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2016 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
// The listplans package contains implementation of the command that
 
5
// can be used to list plans that are available for a charm.
 
6
package listplans
 
7
 
 
8
import (
 
9
        "bytes"
 
10
        "fmt"
 
11
        "io"
 
12
        "io/ioutil"
 
13
        "strings"
 
14
        "text/tabwriter"
 
15
 
 
16
        "github.com/gosuri/uitable"
 
17
        "github.com/juju/cmd"
 
18
        "github.com/juju/errors"
 
19
        "github.com/juju/juju/cmd/modelcmd"
 
20
        "gopkg.in/macaroon-bakery.v1/httpbakery"
 
21
        "gopkg.in/yaml.v2"
 
22
        "launchpad.net/gnuflag"
 
23
 
 
24
        api "github.com/juju/romulus/api/plan"
 
25
        rcmd "github.com/juju/romulus/cmd"
 
26
        wireformat "github.com/juju/romulus/wireformat/plan"
 
27
)
 
28
 
 
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)
 
33
}
 
34
 
 
35
var newClient = func(client *httpbakery.Client) (apiClient, error) {
 
36
        return api.NewClient(api.HTTPClient(client))
 
37
}
 
38
 
 
39
const listPlansDoc = `
 
40
List plans available for the specified charm.
 
41
 
 
42
Examples:
 
43
    juju plans cs:webapp
 
44
`
 
45
 
 
46
// ListPlansCommand retrieves plans that are available for the specified charm
 
47
type ListPlansCommand struct {
 
48
        modelcmd.JujuCommandBase
 
49
 
 
50
        out      cmd.Output
 
51
        CharmURL string
 
52
 
 
53
        CharmResolver rcmd.CharmResolver
 
54
}
 
55
 
 
56
// NewListPlansCommand creates a new ListPlansCommand.
 
57
func NewListPlansCommand() modelcmd.CommandBase {
 
58
        return &ListPlansCommand{
 
59
                CharmResolver: rcmd.NewCharmStoreResolver(),
 
60
        }
 
61
}
 
62
 
 
63
// Info implements Command.Info.
 
64
func (c *ListPlansCommand) Info() *cmd.Info {
 
65
        return &cmd.Info{
 
66
                Name:    "plans",
 
67
                Args:    "",
 
68
                Purpose: "List plans.",
 
69
                Doc:     listPlansDoc,
 
70
                Aliases: []string{"list-plans"},
 
71
        }
 
72
}
 
73
 
 
74
// Init reads and verifies the cli arguments for the ListPlansCommand
 
75
func (c *ListPlansCommand) Init(args []string) error {
 
76
        if len(args) == 0 {
 
77
                return errors.New("missing arguments")
 
78
        }
 
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, ","))
 
82
        }
 
83
        c.CharmURL = charmURL
 
84
        return nil
 
85
}
 
86
 
 
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,
 
97
        })
 
98
}
 
99
 
 
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()
 
105
        if err != nil {
 
106
                return errors.Annotate(err, "failed to create an http client")
 
107
        }
 
108
 
 
109
        resolvedUrl, err := c.CharmResolver.Resolve(client.VisitWebPage, client.Client, c.CharmURL)
 
110
        if err != nil {
 
111
                return errors.Annotatef(err, "failed to resolve charmURL %v", c.CharmURL)
 
112
        }
 
113
        c.CharmURL = resolvedUrl
 
114
 
 
115
        apiClient, err := newClient(client)
 
116
        if err != nil {
 
117
                return errors.Annotate(err, "failed to create a plan API client")
 
118
        }
 
119
 
 
120
        plans, err := apiClient.GetAssociatedPlans(c.CharmURL)
 
121
        if err != nil {
 
122
                return errors.Annotate(err, "failed to retrieve plans")
 
123
        }
 
124
 
 
125
        output := make([]plan, len(plans))
 
126
        for i, p := range plans {
 
127
                outputPlan := plan{
 
128
                        URL: p.URL,
 
129
                }
 
130
                def, err := readPlan(bytes.NewBufferString(p.Definition))
 
131
                if err != nil {
 
132
                        return errors.Annotate(err, "failed to parse plan definition")
 
133
                }
 
134
                if def.Description != nil {
 
135
                        outputPlan.Price = def.Description.Price
 
136
                        outputPlan.Description = def.Description.Text
 
137
                }
 
138
                output[i] = outputPlan
 
139
        }
 
140
        err = c.out.Write(ctx, output)
 
141
        if err != nil {
 
142
                return errors.Trace(err)
 
143
        }
 
144
 
 
145
        return nil
 
146
}
 
147
 
 
148
type plan struct {
 
149
        URL         string `json:"plan" yaml:"plan"`
 
150
        Price       string `json:"price" yaml:"price"`
 
151
        Description string `json:"description" yaml:"description"`
 
152
}
 
153
 
 
154
// formatSummary returns a summary of available plans.
 
155
func formatSummary(value interface{}) ([]byte, error) {
 
156
        plans, ok := value.([]plan)
 
157
        if !ok {
 
158
                return nil, errors.Errorf("expected value of type %T, got %T", plans, value)
 
159
        }
 
160
        var out bytes.Buffer
 
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)
 
165
                }
 
166
                fmt.Fprintln(tw)
 
167
        }
 
168
        p("PLAN", "PRICE")
 
169
        for _, plan := range plans {
 
170
                p(plan.URL, plan.Price)
 
171
        }
 
172
        err := tw.Flush()
 
173
        if err != nil {
 
174
                return nil, errors.Trace(err)
 
175
        }
 
176
 
 
177
        return out.Bytes(), nil
 
178
}
 
179
 
 
180
// formatTabular returns a tabular summary of available plans.
 
181
func formatTabular(value interface{}) ([]byte, error) {
 
182
        plans, ok := value.([]plan)
 
183
        if !ok {
 
184
                return nil, errors.Errorf("expected value of type %T, got %T", plans, value)
 
185
        }
 
186
 
 
187
        table := uitable.New()
 
188
        table.MaxColWidth = 50
 
189
        table.Wrap = true
 
190
 
 
191
        table.AddRow("PLAN", "PRICE", "DESCRIPTION")
 
192
        for _, plan := range plans {
 
193
                table.AddRow(plan.URL, plan.Price, plan.Description)
 
194
        }
 
195
 
 
196
        return []byte(table.String()), nil
 
197
}
 
198
 
 
199
type planModel struct {
 
200
        Description *descriptionModel `json:"description,omitempty"`
 
201
}
 
202
 
 
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"`
 
207
}
 
208
 
 
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)
 
212
        if err != nil {
 
213
                return
 
214
        }
 
215
 
 
216
        var doc planModel
 
217
        err = yaml.Unmarshal(data, &doc)
 
218
        if err != nil {
 
219
                return
 
220
        }
 
221
        return &doc, nil
 
222
}