github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/resource/list.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package resource
     5  
     6  import (
     7  	"sort"
     8  
     9  	"github.com/juju/cmd"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/gnuflag"
    12  	"gopkg.in/juju/names.v2"
    13  
    14  	jujucmd "github.com/juju/juju/cmd"
    15  	"github.com/juju/juju/cmd/modelcmd"
    16  	"github.com/juju/juju/resource"
    17  )
    18  
    19  // ListClient has the API client methods needed by ListCommand.
    20  type ListClient interface {
    21  	// ListResources returns info about resources for applications in the model.
    22  	ListResources(applications []string) ([]resource.ApplicationResources, error)
    23  	// Close closes the connection.
    24  	Close() error
    25  }
    26  
    27  // ListDeps is a type that contains external functions that List needs.
    28  type ListDeps struct {
    29  	// NewClient returns the value that wraps the API for showing
    30  	// resources from the server.
    31  	NewClient func(*ListCommand) (ListClient, error)
    32  }
    33  
    34  // ListCommand discovers and lists application or unit resources.
    35  type ListCommand struct {
    36  	modelcmd.ModelCommandBase
    37  
    38  	details bool
    39  	deps    ListDeps
    40  	out     cmd.Output
    41  	target  string
    42  }
    43  
    44  // NewListCommand returns a new command that lists resources defined
    45  // by a charm.
    46  func NewListCommand(deps ListDeps) modelcmd.ModelCommand {
    47  	return modelcmd.Wrap(&ListCommand{deps: deps})
    48  }
    49  
    50  // Info implements cmd.Command.Info.
    51  func (c *ListCommand) Info() *cmd.Info {
    52  	return jujucmd.Info(&cmd.Info{
    53  		Name:    "resources",
    54  		Aliases: []string{"list-resources"},
    55  		Args:    "<application or unit>",
    56  		Purpose: "Show the resources for an application or unit.",
    57  		Doc: `
    58  This command shows the resources required by and those in use by an existing
    59  application or unit in your model.  When run for an application, it will also show any
    60  updates available for resources from the charmstore.
    61  `,
    62  	})
    63  }
    64  
    65  // SetFlags implements cmd.Command.SetFlags.
    66  func (c *ListCommand) SetFlags(f *gnuflag.FlagSet) {
    67  	c.ModelCommandBase.SetFlags(f)
    68  	const defaultFormat = "tabular"
    69  	c.out.AddFlags(f, defaultFormat, map[string]cmd.Formatter{
    70  		defaultFormat: FormatAppTabular,
    71  		"yaml":        cmd.FormatYaml,
    72  		"json":        cmd.FormatJson,
    73  	})
    74  
    75  	f.BoolVar(&c.details, "details", false, "show detailed information about resources used by each unit.")
    76  }
    77  
    78  // Init implements cmd.Command.Init. It will return an error satisfying
    79  // errors.BadRequest if you give it an incorrect number of arguments.
    80  func (c *ListCommand) Init(args []string) error {
    81  	if len(args) == 0 {
    82  		return errors.NewBadRequest(nil, "missing application or unit name")
    83  	}
    84  	c.target = args[0]
    85  	if err := cmd.CheckEmpty(args[1:]); err != nil {
    86  		return errors.NewBadRequest(err, "")
    87  	}
    88  	return nil
    89  }
    90  
    91  // Run implements cmd.Command.Run.
    92  func (c *ListCommand) Run(ctx *cmd.Context) error {
    93  	apiclient, err := c.deps.NewClient(c)
    94  	if err != nil {
    95  		return errors.Trace(err)
    96  	}
    97  	defer apiclient.Close()
    98  
    99  	var unit string
   100  	var application string
   101  	if names.IsValidApplication(c.target) {
   102  		application = c.target
   103  	} else {
   104  		application, err = names.UnitApplication(c.target)
   105  		if err != nil {
   106  			return errors.Errorf("%q is neither an application nor a unit", c.target)
   107  		}
   108  		unit = c.target
   109  	}
   110  
   111  	vals, err := apiclient.ListResources([]string{application})
   112  	if err != nil {
   113  		return errors.Trace(err)
   114  	}
   115  	if len(vals) != 1 {
   116  		return errors.Errorf("bad data returned from server")
   117  	}
   118  	v := vals[0]
   119  
   120  	// It's a lot easier to read and to digest a list of resources
   121  	// when  they are ordered.
   122  	sort.Sort(charmResourceList(v.CharmStoreResources))
   123  	sort.Sort(resourceList(v.Resources))
   124  	for _, u := range v.UnitResources {
   125  		sort.Sort(resourceList(u.Resources))
   126  	}
   127  
   128  	if unit == "" {
   129  		return c.formatApplicationResources(ctx, v)
   130  	}
   131  	return c.formatUnitResources(ctx, unit, application, v)
   132  }
   133  
   134  const noResources = "No resources to display."
   135  
   136  func (c *ListCommand) formatApplicationResources(ctx *cmd.Context, sr resource.ApplicationResources) error {
   137  	if c.details {
   138  		formatted, err := FormatApplicationDetails(sr)
   139  		if err != nil {
   140  			return errors.Trace(err)
   141  		}
   142  		if len(formatted.Resources) == 0 && len(formatted.Updates) == 0 {
   143  			ctx.Infof(noResources)
   144  			return nil
   145  		}
   146  
   147  		return c.out.Write(ctx, formatted)
   148  	}
   149  
   150  	formatted, err := formatApplicationResources(sr)
   151  	if err != nil {
   152  		return errors.Trace(err)
   153  	}
   154  	if len(formatted.Resources) == 0 && len(formatted.Updates) == 0 {
   155  		ctx.Infof(noResources)
   156  		return nil
   157  	}
   158  	return c.out.Write(ctx, formatted)
   159  }
   160  
   161  func (c *ListCommand) formatUnitResources(ctx *cmd.Context, unit, application string, sr resource.ApplicationResources) error {
   162  	if len(sr.Resources) == 0 && len(sr.UnitResources) == 0 {
   163  		ctx.Infof(noResources)
   164  		return nil
   165  	}
   166  
   167  	if c.details {
   168  		formatted, err := detailedResources(unit, sr)
   169  		if err != nil {
   170  			return errors.Trace(err)
   171  		}
   172  		return c.out.Write(ctx, FormattedUnitDetails(formatted))
   173  	}
   174  
   175  	resources := unitResources(unit, application, sr)
   176  	res := make([]FormattedAppResource, len(sr.Resources))
   177  	for i, r := range sr.Resources {
   178  		if unitResource, ok := resources[r.ID]; ok {
   179  			// Unit has this application resource,
   180  			// so use unit's version.
   181  			r = unitResource
   182  		} else {
   183  			// Unit does not have this application resource.
   184  			// Have to set it to -1 since revision 0 is still a valid revision.
   185  			// All other information is inherited from application resource.
   186  			r.Revision = -1
   187  		}
   188  		res[i] = FormatAppResource(r)
   189  	}
   190  
   191  	return c.out.Write(ctx, res)
   192  
   193  }
   194  
   195  func unitResources(unit, application string, sr resource.ApplicationResources) map[string]resource.Resource {
   196  	var resources []resource.Resource
   197  	for _, res := range sr.UnitResources {
   198  		if res.Tag.Id() == unit {
   199  			resources = res.Resources
   200  		}
   201  	}
   202  	if len(resources) == 0 {
   203  		return nil
   204  	}
   205  	unitResourcesById := make(map[string]resource.Resource)
   206  	for _, r := range resources {
   207  		unitResourcesById[r.ID] = r
   208  	}
   209  	return unitResourcesById
   210  }