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  }