github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/romulus/listplans/list_plans.go (about) 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 15 "github.com/gosuri/uitable" 16 "github.com/juju/cmd" 17 "github.com/juju/errors" 18 "github.com/juju/gnuflag" 19 api "github.com/juju/romulus/api/plan" 20 wireformat "github.com/juju/romulus/wireformat/plan" 21 "gopkg.in/macaroon-bakery.v1/httpbakery" 22 "gopkg.in/yaml.v2" 23 24 rcmd "github.com/juju/juju/cmd/juju/romulus" 25 "github.com/juju/juju/cmd/modelcmd" 26 "github.com/juju/juju/cmd/output" 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 141 if len(output) == 0 && c.out.Name() == "tabular" { 142 ctx.Infof("No plans to display.") 143 } 144 145 err = c.out.Write(ctx, output) 146 if err != nil { 147 return errors.Trace(err) 148 } 149 150 return nil 151 } 152 153 type plan struct { 154 URL string `json:"plan" yaml:"plan"` 155 Price string `json:"price" yaml:"price"` 156 Description string `json:"description" yaml:"description"` 157 } 158 159 // formatSummary returns a summary of available plans. 160 func formatSummary(writer io.Writer, value interface{}) error { 161 plans, ok := value.([]plan) 162 if !ok { 163 return errors.Errorf("expected value of type %T, got %T", plans, value) 164 } 165 tw := output.TabWriter(writer) 166 p := func(values ...interface{}) { 167 for _, v := range values { 168 fmt.Fprintf(tw, "%s\t", v) 169 } 170 fmt.Fprintln(tw) 171 } 172 p("PLAN", "PRICE") 173 for _, plan := range plans { 174 p(plan.URL, plan.Price) 175 } 176 tw.Flush() 177 return nil 178 } 179 180 // formatTabular returns a tabular summary of available plans. 181 func formatTabular(writer io.Writer, value interface{}) error { 182 plans, ok := value.([]plan) 183 if !ok { 184 return 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 fmt.Fprint(writer, table) 196 return 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 }