github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/controller/listmodels.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package controller
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"text/tabwriter"
    10  	"time"
    11  
    12  	"github.com/juju/cmd"
    13  	"github.com/juju/errors"
    14  	"github.com/juju/names"
    15  	"launchpad.net/gnuflag"
    16  
    17  	"github.com/juju/juju/api/base"
    18  	"github.com/juju/juju/apiserver/params"
    19  	"github.com/juju/juju/cmd/juju/common"
    20  	"github.com/juju/juju/cmd/modelcmd"
    21  )
    22  
    23  // NewListModelsCommand returns a command to list models.
    24  func NewListModelsCommand() cmd.Command {
    25  	return modelcmd.WrapController(&modelsCommand{})
    26  }
    27  
    28  // modelsCommand returns the list of all the models the
    29  // current user can access on the current controller.
    30  type modelsCommand struct {
    31  	modelcmd.ControllerCommandBase
    32  	out       cmd.Output
    33  	all       bool
    34  	user      string
    35  	listUUID  bool
    36  	exactTime bool
    37  	modelAPI  ModelManagerAPI
    38  	sysAPI    ModelsSysAPI
    39  }
    40  
    41  var listModelsDoc = `
    42  The models listed here are either models you have created yourself, or
    43  models which have been shared with you. Default values for user and
    44  controller are, respectively, the current user and the current controller.
    45  The active model is denoted by an asterisk.
    46  
    47  Examples:
    48  
    49      juju list-models
    50      juju list-models --user bob
    51  
    52  See also: create-model
    53            share-model
    54            unshare-model
    55  `
    56  
    57  // ModelManagerAPI defines the methods on the model manager API that
    58  // the models command calls.
    59  type ModelManagerAPI interface {
    60  	Close() error
    61  	ListModels(user string) ([]base.UserModel, error)
    62  	ModelInfo([]names.ModelTag) ([]params.ModelInfoResult, error)
    63  }
    64  
    65  // ModelsSysAPI defines the methods on the controller manager API that the
    66  // list models command calls.
    67  type ModelsSysAPI interface {
    68  	Close() error
    69  	AllModels() ([]base.UserModel, error)
    70  }
    71  
    72  // Info implements Command.Info
    73  func (c *modelsCommand) Info() *cmd.Info {
    74  	return &cmd.Info{
    75  		Name:    "list-models",
    76  		Purpose: "Lists models a user can access on a controller.",
    77  		Doc:     listModelsDoc,
    78  	}
    79  }
    80  
    81  func (c *modelsCommand) getModelManagerAPI() (ModelManagerAPI, error) {
    82  	if c.modelAPI != nil {
    83  		return c.modelAPI, nil
    84  	}
    85  	return c.NewModelManagerAPIClient()
    86  }
    87  
    88  func (c *modelsCommand) getSysAPI() (ModelsSysAPI, error) {
    89  	if c.sysAPI != nil {
    90  		return c.sysAPI, nil
    91  	}
    92  	return c.NewControllerAPIClient()
    93  }
    94  
    95  // SetFlags implements Command.SetFlags.
    96  func (c *modelsCommand) SetFlags(f *gnuflag.FlagSet) {
    97  	f.StringVar(&c.user, "user", "", "The user to list models for (administrative users only)")
    98  	f.BoolVar(&c.all, "all", false, "Lists all models, regardless of user accessibility (administrative users only)")
    99  	f.BoolVar(&c.listUUID, "uuid", false, "Display UUID for models")
   100  	f.BoolVar(&c.exactTime, "exact-time", false, "Use full timestamps")
   101  	c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{
   102  		"yaml":    cmd.FormatYaml,
   103  		"json":    cmd.FormatJson,
   104  		"tabular": c.formatTabular,
   105  	})
   106  }
   107  
   108  // ModelSet contains the set of models known to the client,
   109  // and UUID of the current model.
   110  type ModelSet struct {
   111  	Models       []common.ModelInfo `yaml:"models" json:"models"`
   112  	CurrentModel string             `yaml:"current-model,omitempty" json:"current-model,omitempty"`
   113  }
   114  
   115  // Run implements Command.Run
   116  func (c *modelsCommand) Run(ctx *cmd.Context) error {
   117  	if c.user == "" {
   118  		accountDetails, err := c.ClientStore().AccountByName(
   119  			c.ControllerName(), c.AccountName(),
   120  		)
   121  		if err != nil {
   122  			return err
   123  		}
   124  		c.user = accountDetails.User
   125  	}
   126  
   127  	// First get a list of the models.
   128  	var models []base.UserModel
   129  	var err error
   130  	if c.all {
   131  		models, err = c.getAllModels()
   132  	} else {
   133  		models, err = c.getUserModels()
   134  	}
   135  	if err != nil {
   136  		return errors.Annotate(err, "cannot list models")
   137  	}
   138  
   139  	// And now get the full details of the models.
   140  	paramsModelInfo, err := c.getModelInfo(models)
   141  	if err != nil {
   142  		return errors.Annotate(err, "cannot get model details")
   143  	}
   144  
   145  	now := time.Now()
   146  	modelInfo := make([]common.ModelInfo, 0, len(models))
   147  	for _, info := range paramsModelInfo {
   148  		model, err := common.ModelInfoFromParams(info, now)
   149  		if err != nil {
   150  			return errors.Trace(err)
   151  		}
   152  		modelInfo = append(modelInfo, model)
   153  	}
   154  
   155  	modelSet := ModelSet{Models: modelInfo}
   156  	current, err := c.ClientStore().CurrentModel(c.ControllerName(), c.AccountName())
   157  	if err != nil && !errors.IsNotFound(err) {
   158  		return err
   159  	}
   160  	modelSet.CurrentModel = current
   161  	if err := c.out.Write(ctx, modelSet); err != nil {
   162  		return err
   163  	}
   164  
   165  	if len(models) == 0 && c.out.Name() == "tabular" {
   166  		// When the output is tabular, we inform the user when there
   167  		// are no models available, and tell them how to go about
   168  		// creating or granting access to them.
   169  		fmt.Fprintf(ctx.Stderr, "\n%s\n\n", errNoModels.Error())
   170  	}
   171  	return nil
   172  }
   173  
   174  func (c *modelsCommand) getModelInfo(userModels []base.UserModel) ([]params.ModelInfo, error) {
   175  	client, err := c.getModelManagerAPI()
   176  	if err != nil {
   177  		return nil, errors.Trace(err)
   178  	}
   179  	defer client.Close()
   180  
   181  	tags := make([]names.ModelTag, len(userModels))
   182  	for i, m := range userModels {
   183  		tags[i] = names.NewModelTag(m.UUID)
   184  	}
   185  	results, err := client.ModelInfo(tags)
   186  	if err != nil {
   187  		return nil, errors.Trace(err)
   188  	}
   189  
   190  	info := make([]params.ModelInfo, len(tags))
   191  	for i, result := range results {
   192  		if result.Error != nil {
   193  			if params.IsCodeUnauthorized(result.Error) {
   194  				// If we get this, then the model was removed
   195  				// between the initial listing and the call
   196  				// to query its details.
   197  				continue
   198  			}
   199  			return nil, errors.Annotatef(
   200  				result.Error, "getting model %s (%q) info",
   201  				userModels[i].UUID, userModels[i].Name,
   202  			)
   203  		}
   204  		info[i] = *result.Result
   205  	}
   206  	return info, nil
   207  }
   208  
   209  func (c *modelsCommand) getAllModels() ([]base.UserModel, error) {
   210  	client, err := c.getSysAPI()
   211  	if err != nil {
   212  		return nil, errors.Trace(err)
   213  	}
   214  	defer client.Close()
   215  	return client.AllModels()
   216  }
   217  
   218  func (c *modelsCommand) getUserModels() ([]base.UserModel, error) {
   219  	client, err := c.getModelManagerAPI()
   220  	if err != nil {
   221  		return nil, errors.Trace(err)
   222  	}
   223  	defer client.Close()
   224  	return client.ListModels(c.user)
   225  }
   226  
   227  // formatTabular takes an interface{} to adhere to the cmd.Formatter interface
   228  func (c *modelsCommand) formatTabular(value interface{}) ([]byte, error) {
   229  	modelSet, ok := value.(ModelSet)
   230  	if !ok {
   231  		return nil, errors.Errorf("expected value of type %T, got %T", modelSet, value)
   232  	}
   233  	var out bytes.Buffer
   234  	const (
   235  		// To format things into columns.
   236  		minwidth = 0
   237  		tabwidth = 1
   238  		padding  = 2
   239  		padchar  = ' '
   240  		flags    = 0
   241  	)
   242  	tw := tabwriter.NewWriter(&out, minwidth, tabwidth, padding, padchar, flags)
   243  	fmt.Fprintf(tw, "NAME")
   244  	if c.listUUID {
   245  		fmt.Fprintf(tw, "\tMODEL UUID")
   246  	}
   247  	fmt.Fprintf(tw, "\tOWNER\tSTATUS\tLAST CONNECTION\n")
   248  	for _, model := range modelSet.Models {
   249  		name := model.Name
   250  		if name == modelSet.CurrentModel {
   251  			name += "*"
   252  		}
   253  		fmt.Fprintf(tw, "%s", name)
   254  		if c.listUUID {
   255  			fmt.Fprintf(tw, "\t%s", model.UUID)
   256  		}
   257  		user := names.NewUserTag(c.user).Canonical()
   258  		lastConnection := model.Users[user].LastConnection
   259  		if lastConnection == "" {
   260  			lastConnection = "never connected"
   261  		}
   262  		fmt.Fprintf(tw, "\t%s\t%s\t%s\n", model.Owner, model.Status.Current, lastConnection)
   263  	}
   264  	tw.Flush()
   265  	return out.Bytes(), nil
   266  }