github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/api/cloud/cloud.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package cloud
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/loggo"
     9  	"gopkg.in/juju/names.v2"
    10  
    11  	"github.com/juju/juju/api/base"
    12  	"github.com/juju/juju/apiserver/common"
    13  	"github.com/juju/juju/apiserver/params"
    14  	jujucloud "github.com/juju/juju/cloud"
    15  	"github.com/juju/juju/permission"
    16  )
    17  
    18  var logger = loggo.GetLogger("juju.api.cloud")
    19  
    20  // Client provides methods that the Juju client command uses to interact
    21  // with models stored in the Juju Server.
    22  type Client struct {
    23  	base.ClientFacade
    24  	facade base.FacadeCaller
    25  }
    26  
    27  // NewClient creates a new `Client` based on an existing authenticated API
    28  // connection.
    29  func NewClient(st base.APICallCloser) *Client {
    30  	frontend, backend := base.NewClientFacade(st, "Cloud")
    31  	return &Client{ClientFacade: frontend, facade: backend}
    32  }
    33  
    34  // Clouds returns the details of all clouds supported by the controller.
    35  func (c *Client) Clouds() (map[names.CloudTag]jujucloud.Cloud, error) {
    36  	var result params.CloudsResult
    37  	if err := c.facade.FacadeCall("Clouds", nil, &result); err != nil {
    38  		return nil, errors.Trace(err)
    39  	}
    40  	clouds := make(map[names.CloudTag]jujucloud.Cloud)
    41  	for tagString, cloud := range result.Clouds {
    42  		tag, err := names.ParseCloudTag(tagString)
    43  		if err != nil {
    44  			return nil, errors.Trace(err)
    45  		}
    46  		clouds[tag] = common.CloudFromParams(tag.Id(), cloud)
    47  	}
    48  	return clouds, nil
    49  }
    50  
    51  // Cloud returns the details of the cloud with the given tag.
    52  func (c *Client) Cloud(tag names.CloudTag) (jujucloud.Cloud, error) {
    53  	var results params.CloudResults
    54  	args := params.Entities{[]params.Entity{{tag.String()}}}
    55  	if err := c.facade.FacadeCall("Cloud", args, &results); err != nil {
    56  		return jujucloud.Cloud{}, errors.Trace(err)
    57  	}
    58  	if len(results.Results) != 1 {
    59  		return jujucloud.Cloud{}, errors.Errorf("expected 1 result, got %d", len(results.Results))
    60  	}
    61  	if results.Results[0].Error != nil {
    62  		return jujucloud.Cloud{}, results.Results[0].Error
    63  	}
    64  	return common.CloudFromParams(tag.Id(), *results.Results[0].Cloud), nil
    65  }
    66  
    67  // DefaultCloud returns the tag of the cloud that models will be
    68  // created in by default.
    69  func (c *Client) DefaultCloud() (names.CloudTag, error) {
    70  	var result params.StringResult
    71  	if err := c.facade.FacadeCall("DefaultCloud", nil, &result); err != nil {
    72  		return names.CloudTag{}, errors.Trace(err)
    73  	}
    74  	if result.Error != nil {
    75  		return names.CloudTag{}, result.Error
    76  	}
    77  	cloudTag, err := names.ParseCloudTag(result.Result)
    78  	if err != nil {
    79  		return names.CloudTag{}, errors.Trace(err)
    80  	}
    81  	return cloudTag, nil
    82  }
    83  
    84  // UserCredentials returns the tags for cloud credentials available to a user for
    85  // use with a specific cloud.
    86  func (c *Client) UserCredentials(user names.UserTag, cloud names.CloudTag) ([]names.CloudCredentialTag, error) {
    87  	var results params.StringsResults
    88  	args := params.UserClouds{[]params.UserCloud{
    89  		{UserTag: user.String(), CloudTag: cloud.String()},
    90  	}}
    91  	if err := c.facade.FacadeCall("UserCredentials", args, &results); err != nil {
    92  		return nil, errors.Trace(err)
    93  	}
    94  	if len(results.Results) != 1 {
    95  		return nil, errors.Errorf("expected 1 result, got %d", len(results.Results))
    96  	}
    97  	if results.Results[0].Error != nil {
    98  		return nil, results.Results[0].Error
    99  	}
   100  	tags := make([]names.CloudCredentialTag, len(results.Results[0].Result))
   101  	for i, s := range results.Results[0].Result {
   102  		tag, err := names.ParseCloudCredentialTag(s)
   103  		if err != nil {
   104  			return nil, errors.Trace(err)
   105  		}
   106  		tags[i] = tag
   107  	}
   108  	return tags, nil
   109  }
   110  
   111  // UpdateCredentialsCheckModels updates a cloud credential content
   112  // stored on the controller. This call validates that the new content works
   113  // for all models that are using this credential.
   114  func (c *Client) UpdateCredentialsCheckModels(tag names.CloudCredentialTag, credential jujucloud.Credential) ([]params.UpdateCredentialModelResult, error) {
   115  	in := params.UpdateCredentialArgs{
   116  		Credentials: []params.TaggedCredential{{
   117  			Tag: tag.String(),
   118  			Credential: params.CloudCredential{
   119  				AuthType:   string(credential.AuthType()),
   120  				Attributes: credential.Attributes(),
   121  			},
   122  		}},
   123  	}
   124  
   125  	if c.facade.BestAPIVersion() < 3 {
   126  		var out params.ErrorResults
   127  		if err := c.facade.FacadeCall("UpdateCredentials", in, &out); err != nil {
   128  			return nil, errors.Trace(err)
   129  		}
   130  		if len(out.Results) != 1 {
   131  			return nil, errors.Errorf("expected 1 result for when updating credential %q, got %d", tag.Name(), len(out.Results))
   132  		}
   133  		if out.Results[0].Error != nil {
   134  			return nil, errors.Trace(out.Results[0].Error)
   135  		}
   136  		return nil, nil
   137  	}
   138  
   139  	var out params.UpdateCredentialResults
   140  	if err := c.facade.FacadeCall("UpdateCredentialsCheckModels", in, &out); err != nil {
   141  		return nil, errors.Trace(err)
   142  	}
   143  	if len(out.Results) != 1 {
   144  		return nil, errors.Errorf("expected 1 result for when updating credential %q, got %d", tag.Name(), len(out.Results))
   145  	}
   146  	if out.Results[0].Error != nil {
   147  		// Unlike many other places, we want to return something valid here to provide more details.
   148  		return out.Results[0].Models, errors.Trace(out.Results[0].Error)
   149  	}
   150  	return out.Results[0].Models, nil
   151  }
   152  
   153  // RevokeCredential revokes/deletes a cloud credential.
   154  func (c *Client) RevokeCredential(tag names.CloudCredentialTag) error {
   155  	var results params.ErrorResults
   156  
   157  	if c.facade.BestAPIVersion() < 3 {
   158  		args := params.Entities{
   159  			Entities: []params.Entity{{
   160  				Tag: tag.String(),
   161  			}},
   162  		}
   163  		if err := c.facade.FacadeCall("RevokeCredentials", args, &results); err != nil {
   164  			return errors.Trace(err)
   165  		}
   166  		return results.OneError()
   167  	}
   168  
   169  	args := params.RevokeCredentialArgs{
   170  		Credentials: []params.RevokeCredentialArg{
   171  			{Tag: tag.String()},
   172  		},
   173  	}
   174  	if err := c.facade.FacadeCall("RevokeCredentialsCheckModels", args, &results); err != nil {
   175  		return errors.Trace(err)
   176  	}
   177  	return results.OneError()
   178  }
   179  
   180  // Credentials returns a slice of credential values for the specified tags.
   181  // Secrets are excluded from the credential attributes.
   182  func (c *Client) Credentials(tags ...names.CloudCredentialTag) ([]params.CloudCredentialResult, error) {
   183  	if len(tags) == 0 {
   184  		return []params.CloudCredentialResult{}, nil
   185  	}
   186  	var results params.CloudCredentialResults
   187  	args := params.Entities{
   188  		Entities: make([]params.Entity, len(tags)),
   189  	}
   190  	for i, tag := range tags {
   191  		args.Entities[i].Tag = tag.String()
   192  	}
   193  	if err := c.facade.FacadeCall("Credential", args, &results); err != nil {
   194  		return nil, errors.Trace(err)
   195  	}
   196  	return results.Results, nil
   197  }
   198  
   199  // AddCredential adds a credential to the controller with a given tag.
   200  // This can be a credential for a cloud that is not the same cloud as the controller's host.
   201  func (c *Client) AddCredential(tag string, credential jujucloud.Credential) error {
   202  	if bestVer := c.BestAPIVersion(); bestVer < 2 {
   203  		return errors.NotImplementedf("AddCredential() (need v2+, have v%d)", bestVer)
   204  	}
   205  	var results params.ErrorResults
   206  	cloudCredential := params.CloudCredential{
   207  		AuthType:   string(credential.AuthType()),
   208  		Attributes: credential.Attributes(),
   209  	}
   210  	args := params.TaggedCredentials{
   211  		Credentials: []params.TaggedCredential{{
   212  			Tag:        tag,
   213  			Credential: cloudCredential,
   214  		},
   215  		}}
   216  	if err := c.facade.FacadeCall("AddCredentials", args, &results); err != nil {
   217  		return errors.Trace(err)
   218  	}
   219  	return results.OneError()
   220  }
   221  
   222  // AddCloud adds a new cloud to current controller.
   223  func (c *Client) AddCloud(cloud jujucloud.Cloud) error {
   224  	if bestVer := c.BestAPIVersion(); bestVer < 2 {
   225  		return errors.NotImplementedf("AddCloud() (need v2+, have v%d)", bestVer)
   226  	}
   227  	args := params.AddCloudArgs{Name: cloud.Name, Cloud: common.CloudToParams(cloud)}
   228  	err := c.facade.FacadeCall("AddCloud", args, nil)
   229  	if err != nil {
   230  		return errors.Trace(err)
   231  	}
   232  	return nil
   233  }
   234  
   235  // RemoveCloud removes a cloud from the current controller.
   236  func (c *Client) RemoveCloud(cloud string) error {
   237  	if bestVer := c.BestAPIVersion(); bestVer < 2 {
   238  		return errors.NotImplementedf("RemoveCloud() (need v2+, have v%d)", bestVer)
   239  	}
   240  	args := params.Entities{Entities: []params.Entity{{Tag: names.NewCloudTag(cloud).String()}}}
   241  	var result params.ErrorResults
   242  	err := c.facade.FacadeCall("RemoveClouds", args, &result)
   243  	if err != nil {
   244  		return errors.Trace(err)
   245  	}
   246  	return result.OneError()
   247  }
   248  
   249  // CredentialContents returns contents of the credential values for the specified
   250  // cloud and credential name. Secrets will be included if requested.
   251  func (c *Client) CredentialContents(cloud, credential string, withSecrets bool) ([]params.CredentialContentResult, error) {
   252  	if bestVer := c.BestAPIVersion(); bestVer < 2 {
   253  		return nil, errors.NotImplementedf("CredentialContents() (need v2+, have v%d)", bestVer)
   254  	}
   255  	oneCredential := params.CloudCredentialArg{}
   256  	if cloud == "" && credential == "" {
   257  		// this is valid and means we want all.
   258  	} else if cloud == "" {
   259  		return nil, errors.New("cloud name must be supplied")
   260  	} else if credential == "" {
   261  		return nil, errors.New("credential name must be supplied")
   262  	} else {
   263  		oneCredential.CloudName = cloud
   264  		oneCredential.CredentialName = credential
   265  	}
   266  	var out params.CredentialContentResults
   267  	in := params.CloudCredentialArgs{
   268  		IncludeSecrets: withSecrets,
   269  	}
   270  	if !oneCredential.IsEmpty() {
   271  		in.Credentials = []params.CloudCredentialArg{oneCredential}
   272  	}
   273  	err := c.facade.FacadeCall("CredentialContents", in, &out)
   274  	if err != nil {
   275  		return nil, errors.Trace(err)
   276  	}
   277  	if !oneCredential.IsEmpty() && len(out.Results) != 1 {
   278  		return nil, errors.Errorf("expected 1 result for credential %q on cloud %q, got %d", cloud, credential, len(out.Results))
   279  	}
   280  	return out.Results, nil
   281  }
   282  
   283  // GrantCloud grants a user access to a cloud.
   284  func (c *Client) GrantCloud(user, access string, clouds ...string) error {
   285  	if bestVer := c.BestAPIVersion(); bestVer < 3 {
   286  		return errors.NotImplementedf("GrantCloud() (need v3+, have v%d)", bestVer)
   287  	}
   288  	return c.modifyCloudUser(params.GrantCloudAccess, user, access, clouds)
   289  }
   290  
   291  // RevokeCloud revokes a user's access to a cloud.
   292  func (c *Client) RevokeCloud(user, access string, clouds ...string) error {
   293  	if bestVer := c.BestAPIVersion(); bestVer < 3 {
   294  		return errors.NotImplementedf("RevokeCloud() (need v3+, have v%d)", bestVer)
   295  	}
   296  	return c.modifyCloudUser(params.RevokeCloudAccess, user, access, clouds)
   297  }
   298  
   299  func (c *Client) modifyCloudUser(action params.CloudAction, user, access string, clouds []string) error {
   300  	var args params.ModifyCloudAccessRequest
   301  
   302  	if !names.IsValidUser(user) {
   303  		return errors.Errorf("invalid username: %q", user)
   304  	}
   305  	userTag := names.NewUserTag(user)
   306  
   307  	cloudAccess := permission.Access(access)
   308  	if err := permission.ValidateCloudAccess(cloudAccess); err != nil {
   309  		return errors.Trace(err)
   310  	}
   311  	for _, cloud := range clouds {
   312  		if !names.IsValidCloud(cloud) {
   313  			return errors.NotValidf("cloud %q", cloud)
   314  		}
   315  		cloudTag := names.NewCloudTag(cloud)
   316  		args.Changes = append(args.Changes, params.ModifyCloudAccess{
   317  			UserTag:  userTag.String(),
   318  			Action:   action,
   319  			Access:   access,
   320  			CloudTag: cloudTag.String(),
   321  		})
   322  	}
   323  
   324  	var result params.ErrorResults
   325  	err := c.facade.FacadeCall("ModifyCloudAccess", args, &result)
   326  	if err != nil {
   327  		return errors.Trace(err)
   328  	}
   329  	if len(result.Results) != len(args.Changes) {
   330  		return errors.Errorf("expected %d results, got %d", len(args.Changes), len(result.Results))
   331  	}
   332  
   333  	for i, r := range result.Results {
   334  		if r.Error != nil && r.Error.Code == params.CodeAlreadyExists {
   335  			logger.Warningf("cloud %q is already shared with %q", clouds[i], userTag.Id())
   336  			result.Results[i].Error = nil
   337  		}
   338  	}
   339  	return result.Combine()
   340  }