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

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for infos.
     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/base"
    12  	cloudapi "github.com/juju/juju/api/cloud"
    13  	"github.com/juju/juju/api/modelmanager"
    14  	"github.com/juju/juju/cloud"
    15  	jujucmd "github.com/juju/juju/cmd"
    16  	"github.com/juju/juju/cmd/juju/common"
    17  	"github.com/juju/juju/cmd/modelcmd"
    18  )
    19  
    20  // ModelCredentialAPI defines methods used to replace model credential.
    21  type ModelCredentialAPI interface {
    22  	Close() error
    23  	ChangeModelCredential(model names.ModelTag, credential names.CloudCredentialTag) error
    24  }
    25  
    26  // CloudAPI defines methods used to detemine if cloud credential exists on the controller.
    27  type CloudAPI interface {
    28  	Close() error
    29  	UserCredentials(names.UserTag, names.CloudTag) ([]names.CloudCredentialTag, error)
    30  	AddCredential(tag string, credential cloud.Credential) error
    31  }
    32  
    33  // modelCredentialCommand allows to change, replace a cloud credential for a model.
    34  type modelCredentialCommand struct {
    35  	modelcmd.ModelCommandBase
    36  
    37  	cloud      string
    38  	credential string
    39  
    40  	newAPIRootFunc            func() (base.APICallCloser, error)
    41  	newModelCredentialAPIFunc func(base.APICallCloser) ModelCredentialAPI
    42  	newCloudAPIFunc           func(base.APICallCloser) CloudAPI
    43  }
    44  
    45  func NewModelCredentialCommand() cmd.Command {
    46  	command := &modelCredentialCommand{
    47  		newModelCredentialAPIFunc: func(root base.APICallCloser) ModelCredentialAPI {
    48  			return modelmanager.NewClient(root)
    49  		},
    50  		newCloudAPIFunc: func(root base.APICallCloser) CloudAPI {
    51  			return cloudapi.NewClient(root)
    52  		},
    53  	}
    54  	command.newAPIRootFunc = func() (base.APICallCloser, error) {
    55  		return command.NewControllerAPIRoot()
    56  	}
    57  	return modelcmd.Wrap(command)
    58  }
    59  
    60  // Info implements Command.Info.
    61  func (c *modelCredentialCommand) Info() *cmd.Info {
    62  	return jujucmd.Info(&cmd.Info{
    63  		Name:    "set-credential",
    64  		Args:    "<cloud name> <credential name>",
    65  		Purpose: "Relates a remote credential to a model.",
    66  		Doc:     modelCredentialDoc,
    67  	})
    68  }
    69  
    70  // Init implements Command.Init.
    71  func (c *modelCredentialCommand) Init(args []string) error {
    72  	if len(args) != 2 {
    73  		return errors.Errorf("Usage: juju set-credential [options] <cloud name> <credential name>")
    74  	}
    75  	if !names.IsValidCloud(args[0]) {
    76  		return errors.NotValidf("cloud name %q", args[0])
    77  	}
    78  	if !names.IsValidCloudCredentialName(args[1]) {
    79  		return errors.NotValidf("cloud credential name %q", args[1])
    80  	}
    81  	c.cloud = args[0]
    82  	c.credential = args[1]
    83  	return nil
    84  }
    85  
    86  // Run implements Command.Run.
    87  func (c *modelCredentialCommand) Run(ctx *cmd.Context) error {
    88  	fail := func(e error) error {
    89  		ctx.Infof("Failed to change model credential: %v", e)
    90  		return e
    91  	}
    92  
    93  	root, err := c.newAPIRootFunc()
    94  	if err != nil {
    95  		return fail(errors.Annotate(err, "opening API connection"))
    96  	}
    97  	defer root.Close()
    98  
    99  	accountDetails, err := c.CurrentAccountDetails()
   100  	if err != nil {
   101  		return fail(errors.Annotate(err, "getting current account"))
   102  	}
   103  	userTag := names.NewUserTag(accountDetails.User)
   104  	cloudTag := names.NewCloudTag(c.cloud)
   105  	credentialTag, err := common.ResolveCloudCredentialTag(userTag, cloudTag, c.credential)
   106  	if err != nil {
   107  		return fail(errors.Annotate(err, "resolving credential"))
   108  	}
   109  
   110  	cloudClient := c.newCloudAPIFunc(root)
   111  	defer cloudClient.Close()
   112  
   113  	remote := false
   114  	remoteCredentials, err := cloudClient.UserCredentials(userTag, cloudTag)
   115  	if err != nil {
   116  		// This is ok - we can proceed with local ones anyway.
   117  		ctx.Infof("Could not determine if there are remote credentials for the user: %v", err)
   118  	} else {
   119  		for _, credTag := range remoteCredentials {
   120  			if credTag == credentialTag {
   121  				remote = true
   122  				ctx.Infof("Found credential remotely, on the controller. Not looking locally...")
   123  				break
   124  			}
   125  		}
   126  	}
   127  
   128  	// Credential does not exist remotely. Upload it.
   129  	if !remote {
   130  		ctx.Infof("Did not find credential remotely. Looking locally...")
   131  		credential, err := c.findCredentialLocally(ctx)
   132  		if err != nil {
   133  			return fail((err))
   134  		}
   135  		ctx.Infof("Uploading local credential to the controller.")
   136  		err = cloudClient.AddCredential(credentialTag.String(), *credential)
   137  		if err != nil {
   138  			return fail(err)
   139  		}
   140  	}
   141  
   142  	modelName, modelDetails, err := c.ModelDetails()
   143  	if err != nil {
   144  		return fail(errors.Trace(err))
   145  	}
   146  	modelTag := names.NewModelTag(modelDetails.ModelUUID)
   147  
   148  	modelClient := c.newModelCredentialAPIFunc(root)
   149  	defer modelClient.Close()
   150  
   151  	err = modelClient.ChangeModelCredential(modelTag, credentialTag)
   152  	if err != nil {
   153  		return fail(errors.Trace(err))
   154  	}
   155  	ctx.Infof("Changed cloud credential on model %q to %q.", modelName, c.credential)
   156  	return nil
   157  }
   158  
   159  func (c *modelCredentialCommand) findCredentialLocally(ctx *cmd.Context) (*cloud.Credential, error) {
   160  	foundcloud, err := common.CloudByName(c.cloud)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  	getCredentialsParams := modelcmd.GetCredentialsParams{
   165  		Cloud:          *foundcloud,
   166  		CredentialName: c.credential,
   167  	}
   168  	credential, _, _, err := modelcmd.GetCredentials(ctx, c.ClientStore(), getCredentialsParams)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  	return credential, nil
   173  }
   174  
   175  const modelCredentialDoc = `
   176  This command relates a credential cached on a controller to a specific model.
   177  It does not change/update the contents of an existing active credential. See
   178  command ` + "`update-credential`" + ` for that.
   179  
   180  The credential specified may exist locally (on the client), remotely (on the
   181  controller), or both. The command will error out if the credential is stored
   182  neither remotely nor locally.
   183  
   184  When remote, the credential will be related to the specified model.
   185  
   186  When local and not remote, the credential will first be uploaded to the
   187  controller and then related.
   188  
   189  This command does not affect an existing relation between the specified
   190  credential and another model. If the credential is already related to a model
   191  this operation will result in that credential being related to two models.
   192  
   193  Use the ` + "`show-credential`" + ` command to see how remote credentials are related
   194  to models.
   195  
   196  Examples:
   197  
   198  For cloud 'aws', relate remote credential 'bob' to model 'trinity':
   199  
   200      juju set-credential -m trinity aws bob
   201  
   202  See also:
   203      credentials
   204      show-credential
   205      update-credential
   206  `