1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
9
"github.com/juju/errors"
10
"github.com/juju/utils/set"
11
"gopkg.in/juju/names.v2"
12
"gopkg.in/mgo.v2/bson"
15
"github.com/juju/juju/cloud"
18
// cloudCredentialDoc records information about a user's cloud credentials.
19
type cloudCredentialDoc struct {
20
DocID string `bson:"_id"`
21
Owner string `bson:"owner"`
22
Cloud string `bson:"cloud"`
23
Name string `bson:"name"`
24
AuthType string `bson:"auth-type"`
25
Attributes map[string]string `bson:"attributes,omitempty"`
28
// CloudCredentials returns the user's cloud credentials for a given cloud,
29
// keyed by credential name.
30
func (st *State) CloudCredentials(user names.UserTag, cloudName string) (map[string]cloud.Credential, error) {
31
coll, cleanup := st.getCollection(cloudCredentialsC)
34
var doc cloudCredentialDoc
35
credentials := make(map[string]cloud.Credential)
36
iter := coll.Find(bson.D{
37
{"owner", user.Canonical()},
41
credentials[doc.Name] = doc.toCredential()
43
if err := iter.Err(); err != nil {
44
return nil, errors.Annotatef(
45
err, "cannot get cloud credentials for user %q, cloud %q",
46
user.Canonical(), cloudName,
49
return credentials, nil
52
// UpdateCloudCredentials updates the user's cloud credentials. Any existing
53
// credentials with the same names will be replaced, and any other credentials
54
// not in the updated set will be untouched.
55
func (st *State) UpdateCloudCredentials(user names.UserTag, cloudName string, credentials map[string]cloud.Credential) error {
56
buildTxn := func(attempt int) ([]txn.Op, error) {
57
cloud, err := st.Cloud(cloudName)
59
return nil, errors.Trace(err)
61
ops, err := validateCloudCredentials(cloud, cloudName, credentials)
63
return nil, errors.Annotate(err, "validating cloud credentials")
65
existingCreds, err := st.CloudCredentials(user, cloudName)
67
return nil, errors.Maskf(err, "fetching cloud credentials")
69
for credName, cred := range credentials {
70
if _, ok := existingCreds[credName]; ok {
71
ops = append(ops, updateCloudCredentialOp(user, cloudName, credName, cred))
73
ops = append(ops, createCloudCredentialOp(user, cloudName, credName, cred))
78
if err := st.run(buildTxn); err != nil {
79
return errors.Annotatef(
80
err, "updating cloud credentials for user %q, cloud %q",
81
user.String(), cloudName,
87
// createCloudCredentialOp returns a txn.Op that will create
88
// a cloud credential.
89
func createCloudCredentialOp(user names.UserTag, cloudName, credName string, cred cloud.Credential) txn.Op {
92
Id: cloudCredentialDocID(user, cloudName, credName),
93
Assert: txn.DocMissing,
94
Insert: &cloudCredentialDoc{
95
Owner: user.Canonical(),
98
AuthType: string(cred.AuthType()),
99
Attributes: cred.Attributes(),
104
// updateCloudCredentialOp returns a txn.Op that will update
105
// a cloud credential.
106
func updateCloudCredentialOp(user names.UserTag, cloudName, credName string, cred cloud.Credential) txn.Op {
108
C: cloudCredentialsC,
109
Id: cloudCredentialDocID(user, cloudName, credName),
110
Assert: txn.DocExists,
111
Update: bson.D{{"$set", bson.D{
112
{"auth-type", string(cred.AuthType())},
113
{"attributes", cred.Attributes()},
118
func cloudCredentialDocID(user names.UserTag, cloudName, credentialName string) string {
119
return fmt.Sprintf("%s#%s#%s", user.Canonical(), cloudName, credentialName)
122
func (c cloudCredentialDoc) toCredential() cloud.Credential {
123
out := cloud.NewCredential(cloud.AuthType(c.AuthType), c.Attributes)
128
// validateCloudCredentials checks that the supplied cloud credentials are
129
// valid for use with the controller's cloud, and returns a set of txn.Ops
130
// to assert the same in a transaction.
132
// TODO(rogpeppe) We're going to a lot of effort here to assert that a
133
// cloud's auth types haven't changed since we looked at them a moment
134
// ago, but we don't support changing a cloud's definition currently and
135
// it's not clear that doing so would be a good idea, as changing a
136
// cloud's auth type would invalidate all existing credentials and would
137
// usually involve a new provider version and juju binary too, so
138
// perhaps all this code is unnecessary.
139
func validateCloudCredentials(cloud cloud.Cloud, cloudName string, credentials map[string]cloud.Credential) ([]txn.Op, error) {
140
requiredAuthTypes := make(set.Strings)
141
for name, credential := range credentials {
143
for _, authType := range cloud.AuthTypes {
144
if credential.AuthType() == authType {
150
return nil, errors.NewNotValid(nil, fmt.Sprintf(
151
"credential %q with auth-type %q is not supported (expected one of %q)",
152
name, credential.AuthType(), cloud.AuthTypes,
155
requiredAuthTypes.Add(string(credential.AuthType()))
157
ops := make([]txn.Op, len(requiredAuthTypes))
158
for i, authType := range requiredAuthTypes.SortedValues() {
162
Assert: bson.D{{"auth-types", authType}},