github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/model/grantrevoke.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package model 5 6 import ( 7 "github.com/juju/cmd" 8 "github.com/juju/errors" 9 "gopkg.in/juju/names.v2" 10 11 "github.com/juju/juju/api/applicationoffers" 12 jujucmd "github.com/juju/juju/cmd" 13 "github.com/juju/juju/cmd/juju/block" 14 "github.com/juju/juju/cmd/modelcmd" 15 "github.com/juju/juju/core/crossmodel" 16 "github.com/juju/juju/jujuclient" 17 "github.com/juju/juju/permission" 18 ) 19 20 var usageGrantSummary = ` 21 Grants access level to a Juju user for a model, controller, or application offer.`[1:] 22 23 var usageGrantDetails = ` 24 By default, the controller is the current controller. 25 26 Users with read access are limited in what they can do with models: 27 ` + "`juju models`, `juju machines`, and `juju status`" + `. 28 29 Valid access levels for models are: 30 read 31 write 32 admin 33 34 Valid access levels for controllers are: 35 login 36 superuser 37 38 Valid access levels for application offers are: 39 read 40 consume 41 admin 42 43 Examples: 44 Grant user 'joe' 'read' access to model 'mymodel': 45 46 juju grant joe read mymodel 47 48 Grant user 'jim' 'write' access to model 'mymodel': 49 50 juju grant jim write mymodel 51 52 Grant user 'sam' 'read' access to models 'model1' and 'model2': 53 54 juju grant sam read model1 model2 55 56 Grant user 'joe' 'read' access to application offer 'fred/prod.hosted-mysql': 57 58 juju grant joe read fred/prod.hosted-mysql 59 60 Grant user 'jim' 'consume' access to application offer 'fred/prod.hosted-mysql': 61 62 juju grant jim consume fred/prod.hosted-mysql 63 64 Grant user 'sam' 'read' access to application offers 'fred/prod.hosted-mysql' and 'mary/test.hosted-mysql': 65 66 juju grant sam read fred/prod.hosted-mysql mary/test.hosted-mysql 67 68 See also: 69 revoke 70 add-user`[1:] 71 72 var usageRevokeSummary = ` 73 Revokes access from a Juju user for a model, controller, or application offer.`[1:] 74 75 var usageRevokeDetails = ` 76 By default, the controller is the current controller. 77 78 Revoking write access, from a user who has that permission, will leave 79 that user with read access. Revoking read access, however, also revokes 80 write access. 81 82 Examples: 83 Revoke 'read' (and 'write') access from user 'joe' for model 'mymodel': 84 85 juju revoke joe read mymodel 86 87 Revoke 'write' access from user 'sam' for models 'model1' and 'model2': 88 89 juju revoke sam write model1 model2 90 91 Revoke 'read' (and 'write') access from user 'joe' for application offer 'fred/prod.hosted-mysql': 92 93 juju revoke joe read fred/prod.hosted-mysql 94 95 Revoke 'consume' access from user 'sam' for models 'fred/prod.hosted-mysql' and 'mary/test.hosted-mysql': 96 97 juju revoke sam consume fred/prod.hosted-mysql mary/test.hosted-mysql 98 99 See also: 100 grant`[1:] 101 102 type accessCommand struct { 103 modelcmd.ControllerCommandBase 104 105 User string 106 ModelNames []string 107 OfferURLs []*crossmodel.OfferURL 108 Access string 109 } 110 111 // Init implements cmd.Command. 112 func (c *accessCommand) Init(args []string) error { 113 if len(args) < 1 { 114 return errors.New("no user specified") 115 } 116 117 if len(args) < 2 { 118 return errors.New("no permission level specified") 119 } 120 121 c.User = args[0] 122 c.Access = args[1] 123 // The remaining args are either model names or offer names. 124 for _, arg := range args[2:] { 125 url, err := crossmodel.ParseOfferURL(arg) 126 if err == nil { 127 c.OfferURLs = append(c.OfferURLs, url) 128 continue 129 } 130 maybeModelName := arg 131 if jujuclient.IsQualifiedModelName(maybeModelName) { 132 var err error 133 maybeModelName, _, err = jujuclient.SplitModelName(maybeModelName) 134 if err != nil { 135 return errors.Annotatef(err, "validating model name %q", maybeModelName) 136 } 137 } 138 if !names.IsValidModelName(maybeModelName) { 139 return errors.NotValidf("model name %q", maybeModelName) 140 } 141 c.ModelNames = append(c.ModelNames, arg) 142 } 143 if len(c.ModelNames) > 0 && len(c.OfferURLs) > 0 { 144 return errors.New("either specify model names or offer URLs but not both") 145 } 146 147 if len(c.ModelNames) > 0 || len(c.OfferURLs) > 0 { 148 if err := permission.ValidateControllerAccess(permission.Access(c.Access)); err == nil { 149 return errors.Errorf("You have specified a controller access permission %q.\n"+ 150 "If you intended to change controller access, do not specify any model names or offer URLs.\n"+ 151 "See 'juju help grant'.", c.Access) 152 } 153 } 154 if len(c.ModelNames) > 0 { 155 return permission.ValidateModelAccess(permission.Access(c.Access)) 156 } 157 if len(c.OfferURLs) > 0 { 158 return permission.ValidateOfferAccess(permission.Access(c.Access)) 159 } 160 if err := permission.ValidateModelAccess(permission.Access(c.Access)); err == nil { 161 return errors.Errorf("You have specified a model access permission %q.\n"+ 162 "If you intended to change model access, you need to specify one or more model names.\n"+ 163 "See 'juju help grant'.", c.Access) 164 } 165 return nil 166 } 167 168 // NewGrantCommand returns a new grant command. 169 func NewGrantCommand() cmd.Command { 170 return modelcmd.WrapController(&grantCommand{}) 171 } 172 173 // grantCommand represents the command to grant a user access to one or more models. 174 type grantCommand struct { 175 accessCommand 176 modelsApi GrantModelAPI 177 offersApi GrantOfferAPI 178 } 179 180 // Info implements Command.Info. 181 func (c *grantCommand) Info() *cmd.Info { 182 return jujucmd.Info(&cmd.Info{ 183 Name: "grant", 184 Args: "<user name> <permission> [<model name> ... | <offer url> ...]", 185 Purpose: usageGrantSummary, 186 Doc: usageGrantDetails, 187 }) 188 } 189 190 func (c *grantCommand) getModelAPI() (GrantModelAPI, error) { 191 if c.modelsApi != nil { 192 return c.modelsApi, nil 193 } 194 return c.NewModelManagerAPIClient() 195 } 196 197 func (c *grantCommand) getControllerAPI() (GrantControllerAPI, error) { 198 return c.NewControllerAPIClient() 199 } 200 201 func (c *grantCommand) getOfferAPI() (GrantOfferAPI, error) { 202 if c.offersApi != nil { 203 return c.offersApi, nil 204 } 205 root, err := c.NewAPIRoot() 206 if err != nil { 207 return nil, errors.Trace(err) 208 } 209 return applicationoffers.NewClient(root), nil 210 } 211 212 // GrantModelAPI defines the API functions used by the grant command. 213 type GrantModelAPI interface { 214 Close() error 215 GrantModel(user, access string, modelUUIDs ...string) error 216 } 217 218 // GrantControllerAPI defines the API functions used by the grant command. 219 type GrantControllerAPI interface { 220 Close() error 221 GrantController(user, access string) error 222 } 223 224 // GrantOfferAPI defines the API functions used by the grant command. 225 type GrantOfferAPI interface { 226 Close() error 227 GrantOffer(user, access string, offerURLs ...string) error 228 } 229 230 // Run implements cmd.Command. 231 func (c *grantCommand) Run(ctx *cmd.Context) error { 232 if len(c.ModelNames) > 0 { 233 return c.runForModel() 234 } 235 if len(c.OfferURLs) > 0 { 236 if err := setUnsetUsers(c, c.OfferURLs); err != nil { 237 return errors.Trace(err) 238 } 239 return c.runForOffers() 240 } 241 return c.runForController() 242 } 243 244 func (c *grantCommand) runForController() error { 245 client, err := c.getControllerAPI() 246 if err != nil { 247 return err 248 } 249 defer client.Close() 250 251 return block.ProcessBlockedError(client.GrantController(c.User, c.Access), block.BlockChange) 252 } 253 254 func (c *grantCommand) runForModel() error { 255 client, err := c.getModelAPI() 256 if err != nil { 257 return err 258 } 259 defer client.Close() 260 261 models, err := c.ModelUUIDs(c.ModelNames) 262 if err != nil { 263 return err 264 } 265 return block.ProcessBlockedError(client.GrantModel(c.User, c.Access, models...), block.BlockChange) 266 } 267 268 func (c *grantCommand) runForOffers() error { 269 client, err := c.getOfferAPI() 270 if err != nil { 271 return err 272 } 273 defer client.Close() 274 275 urls := make([]string, len(c.OfferURLs)) 276 for i, url := range c.OfferURLs { 277 urls[i] = url.String() 278 } 279 err = client.GrantOffer(c.User, c.Access, urls...) 280 return block.ProcessBlockedError(err, block.BlockChange) 281 } 282 283 // NewRevokeCommand returns a new revoke command. 284 func NewRevokeCommand() cmd.Command { 285 return modelcmd.WrapController(&revokeCommand{}) 286 } 287 288 // revokeCommand revokes a user's access to models. 289 type revokeCommand struct { 290 accessCommand 291 modelsApi RevokeModelAPI 292 offersApi RevokeOfferAPI 293 } 294 295 // Info implements cmd.Command. 296 func (c *revokeCommand) Info() *cmd.Info { 297 return jujucmd.Info(&cmd.Info{ 298 Name: "revoke", 299 Args: "<user name> <permission> [<model name> ... | <offer url> ...]", 300 Purpose: usageRevokeSummary, 301 Doc: usageRevokeDetails, 302 }) 303 } 304 305 func (c *revokeCommand) getModelAPI() (RevokeModelAPI, error) { 306 if c.modelsApi != nil { 307 return c.modelsApi, nil 308 } 309 return c.NewModelManagerAPIClient() 310 } 311 312 func (c *revokeCommand) getControllerAPI() (RevokeControllerAPI, error) { 313 return c.NewControllerAPIClient() 314 } 315 316 func (c *revokeCommand) getOfferAPI() (RevokeOfferAPI, error) { 317 if c.offersApi != nil { 318 return c.offersApi, nil 319 } 320 root, err := c.NewAPIRoot() 321 if err != nil { 322 return nil, errors.Trace(err) 323 } 324 return applicationoffers.NewClient(root), nil 325 } 326 327 // RevokeModelAPI defines the API functions used by the revoke command. 328 type RevokeModelAPI interface { 329 Close() error 330 RevokeModel(user, access string, modelUUIDs ...string) error 331 } 332 333 // RevokeControllerAPI defines the API functions used by the revoke command. 334 type RevokeControllerAPI interface { 335 Close() error 336 RevokeController(user, access string) error 337 } 338 339 // RevokeOfferAPI defines the API functions used by the revoke command. 340 type RevokeOfferAPI interface { 341 Close() error 342 RevokeOffer(user, access string, offerURLs ...string) error 343 } 344 345 // Run implements cmd.Command. 346 func (c *revokeCommand) Run(ctx *cmd.Context) error { 347 if len(c.ModelNames) > 0 { 348 return c.runForModel() 349 } 350 if len(c.OfferURLs) > 0 { 351 if err := setUnsetUsers(c, c.OfferURLs); err != nil { 352 return errors.Trace(err) 353 } 354 return c.runForOffers() 355 } 356 return c.runForController() 357 } 358 359 func (c *revokeCommand) runForController() error { 360 client, err := c.getControllerAPI() 361 if err != nil { 362 return err 363 } 364 defer client.Close() 365 366 return block.ProcessBlockedError(client.RevokeController(c.User, c.Access), block.BlockChange) 367 } 368 369 func (c *revokeCommand) runForModel() error { 370 client, err := c.getModelAPI() 371 if err != nil { 372 return err 373 } 374 defer client.Close() 375 376 models, err := c.ModelUUIDs(c.ModelNames) 377 if err != nil { 378 return err 379 } 380 return block.ProcessBlockedError(client.RevokeModel(c.User, c.Access, models...), block.BlockChange) 381 } 382 383 type accountDetailsGetter interface { 384 CurrentAccountDetails() (*jujuclient.AccountDetails, error) 385 } 386 387 // setUnsetUsers sets any empty user entries in the given offer URLs 388 // to the currently logged in user. 389 func setUnsetUsers(c accountDetailsGetter, offerURLs []*crossmodel.OfferURL) error { 390 var currentAccountDetails *jujuclient.AccountDetails 391 for _, url := range offerURLs { 392 if url.User != "" { 393 continue 394 } 395 if currentAccountDetails == nil { 396 var err error 397 currentAccountDetails, err = c.CurrentAccountDetails() 398 if err != nil { 399 return errors.Trace(err) 400 } 401 } 402 url.User = currentAccountDetails.User 403 } 404 return nil 405 } 406 407 // offersForModel group the offer URLs per model. 408 func offersForModel(offerURLs []*crossmodel.OfferURL) map[string][]string { 409 offersForModel := make(map[string][]string) 410 for _, url := range offerURLs { 411 fullName := jujuclient.JoinOwnerModelName(names.NewUserTag(url.User), url.ModelName) 412 offers := offersForModel[fullName] 413 offers = append(offers, url.ApplicationName) 414 offersForModel[fullName] = offers 415 } 416 return offersForModel 417 } 418 419 func (c *revokeCommand) runForOffers() error { 420 client, err := c.getOfferAPI() 421 if err != nil { 422 return err 423 } 424 defer client.Close() 425 426 urls := make([]string, len(c.OfferURLs)) 427 for i, url := range c.OfferURLs { 428 urls[i] = url.String() 429 } 430 err = client.RevokeOffer(c.User, c.Access, urls...) 431 return block.ProcessBlockedError(err, block.BlockChange) 432 }