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  }