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 }