~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/state/cloudcredentials.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2016 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package state
 
5
 
 
6
import (
 
7
        "fmt"
 
8
 
 
9
        "github.com/juju/errors"
 
10
        "github.com/juju/utils/set"
 
11
        "gopkg.in/juju/names.v2"
 
12
        "gopkg.in/mgo.v2/bson"
 
13
        "gopkg.in/mgo.v2/txn"
 
14
 
 
15
        "github.com/juju/juju/cloud"
 
16
)
 
17
 
 
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"`
 
26
}
 
27
 
 
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)
 
32
        defer cleanup()
 
33
 
 
34
        var doc cloudCredentialDoc
 
35
        credentials := make(map[string]cloud.Credential)
 
36
        iter := coll.Find(bson.D{
 
37
                {"owner", user.Canonical()},
 
38
                {"cloud", cloudName},
 
39
        }).Iter()
 
40
        for iter.Next(&doc) {
 
41
                credentials[doc.Name] = doc.toCredential()
 
42
        }
 
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,
 
47
                )
 
48
        }
 
49
        return credentials, nil
 
50
}
 
51
 
 
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)
 
58
                if err != nil {
 
59
                        return nil, errors.Trace(err)
 
60
                }
 
61
                ops, err := validateCloudCredentials(cloud, cloudName, credentials)
 
62
                if err != nil {
 
63
                        return nil, errors.Annotate(err, "validating cloud credentials")
 
64
                }
 
65
                existingCreds, err := st.CloudCredentials(user, cloudName)
 
66
                if err != nil {
 
67
                        return nil, errors.Maskf(err, "fetching cloud credentials")
 
68
                }
 
69
                for credName, cred := range credentials {
 
70
                        if _, ok := existingCreds[credName]; ok {
 
71
                                ops = append(ops, updateCloudCredentialOp(user, cloudName, credName, cred))
 
72
                        } else {
 
73
                                ops = append(ops, createCloudCredentialOp(user, cloudName, credName, cred))
 
74
                        }
 
75
                }
 
76
                return ops, nil
 
77
        }
 
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,
 
82
                )
 
83
        }
 
84
        return nil
 
85
}
 
86
 
 
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 {
 
90
        return txn.Op{
 
91
                C:      cloudCredentialsC,
 
92
                Id:     cloudCredentialDocID(user, cloudName, credName),
 
93
                Assert: txn.DocMissing,
 
94
                Insert: &cloudCredentialDoc{
 
95
                        Owner:      user.Canonical(),
 
96
                        Cloud:      cloudName,
 
97
                        Name:       credName,
 
98
                        AuthType:   string(cred.AuthType()),
 
99
                        Attributes: cred.Attributes(),
 
100
                },
 
101
        }
 
102
}
 
103
 
 
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 {
 
107
        return 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()},
 
114
                }}},
 
115
        }
 
116
}
 
117
 
 
118
func cloudCredentialDocID(user names.UserTag, cloudName, credentialName string) string {
 
119
        return fmt.Sprintf("%s#%s#%s", user.Canonical(), cloudName, credentialName)
 
120
}
 
121
 
 
122
func (c cloudCredentialDoc) toCredential() cloud.Credential {
 
123
        out := cloud.NewCredential(cloud.AuthType(c.AuthType), c.Attributes)
 
124
        out.Label = c.Name
 
125
        return out
 
126
}
 
127
 
 
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.
 
131
//
 
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 {
 
142
                var found bool
 
143
                for _, authType := range cloud.AuthTypes {
 
144
                        if credential.AuthType() == authType {
 
145
                                found = true
 
146
                                break
 
147
                        }
 
148
                }
 
149
                if !found {
 
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,
 
153
                        ))
 
154
                }
 
155
                requiredAuthTypes.Add(string(credential.AuthType()))
 
156
        }
 
157
        ops := make([]txn.Op, len(requiredAuthTypes))
 
158
        for i, authType := range requiredAuthTypes.SortedValues() {
 
159
                ops[i] = txn.Op{
 
160
                        C:      cloudsC,
 
161
                        Id:     cloudName,
 
162
                        Assert: bson.D{{"auth-types", authType}},
 
163
                }
 
164
        }
 
165
        return ops, nil
 
166
}