github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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  	"fmt"
     8  	"io"
     9  	"time"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/gnuflag"
    14  	"gopkg.in/juju/names.v2"
    15  
    16  	"github.com/juju/juju/api/base"
    17  	"github.com/juju/juju/apiserver/params"
    18  	"github.com/juju/juju/cmd/juju/common"
    19  	"github.com/juju/juju/cmd/modelcmd"
    20  	"github.com/juju/juju/cmd/output"
    21  	"github.com/juju/juju/jujuclient"
    22  )
    23  
    24  // NewListModelsCommand returns a command to list models.
    25  func NewListModelsCommand() cmd.Command {
    26  	return modelcmd.WrapController(&modelsCommand{})
    27  }
    28  
    29  // modelsCommand returns the list of all the models the
    30  // current user can access on the current controller.
    31  type modelsCommand struct {
    32  	modelcmd.ControllerCommandBase
    33  	out          cmd.Output
    34  	all          bool
    35  	loggedInUser string
    36  	user         string
    37  	listUUID     bool
    38  	exactTime    bool
    39  	modelAPI     ModelManagerAPI
    40  	sysAPI       ModelsSysAPI
    41  }
    42  
    43  var listModelsDoc = `
    44  The models listed here are either models you have created yourself, or
    45  models which have been shared with you. Default values for user and
    46  controller are, respectively, the current user and the current controller.
    47  The active model is denoted by an asterisk.
    48  
    49  Examples:
    50  
    51      juju models
    52      juju models --user bob
    53  
    54  See also:
    55      add-model
    56      share-model
    57      unshare-model
    58  `
    59  
    60  // ModelManagerAPI defines the methods on the model manager API that
    61  // the models command calls.
    62  type ModelManagerAPI interface {
    63  	Close() error
    64  	ListModels(user string) ([]base.UserModel, error)
    65  	ModelInfo([]names.ModelTag) ([]params.ModelInfoResult, error)
    66  }
    67  
    68  // ModelsSysAPI defines the methods on the controller manager API that the
    69  // list models command calls.
    70  type ModelsSysAPI interface {
    71  	Close() error
    72  	AllModels() ([]base.UserModel, error)
    73  }
    74  
    75  // Info implements Command.Info
    76  func (c *modelsCommand) Info() *cmd.Info {
    77  	return &cmd.Info{
    78  		Name:    "models",
    79  		Purpose: "Lists models a user can access on a controller.",
    80  		Doc:     listModelsDoc,
    81  		Aliases: []string{"list-models"},
    82  	}
    83  }
    84  
    85  func (c *modelsCommand) getModelManagerAPI() (ModelManagerAPI, error) {
    86  	if c.modelAPI != nil {
    87  		return c.modelAPI, nil
    88  	}
    89  	return c.NewModelManagerAPIClient()
    90  }
    91  
    92  func (c *modelsCommand) getSysAPI() (ModelsSysAPI, error) {
    93  	if c.sysAPI != nil {
    94  		return c.sysAPI, nil
    95  	}
    96  	return c.NewControllerAPIClient()
    97  }
    98  
    99  // SetFlags implements Command.SetFlags.
   100  func (c *modelsCommand) SetFlags(f *gnuflag.FlagSet) {
   101  	c.ControllerCommandBase.SetFlags(f)
   102  	f.StringVar(&c.user, "user", "", "The user to list models for (administrative users only)")
   103  	f.BoolVar(&c.all, "all", false, "Lists all models, regardless of user accessibility (administrative users only)")
   104  	f.BoolVar(&c.listUUID, "uuid", false, "Display UUID for models")
   105  	f.BoolVar(&c.exactTime, "exact-time", false, "Use full timestamps")
   106  	c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{
   107  		"yaml":    cmd.FormatYaml,
   108  		"json":    cmd.FormatJson,
   109  		"tabular": c.formatTabular,
   110  	})
   111  }
   112  
   113  // ModelSet contains the set of models known to the client,
   114  // and UUID of the current model.
   115  type ModelSet struct {
   116  	Models []common.ModelInfo `yaml:"models" json:"models"`
   117  
   118  	// CurrentModel is the name of the current model, qualified for the
   119  	// user for which we're listing models. i.e. for the user admin@local,
   120  	// and the model admin@local/foo, this field will contain "foo"; for
   121  	// bob@local and the same model, the field will contain "admin/foo".
   122  	CurrentModel string `yaml:"current-model,omitempty" json:"current-model,omitempty"`
   123  
   124  	// CurrentModelQualified is the fully qualified name for the current
   125  	// model, i.e. having the format $owner/$model.
   126  	CurrentModelQualified string `yaml:"-" json:"-"`
   127  }
   128  
   129  // Run implements Command.Run
   130  func (c *modelsCommand) Run(ctx *cmd.Context) error {
   131  	accountDetails, err := c.ClientStore().AccountDetails(c.ControllerName())
   132  	if err != nil {
   133  		return err
   134  	}
   135  	c.loggedInUser = accountDetails.User
   136  
   137  	// First get a list of the models.
   138  	var models []base.UserModel
   139  	if c.all {
   140  		models, err = c.getAllModels()
   141  	} else {
   142  		if c.user == "" {
   143  			c.user = accountDetails.User
   144  		}
   145  		models, err = c.getUserModels()
   146  	}
   147  	if err != nil {
   148  		return errors.Annotate(err, "cannot list models")
   149  	}
   150  
   151  	// And now get the full details of the models.
   152  	paramsModelInfo, err := c.getModelInfo(models)
   153  	if err != nil {
   154  		return errors.Annotate(err, "cannot get model details")
   155  	}
   156  
   157  	// TODO(perrito666) 2016-05-02 lp:1558657
   158  	now := time.Now()
   159  	modelInfo := make([]common.ModelInfo, 0, len(models))
   160  	for _, info := range paramsModelInfo {
   161  		model, err := common.ModelInfoFromParams(info, now)
   162  		if err != nil {
   163  			return errors.Trace(err)
   164  		}
   165  		model.ControllerName = c.ControllerName()
   166  		modelInfo = append(modelInfo, model)
   167  	}
   168  
   169  	modelSet := ModelSet{Models: modelInfo}
   170  	current, err := c.ClientStore().CurrentModel(c.ControllerName())
   171  	if err != nil && !errors.IsNotFound(err) {
   172  		return err
   173  	}
   174  	modelSet.CurrentModelQualified = current
   175  	modelSet.CurrentModel = current
   176  	if c.user != "" {
   177  		userForListing := names.NewUserTag(c.user)
   178  		unqualifiedModelName, owner, err := jujuclient.SplitModelName(current)
   179  		if err == nil {
   180  			modelSet.CurrentModel = common.OwnerQualifiedModelName(
   181  				unqualifiedModelName, owner, userForListing,
   182  			)
   183  		}
   184  	}
   185  
   186  	if err := c.out.Write(ctx, modelSet); err != nil {
   187  		return err
   188  	}
   189  	if len(models) == 0 && c.out.Name() == "tabular" {
   190  		// When the output is tabular, we inform the user when there
   191  		// are no models available, and tell them how to go about
   192  		// creating or granting access to them.
   193  		fmt.Fprintf(ctx.Stderr, "\n%s\n\n", errNoModels.Error())
   194  	}
   195  	return nil
   196  }
   197  
   198  func (c *modelsCommand) getModelInfo(userModels []base.UserModel) ([]params.ModelInfo, error) {
   199  	client, err := c.getModelManagerAPI()
   200  	if err != nil {
   201  		return nil, errors.Trace(err)
   202  	}
   203  	defer client.Close()
   204  
   205  	tags := make([]names.ModelTag, len(userModels))
   206  	for i, m := range userModels {
   207  		tags[i] = names.NewModelTag(m.UUID)
   208  	}
   209  	results, err := client.ModelInfo(tags)
   210  	if err != nil {
   211  		return nil, errors.Trace(err)
   212  	}
   213  
   214  	info := make([]params.ModelInfo, len(tags))
   215  	for i, result := range results {
   216  		if result.Error != nil {
   217  			if params.IsCodeUnauthorized(result.Error) {
   218  				// If we get this, then the model was removed
   219  				// between the initial listing and the call
   220  				// to query its details.
   221  				continue
   222  			}
   223  			return nil, errors.Annotatef(
   224  				result.Error, "getting model %s (%q) info",
   225  				userModels[i].UUID, userModels[i].Name,
   226  			)
   227  		}
   228  		info[i] = *result.Result
   229  	}
   230  	return info, nil
   231  }
   232  
   233  func (c *modelsCommand) getAllModels() ([]base.UserModel, error) {
   234  	client, err := c.getSysAPI()
   235  	if err != nil {
   236  		return nil, errors.Trace(err)
   237  	}
   238  	defer client.Close()
   239  	return client.AllModels()
   240  }
   241  
   242  func (c *modelsCommand) getUserModels() ([]base.UserModel, error) {
   243  	client, err := c.getModelManagerAPI()
   244  	if err != nil {
   245  		return nil, errors.Trace(err)
   246  	}
   247  	defer client.Close()
   248  	return client.ListModels(c.user)
   249  }
   250  
   251  // formatTabular takes an interface{} to adhere to the cmd.Formatter interface
   252  func (c *modelsCommand) formatTabular(writer io.Writer, value interface{}) error {
   253  	modelSet, ok := value.(ModelSet)
   254  	if !ok {
   255  		return errors.Errorf("expected value of type %T, got %T", modelSet, value)
   256  	}
   257  
   258  	// We need the tag of the user for which we're listing models,
   259  	// and for the logged-in user. We use these below when formatting
   260  	// the model display names.
   261  	loggedInUser := names.NewUserTag(c.loggedInUser)
   262  	userForLastConn := loggedInUser
   263  	var userForListing names.UserTag
   264  	if c.user != "" {
   265  		userForListing = names.NewUserTag(c.user)
   266  		userForLastConn = userForListing
   267  	}
   268  
   269  	tw := output.TabWriter(writer)
   270  	w := output.Wrapper{tw}
   271  	w.Println("CONTROLLER: " + c.ControllerName())
   272  	w.Println()
   273  	w.Print("MODEL")
   274  	if c.listUUID {
   275  		w.Print("UUID")
   276  	}
   277  	// Only owners, or users with write access or above get to see machines and cores.
   278  	haveMachineInfo := false
   279  	for _, m := range modelSet.Models {
   280  		if haveMachineInfo = len(m.Machines) > 0; haveMachineInfo {
   281  			break
   282  		}
   283  	}
   284  	if haveMachineInfo {
   285  		w.Println("OWNER", "STATUS", "MACHINES", "CORES", "ACCESS", "LAST CONNECTION")
   286  		offset := 0
   287  		if c.listUUID {
   288  			offset++
   289  		}
   290  		tw.SetColumnAlignRight(3 + offset)
   291  		tw.SetColumnAlignRight(4 + offset)
   292  	} else {
   293  		w.Println("OWNER", "STATUS", "ACCESS", "LAST CONNECTION")
   294  	}
   295  	for _, model := range modelSet.Models {
   296  		owner := names.NewUserTag(model.Owner)
   297  		name := common.OwnerQualifiedModelName(model.Name, owner, userForListing)
   298  		if jujuclient.JoinOwnerModelName(owner, model.Name) == modelSet.CurrentModelQualified {
   299  			name += "*"
   300  			w.PrintColor(output.CurrentHighlight, name)
   301  		} else {
   302  			w.Print(name)
   303  		}
   304  		if c.listUUID {
   305  			w.Print(model.UUID)
   306  		}
   307  		lastConnection := model.Users[userForLastConn.Canonical()].LastConnection
   308  		if lastConnection == "" {
   309  			lastConnection = "never connected"
   310  		}
   311  		userForAccess := loggedInUser
   312  		if c.user != "" {
   313  			userForAccess = names.NewUserTag(c.user)
   314  		}
   315  		access := model.Users[userForAccess.Canonical()].Access
   316  		w.Print(model.Owner, model.Status.Current)
   317  		if haveMachineInfo {
   318  			machineInfo := fmt.Sprintf("%d", len(model.Machines))
   319  			cores := uint64(0)
   320  			for _, m := range model.Machines {
   321  				cores += m.Cores
   322  			}
   323  			coresInfo := "-"
   324  			if cores > 0 {
   325  				coresInfo = fmt.Sprintf("%d", cores)
   326  			}
   327  			w.Print(machineInfo, coresInfo)
   328  		}
   329  		w.Println(access, lastConnection)
   330  	}
   331  	tw.Flush()
   332  	return nil
   333  }