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

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package crossmodel
     5  
     6  import (
     7  	"github.com/juju/cmd"
     8  	"github.com/juju/errors"
     9  	"github.com/juju/gnuflag"
    10  	"gopkg.in/juju/names.v2"
    11  
    12  	jujucmd "github.com/juju/juju/cmd"
    13  	"github.com/juju/juju/cmd/modelcmd"
    14  	"github.com/juju/juju/core/crossmodel"
    15  )
    16  
    17  const findCommandDoc = `
    18  Find which offered application endpoints are available to the current user.
    19  
    20  This command is aimed for a user who wants to discover what endpoints are available to them.
    21  
    22  Examples:
    23     $ juju find-offers
    24     $ juju find-offers mycontroller:
    25     $ juju find-offers fred/prod
    26     $ juju find-offers --interface mysql
    27     $ juju find-offers --url fred/prod.db2
    28     $ juju find-offers --offer db2
    29     
    30  See also:
    31     show-offer   
    32  `
    33  
    34  type findCommand struct {
    35  	RemoteEndpointsCommandBase
    36  
    37  	url            string
    38  	source         string
    39  	modelOwnerName string
    40  	modelName      string
    41  	offerName      string
    42  	interfaceName  string
    43  
    44  	out        cmd.Output
    45  	newAPIFunc func(string) (FindAPI, error)
    46  }
    47  
    48  // NewFindEndpointsCommand constructs command that
    49  // allows to find offered application endpoints.
    50  func NewFindEndpointsCommand() cmd.Command {
    51  	findCmd := &findCommand{}
    52  	findCmd.newAPIFunc = func(controllerName string) (FindAPI, error) {
    53  		return findCmd.NewRemoteEndpointsAPI(controllerName)
    54  	}
    55  	return modelcmd.WrapController(findCmd)
    56  }
    57  
    58  // Init implements Command.Init.
    59  func (c *findCommand) Init(args []string) (err error) {
    60  	if c.offerName != "" && c.url != "" {
    61  		return errors.New("cannot specify both a URL term and offer term")
    62  	}
    63  	url, err := cmd.ZeroOrOneArgs(args)
    64  	if err != nil {
    65  		return errors.Trace(err)
    66  	}
    67  	if url != "" {
    68  		if c.url != "" {
    69  			return errors.New("URL term cannot be specified twice")
    70  		}
    71  		c.url = url
    72  	}
    73  	return nil
    74  }
    75  
    76  // Info implements Command.Info.
    77  func (c *findCommand) Info() *cmd.Info {
    78  	return jujucmd.Info(&cmd.Info{
    79  		Name:    "find-offers",
    80  		Purpose: "Find offered application endpoints.",
    81  		Doc:     findCommandDoc,
    82  	})
    83  }
    84  
    85  // SetFlags implements Command.SetFlags.
    86  func (c *findCommand) SetFlags(f *gnuflag.FlagSet) {
    87  	c.RemoteEndpointsCommandBase.SetFlags(f)
    88  	f.StringVar(&c.url, "url", "", "return results matching the offer URL")
    89  	f.StringVar(&c.interfaceName, "interface", "", "return results matching the interface name")
    90  	f.StringVar(&c.offerName, "offer", "", "return results matching the offer name")
    91  	c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{
    92  		"yaml":    cmd.FormatYaml,
    93  		"json":    cmd.FormatJson,
    94  		"tabular": formatFindTabular,
    95  	})
    96  }
    97  
    98  // Run implements Command.Run.
    99  func (c *findCommand) Run(ctx *cmd.Context) (err error) {
   100  	if err := c.validateOrSetURL(); err != nil {
   101  		return errors.Trace(err)
   102  	}
   103  	accountDetails, err := c.CurrentAccountDetails()
   104  	if err != nil {
   105  		return err
   106  	}
   107  	loggedInUser := accountDetails.User
   108  
   109  	api, err := c.newAPIFunc(c.source)
   110  	if err != nil {
   111  		return err
   112  	}
   113  	defer api.Close()
   114  
   115  	filter := crossmodel.ApplicationOfferFilter{
   116  		OwnerName: c.modelOwnerName,
   117  		ModelName: c.modelName,
   118  		OfferName: c.offerName,
   119  	}
   120  	if c.interfaceName != "" {
   121  		filter.Endpoints = []crossmodel.EndpointFilterTerm{{
   122  			Interface: c.interfaceName,
   123  		}}
   124  	}
   125  	found, err := api.FindApplicationOffers(filter)
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	output, err := convertFoundOffers(c.source, names.NewUserTag(loggedInUser), found...)
   131  	if err != nil {
   132  		return err
   133  	}
   134  	if len(output) == 0 {
   135  		return errors.New("no matching application offers found")
   136  	}
   137  	return c.out.Write(ctx, output)
   138  }
   139  
   140  func (c *findCommand) validateOrSetURL() error {
   141  	controllerName, err := c.ControllerName()
   142  	if err != nil {
   143  		return errors.Trace(err)
   144  	}
   145  	if c.url == "" {
   146  		c.url = controllerName + ":"
   147  		c.source = controllerName
   148  		return nil
   149  	}
   150  	urlParts, err := crossmodel.ParseOfferURLParts(c.url)
   151  	if err != nil {
   152  		return errors.Trace(err)
   153  	}
   154  	if urlParts.Source != "" {
   155  		c.source = urlParts.Source
   156  	} else {
   157  		c.source = controllerName
   158  	}
   159  	user := urlParts.User
   160  	if user == "" {
   161  		accountDetails, err := c.CurrentAccountDetails()
   162  		if err != nil {
   163  			return errors.Trace(err)
   164  		}
   165  		user = accountDetails.User
   166  	}
   167  	c.modelOwnerName = user
   168  	c.modelName = urlParts.ModelName
   169  	c.offerName = urlParts.ApplicationName
   170  	return nil
   171  }
   172  
   173  // FindAPI defines the API methods that cross model find command uses.
   174  type FindAPI interface {
   175  	Close() error
   176  	FindApplicationOffers(filters ...crossmodel.ApplicationOfferFilter) ([]*crossmodel.ApplicationOfferDetails, error)
   177  }
   178  
   179  // ApplicationOfferResult defines the serialization behaviour of an application offer.
   180  // This is used in map-style yaml output where offer URL is the key.
   181  type ApplicationOfferResult struct {
   182  	// Access is the level of access the user has on the offer.
   183  	Access string `yaml:"access" json:"access"`
   184  
   185  	// Endpoints is the list of offered application endpoints.
   186  	Endpoints map[string]RemoteEndpoint `yaml:"endpoints" json:"endpoints"`
   187  
   188  	// Users are the users who can access the offer.
   189  	Users map[string]OfferUser `yaml:"users,omitempty" json:"users,omitempty"`
   190  }
   191  
   192  func accessForUser(user names.UserTag, users []crossmodel.OfferUserDetails) string {
   193  	for _, u := range users {
   194  		if u.UserName == user.Id() {
   195  			return string(u.Access)
   196  		}
   197  	}
   198  	return "-"
   199  }
   200  
   201  // convertFoundOffers takes any number of api-formatted remote applications and
   202  // creates a collection of ui-formatted applications.
   203  func convertFoundOffers(
   204  	store string, loggedInUser names.UserTag, offers ...*crossmodel.ApplicationOfferDetails,
   205  ) (map[string]ApplicationOfferResult, error) {
   206  	if len(offers) == 0 {
   207  		return nil, nil
   208  	}
   209  	output := make(map[string]ApplicationOfferResult, len(offers))
   210  	for _, one := range offers {
   211  		access := accessForUser(loggedInUser, one.Users)
   212  		app := ApplicationOfferResult{
   213  			Access:    access,
   214  			Endpoints: convertRemoteEndpoints(one.Endpoints...),
   215  			Users:     convertUsers(one.Users...),
   216  		}
   217  		url, err := crossmodel.ParseOfferURL(one.OfferURL)
   218  		if err != nil {
   219  			return nil, err
   220  		}
   221  		if url.Source == "" {
   222  			url.Source = store
   223  		}
   224  		output[url.String()] = app
   225  	}
   226  	return output, nil
   227  }