github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/romulus/showbudget/show_budget.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package showbudget 5 6 import ( 7 "fmt" 8 "io" 9 "sort" 10 11 "github.com/gosuri/uitable" 12 "github.com/juju/cmd" 13 "github.com/juju/errors" 14 "github.com/juju/gnuflag" 15 "github.com/juju/juju/api/modelmanager" 16 "github.com/juju/juju/apiserver/params" 17 "github.com/juju/juju/cmd/modelcmd" 18 "github.com/juju/loggo" 19 "gopkg.in/juju/names.v2" 20 "gopkg.in/macaroon-bakery.v1/httpbakery" 21 22 api "github.com/juju/romulus/api/budget" 23 wireformat "github.com/juju/romulus/wireformat/budget" 24 ) 25 26 var logger = loggo.GetLogger("romulus.cmd.showbudget") 27 28 // NewShowBudgetCommand returns a new command that is used 29 // to show details of the specified wireformat. 30 func NewShowBudgetCommand() modelcmd.CommandBase { 31 return &showBudgetCommand{} 32 } 33 34 type showBudgetCommand struct { 35 modelcmd.ModelCommandBase 36 37 out cmd.Output 38 budget string 39 } 40 41 const showBudgetDoc = ` 42 Display budget usage information. 43 44 Examples: 45 juju show-budget personal 46 ` 47 48 // Info implements cmd.Command.Info. 49 func (c *showBudgetCommand) Info() *cmd.Info { 50 return &cmd.Info{ 51 Name: "show-budget", 52 Args: "<budget>", 53 Purpose: "Show details about a budget.", 54 Doc: showBudgetDoc, 55 } 56 } 57 58 // Init implements cmd.Command.Init. 59 func (c *showBudgetCommand) Init(args []string) error { 60 if len(args) < 1 { 61 return errors.New("missing arguments") 62 } 63 c.budget, args = args[0], args[1:] 64 65 return cmd.CheckEmpty(args) 66 } 67 68 // SetFlags implements cmd.Command.SetFlags. 69 func (c *showBudgetCommand) SetFlags(f *gnuflag.FlagSet) { 70 c.ModelCommandBase.SetFlags(f) 71 c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{ 72 "tabular": formatTabular, 73 "json": cmd.FormatJson, 74 }) 75 } 76 77 func (c *showBudgetCommand) Run(ctx *cmd.Context) error { 78 client, err := c.BakeryClient() 79 if err != nil { 80 return errors.Annotate(err, "failed to create an http client") 81 } 82 api, err := newBudgetAPIClient(client) 83 if err != nil { 84 return errors.Annotate(err, "failed to create an api client") 85 } 86 budget, err := api.GetBudget(c.budget) 87 if err != nil { 88 return errors.Annotate(err, "failed to retrieve the budget") 89 } 90 c.resolveModelNames(budget) 91 err = c.out.Write(ctx, budget) 92 return errors.Trace(err) 93 } 94 95 // resolveModelNames is a best-effort method to resolve model names - if we 96 // encounter any error, we do not issue an error. 97 func (c *showBudgetCommand) resolveModelNames(budget *wireformat.BudgetWithAllocations) { 98 models := make([]names.ModelTag, len(budget.Allocations)) 99 for i, allocation := range budget.Allocations { 100 models[i] = names.NewModelTag(allocation.Model) 101 } 102 client, err := newAPIClient(c) 103 if err != nil { 104 logger.Errorf("failed to open the API client: %v", err) 105 return 106 } 107 modelInfoSlice, err := client.ModelInfo(models) 108 if err != nil { 109 logger.Errorf("failed to retrieve model info: %v", err) 110 return 111 } 112 for j, info := range modelInfoSlice { 113 if info.Error != nil { 114 logger.Errorf("failed to get model info for model %q: %v", models[j], info.Error) 115 continue 116 } 117 for i, allocation := range budget.Allocations { 118 if info.Result.UUID == allocation.Model { 119 budget.Allocations[i].Model = info.Result.Name 120 } 121 } 122 } 123 } 124 125 // formatTabular returns a tabular view of available budgets. 126 func formatTabular(writer io.Writer, value interface{}) error { 127 b, ok := value.(*wireformat.BudgetWithAllocations) 128 if !ok { 129 return errors.Errorf("expected value of type %T, got %T", b, value) 130 } 131 132 table := uitable.New() 133 table.MaxColWidth = 50 134 table.Wrap = true 135 for _, col := range []int{2, 3, 5} { 136 table.RightAlign(col) 137 } 138 139 table.AddRow("MODEL", "SERVICES", "SPENT", "ALLOCATED", "BY", "USAGE") 140 for _, allocation := range b.Allocations { 141 firstLine := true 142 // We'll sort the service names to avoid nondeterministic 143 // command output. 144 services := make([]string, 0, len(allocation.Services)) 145 for serviceName, _ := range allocation.Services { 146 services = append(services, serviceName) 147 } 148 sort.Strings(services) 149 for _, serviceName := range services { 150 service, _ := allocation.Services[serviceName] 151 if firstLine { 152 table.AddRow(allocation.Model, serviceName, service.Consumed, allocation.Limit, allocation.Owner, allocation.Usage) 153 firstLine = false 154 continue 155 } 156 table.AddRow("", serviceName, service.Consumed, "", "") 157 } 158 159 } 160 table.AddRow("", "", "", "", "") 161 table.AddRow("TOTAL", "", b.Total.Consumed, b.Total.Allocated, "", b.Total.Usage) 162 table.AddRow("BUDGET", "", "", b.Limit, "") 163 table.AddRow("UNALLOCATED", "", "", b.Total.Unallocated, "") 164 fmt.Fprint(writer, table) 165 return nil 166 } 167 168 var newAPIClient = newAPIClientImpl 169 170 func newAPIClientImpl(c *showBudgetCommand) (APIClient, error) { 171 root, err := c.NewControllerAPIRoot() 172 if err != nil { 173 return nil, errors.Trace(err) 174 } 175 return modelmanager.NewClient(root), nil 176 } 177 178 type APIClient interface { 179 ModelInfo(tags []names.ModelTag) ([]params.ModelInfoResult, error) 180 } 181 182 var newBudgetAPIClient = newBudgetAPIClientImpl 183 184 func newBudgetAPIClientImpl(c *httpbakery.Client) (budgetAPIClient, error) { 185 client := api.NewClient(c) 186 return client, nil 187 } 188 189 type budgetAPIClient interface { 190 GetBudget(string) (*wireformat.BudgetWithAllocations, error) 191 }