github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/resource/charmresources.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  	"github.com/juju/cmd"
     8  	"github.com/juju/errors"
     9  	"github.com/juju/gnuflag"
    10  	"gopkg.in/juju/charm.v6"
    11  	charmresource "gopkg.in/juju/charm.v6/resource"
    12  	csparams "gopkg.in/juju/charmrepo.v3/csclient/params"
    13  
    14  	"github.com/juju/juju/api"
    15  	"github.com/juju/juju/api/controller"
    16  	"github.com/juju/juju/charmstore"
    17  	jujucmd "github.com/juju/juju/cmd"
    18  	"github.com/juju/juju/cmd/modelcmd"
    19  )
    20  
    21  // CharmResourcesCommand implements the "juju charm-resources" command.
    22  type CharmResourcesCommand struct {
    23  	baseCharmResourcesCommand
    24  }
    25  
    26  // NewCharmResourcesCommand returns a new command that lists resources defined
    27  // by a charm.
    28  func NewCharmResourcesCommand(resourceLister ResourceLister) modelcmd.ModelCommand {
    29  	var c CharmResourcesCommand
    30  	c.setResourceLister(resourceLister)
    31  	return modelcmd.Wrap(&c)
    32  }
    33  
    34  // Info implements cmd.Command.
    35  func (c *CharmResourcesCommand) Info() *cmd.Info {
    36  	i := c.baseInfo()
    37  	i.Name = "charm-resources"
    38  	i.Aliases = []string{"list-charm-resources"}
    39  	return jujucmd.Info(i)
    40  }
    41  
    42  // SetFlags implements cmd.Command.
    43  func (c *CharmResourcesCommand) SetFlags(f *gnuflag.FlagSet) {
    44  	c.setBaseFlags(f)
    45  }
    46  
    47  // Init implements cmd.Command.
    48  func (c *CharmResourcesCommand) Init(args []string) error {
    49  	return c.baseInit(args)
    50  }
    51  
    52  // Run implements cmd.Command.
    53  func (c *CharmResourcesCommand) Run(ctx *cmd.Context) error {
    54  	return c.baseRun(ctx)
    55  }
    56  
    57  // CharmResourceLister lists resources for the given charm ids.
    58  type ResourceLister interface {
    59  	ListResources(ids []charmstore.CharmID) ([][]charmresource.Resource, error)
    60  }
    61  
    62  type baseCharmResourcesCommand struct {
    63  	modelcmd.ModelCommandBase
    64  
    65  	// resourceLister is called by Run to list charm resources and
    66  	// uses juju/juju/charmstore.Client.
    67  	resourceLister ResourceLister
    68  
    69  	out     cmd.Output
    70  	channel string
    71  	charm   string
    72  }
    73  
    74  func (b *baseCharmResourcesCommand) setResourceLister(resourceLister ResourceLister) {
    75  	if resourceLister == nil {
    76  		resourceLister = b
    77  	}
    78  	b.resourceLister = resourceLister
    79  }
    80  
    81  func (c *baseCharmResourcesCommand) baseInfo() *cmd.Info {
    82  	return jujucmd.Info(&cmd.Info{
    83  		Args:    "<charm>",
    84  		Purpose: "Display the resources for a charm in the charm store.",
    85  		Doc:     charmResourcesDoc,
    86  	})
    87  }
    88  
    89  func (c *baseCharmResourcesCommand) setBaseFlags(f *gnuflag.FlagSet) {
    90  	c.ModelCommandBase.SetFlags(f)
    91  	defaultFormat := "tabular"
    92  	c.out.AddFlags(f, defaultFormat, map[string]cmd.Formatter{
    93  		"tabular": FormatCharmTabular,
    94  		"yaml":    cmd.FormatYaml,
    95  		"json":    cmd.FormatJson,
    96  	})
    97  	f.StringVar(&c.channel, "channel", "stable", "the charmstore channel of the charm")
    98  }
    99  
   100  func (c *baseCharmResourcesCommand) baseInit(args []string) error {
   101  	if len(args) == 0 {
   102  		return errors.New("missing charm")
   103  	}
   104  	c.charm = args[0]
   105  
   106  	if err := cmd.CheckEmpty(args[1:]); err != nil {
   107  		return errors.Trace(err)
   108  	}
   109  	return nil
   110  }
   111  
   112  func (c *baseCharmResourcesCommand) baseRun(ctx *cmd.Context) error {
   113  	// TODO(ericsnow) Adjust this to the charm store.
   114  
   115  	charmURL, err := resolveCharm(c.charm)
   116  	if err != nil {
   117  		return errors.Trace(err)
   118  	}
   119  	charm := charmstore.CharmID{URL: charmURL, Channel: csparams.Channel(c.channel)}
   120  
   121  	resources, err := c.resourceLister.ListResources([]charmstore.CharmID{charm})
   122  	if err != nil {
   123  		return errors.Trace(err)
   124  	}
   125  	if len(resources) != 1 {
   126  		return errors.New("got bad data from charm store")
   127  	}
   128  	res := resources[0]
   129  
   130  	if len(res) == 0 && c.out.Name() == "tabular" {
   131  		ctx.Infof("No resources to display.")
   132  		return nil
   133  	}
   134  
   135  	// Note that we do not worry about c.CompatVersion
   136  	// for show-charm-resources...
   137  	formatter := newCharmResourcesFormatter(resources[0])
   138  	formatted := formatter.format()
   139  	return c.out.Write(ctx, formatted)
   140  }
   141  
   142  var charmResourcesDoc = `
   143  This command will report the resources for a charm in the charm store.
   144  
   145  <charm> can be a charm URL, or an unambiguously condensed form of it,
   146  just like the deploy command. So the following forms will be accepted:
   147  
   148  For cs:trusty/mysql
   149    mysql
   150    trusty/mysql
   151  
   152  For cs:~user/trusty/mysql
   153    cs:~user/mysql
   154  
   155  Where the series is not supplied, the series from your local host is used.
   156  Thus the above examples imply that the local series is trusty.
   157  `
   158  
   159  // ListCharmResources implements CharmResourceLister by getting the charmstore client
   160  // from the command's ModelCommandBase.
   161  func (c *baseCharmResourcesCommand) ListResources(ids []charmstore.CharmID) ([][]charmresource.Resource, error) {
   162  	bakeryClient, err := c.BakeryClient()
   163  	if err != nil {
   164  		return nil, errors.Trace(err)
   165  	}
   166  	conAPIRoot, err := c.NewControllerAPIRoot()
   167  	if err != nil {
   168  		return nil, errors.Trace(err)
   169  	}
   170  	csURL, err := getCharmStoreAPIURL(conAPIRoot)
   171  	if err != nil {
   172  		return nil, errors.Trace(err)
   173  	}
   174  	client, err := charmstore.NewCustomClient(bakeryClient, csURL)
   175  	if err != nil {
   176  		return nil, errors.Trace(err)
   177  	}
   178  	return client.ListResources(ids)
   179  }
   180  
   181  func resolveCharm(raw string) (*charm.URL, error) {
   182  	charmURL, err := charm.ParseURL(raw)
   183  	if err != nil {
   184  		return charmURL, errors.Trace(err)
   185  	}
   186  
   187  	if charmURL.Series == "bundle" {
   188  		return charmURL, errors.Errorf("charm bundles are not supported")
   189  	}
   190  
   191  	return charmURL, nil
   192  }
   193  
   194  // getCharmStoreAPIURL consults the controller config for the charmstore api url to use.
   195  var getCharmStoreAPIURL = func(conAPIRoot api.Connection) (string, error) {
   196  	controllerAPI := controller.NewClient(conAPIRoot)
   197  	controllerCfg, err := controllerAPI.ControllerConfig()
   198  	if err != nil {
   199  		return "", errors.Trace(err)
   200  	}
   201  	return controllerCfg.CharmStoreURL(), nil
   202  }