~rogpeppe/goose/state-of-the-world

« back to all changes in this revision

Viewing changes to client/client.go

  • Committer: Ian Booth
  • Date: 2012-11-21 07:56:19 UTC
  • Revision ID: ian.booth@canonical.com-20121121075619-4fh6i9yq6fj6cwct
Extract identity functionality from client, and also extract common HTTP methods

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
package client
2
2
 
3
3
import (
4
 
        "bytes"
5
4
        "encoding/base64"
6
 
        "encoding/json"
7
5
        "errors"
8
6
        "fmt"
9
 
        "io/ioutil"
 
7
        gooseerrors "launchpad.net/goose/errors"
 
8
        goosehttp "launchpad.net/goose/http"
 
9
        "launchpad.net/goose/identity"
10
10
        "net/http"
11
11
        "net/url"
12
 
        "os"
13
 
        "strings"
14
12
)
15
13
 
16
 
// ErrorContextf prefixes any error stored in err with text formatted
17
 
// according to the format specifier. If err does not contain an error,
18
 
// ErrorContextf does nothing.
19
 
func ErrorContextf(err *error, format string, args ...interface{}) {
20
 
        if *err != nil {
21
 
                *err = errors.New(fmt.Sprintf(format, args...) + ": " + (*err).Error())
22
 
        }
23
 
}
24
 
 
25
 
func getConfig(envVars ...string) (value string) {
26
 
        value = ""
27
 
        for _, v := range envVars {
28
 
                value = os.Getenv(v)
29
 
                if value != "" {
30
 
                        break
31
 
                }
32
 
        }
33
 
        return
34
 
}
35
 
 
36
 
func GetEnvVars() (username, password, tenant, region, authUrl string) {
37
 
        username = getConfig("OS_USERNAME", "NOVA_USERNAME")
38
 
        password = getConfig("OS_PASSWORD", "NOVA_PASSWORD")
39
 
        tenant = getConfig("OS_TENANT_NAME", "NOVA_PROJECT_ID")
40
 
        region = getConfig("OS_REGION_NAME", "NOVA_REGION")
41
 
        authUrl = getConfig("OS_AUTH_URL")
42
 
        return
43
 
}
44
 
 
45
14
const (
46
15
        OS_API_TOKENS               = "/tokens"
47
16
        OS_API_FLAVORS              = "/flavors"
60
29
        COPY   = "COPY"
61
30
)
62
31
 
63
 
type endpoint struct {
64
 
        AdminURL    string
65
 
        Region      string
66
 
        InternalURL string
67
 
        Id          string
68
 
        PublicURL   string
69
 
}
70
 
 
71
 
type service struct {
72
 
        Name      string
73
 
        Type      string
74
 
        Endpoints []endpoint
75
 
}
76
 
 
77
 
type token struct {
78
 
        Expires string
79
 
        Id      string
80
 
        Tenant  struct {
81
 
                Enabled     bool
82
 
                Description string
83
 
                Name        string
84
 
                Id          string
85
 
        }
86
 
}
87
 
 
88
 
type user struct {
89
 
        Username string
90
 
        Roles    []struct {
91
 
                Name string
92
 
        }
93
 
        Id   string
94
 
        Name string
95
 
}
96
 
 
97
 
type metadata struct {
98
 
        IsAdmin bool
99
 
        Roles   []string
100
 
}
101
 
 
102
32
type OpenStackClient struct {
103
 
        // URL to the OpenStack Identity service (Keystone)
104
 
        IdentityEndpoint string
105
 
        // Which region to use
106
 
        Region string
107
 
 
108
 
        client *http.Client
109
 
 
110
 
        Services map[string]service
111
 
        Token    token
112
 
        User     user
113
 
        Metadata metadata
114
 
}
115
 
 
116
 
func (c *OpenStackClient) Authenticate(username, password, tenant string) (err error) {
 
33
 
 
34
        client *goosehttp.GooseHTTPClient
 
35
 
 
36
        creds *identity.Credentials
 
37
        auth identity.Authenticator
 
38
 
 
39
        //TODO - store service urls by region.
 
40
        ServiceURLs map[string]string
 
41
        TokenId     string
 
42
        TenantId    string
 
43
        UserId      string
 
44
}
 
45
 
 
46
func NewOpenStackClient(creds *identity.Credentials, auth_method int) *OpenStackClient {
 
47
        client := OpenStackClient{creds: creds}
 
48
        client.creds.URL = client.creds.URL + OS_API_TOKENS
 
49
        switch auth_method {
 
50
        default: panic(fmt.Errorf("Invalid identity authorisation method: %d", auth_method))
 
51
        case identity.AUTH_LEGACY:
 
52
                client.auth = &identity.Legacy{}
 
53
        case identity.AUTH_USERPASS:
 
54
                client.auth = &identity.UserPass{}
 
55
        }
 
56
        return &client
 
57
}
 
58
 
 
59
func (c *OpenStackClient) Authenticate() (err error) {
117
60
        err = nil
118
 
        if username == "" || password == "" || tenant == "" {
119
 
                return fmt.Errorf("required argument(s) missing")
120
 
        }
121
 
        var req struct {
122
 
                Auth struct {
123
 
                        Credentials struct {
124
 
                                Username string `json:"username"`
125
 
                                Password string `json:"password"`
126
 
                        } `json:"passwordCredentials"`
127
 
                        Tenant string `json:"tenantName"`
128
 
                } `json:"auth"`
129
 
        }
130
 
        req.Auth.Credentials.Username = username
131
 
        req.Auth.Credentials.Password = password
132
 
        req.Auth.Tenant = tenant
133
 
 
134
 
        var resp struct {
135
 
                Access struct {
136
 
                        Token          token
137
 
                        ServiceCatalog []service
138
 
                        User           user
139
 
                        Metadata       metadata
140
 
                }
141
 
        }
142
 
 
143
 
        err = c.request(POST, c.IdentityEndpoint+OS_API_TOKENS, nil, req, &resp, []int{http.StatusOK}, nil, nil, nil)
 
61
        if c.auth == nil {
 
62
                return fmt.Errorf("Authentication method has not been specified")
 
63
        }
 
64
        authDetails, err := c.auth.Auth(c.creds)
144
65
        if err != nil {
145
 
                ErrorContextf(&err, "authentication failed")
 
66
                gooseerrors.AddErrorContext(&err, "authentication failed")
146
67
                return
147
68
        }
148
69
 
149
 
        c.Token = resp.Access.Token
150
 
        c.User = resp.Access.User
151
 
        c.Metadata = resp.Access.Metadata
152
 
        if c.Services == nil {
153
 
                c.Services = make(map[string]service)
154
 
        }
155
 
        for _, s := range resp.Access.ServiceCatalog {
156
 
                // Filter endpoints outside our region
157
 
                for i, e := range s.Endpoints {
158
 
                        if e.Region != c.Region {
159
 
                                s.Endpoints = append(s.Endpoints[:i], s.Endpoints[i+1:]...)
160
 
                        }
161
 
                }
162
 
                c.Services[s.Type] = s
163
 
        }
 
70
        c.TokenId = authDetails.TokenId
 
71
        c.TenantId = authDetails.TenantId
 
72
        c.UserId = authDetails.UserId
 
73
        c.ServiceURLs = authDetails.ServiceURLs
164
74
        return nil
165
75
}
166
76
 
167
77
func (c *OpenStackClient) IsAuthenticated() bool {
168
 
        return c.Token.Id != ""
 
78
        return c.TokenId != ""
169
79
}
170
80
 
171
81
type Link struct {
187
97
        var resp struct {
188
98
                Flavors []Entity
189
99
        }
190
 
        err = c.authRequest(GET, "compute", OS_API_FLAVORS, nil, nil, nil, &resp, []int{http.StatusOK}, nil, nil, nil)
 
100
        requestData := goosehttp.RequestData{RespValue: &resp}
 
101
        err = c.authRequest(GET, "compute", OS_API_FLAVORS, nil, &requestData)
191
102
        if err != nil {
192
 
                ErrorContextf(&err, "failed to get list of flavors")
 
103
                gooseerrors.AddErrorContext(&err, "failed to get list of flavors")
193
104
                return
194
105
        }
195
106
 
210
121
        var resp struct {
211
122
                Flavors []FlavorDetail
212
123
        }
213
 
        err = c.authRequest(GET, "compute", OS_API_FLAVORS_DETAIL, nil, nil, nil, &resp, []int{http.StatusOK}, nil, nil, nil)
 
124
        requestData := goosehttp.RequestData{RespValue: &resp}
 
125
        err = c.authRequest(GET, "compute", OS_API_FLAVORS_DETAIL, nil, &requestData)
214
126
        if err != nil {
215
 
                ErrorContextf(&err, "failed to get list of flavors details")
 
127
                gooseerrors.AddErrorContext(&err, "failed to get list of flavors details")
216
128
                return
217
129
        }
218
130
 
224
136
        var resp struct {
225
137
                Servers []Entity
226
138
        }
227
 
        err = c.authRequest(GET, "compute", OS_API_SERVERS, nil, nil, nil, &resp, []int{http.StatusOK}, nil, nil, nil)
 
139
        requestData := goosehttp.RequestData{RespValue: &resp, ExpectedStatus: []int{http.StatusOK}}
 
140
        err = c.authRequest(GET, "compute", OS_API_SERVERS, nil, &requestData)
228
141
        if err != nil {
229
 
                ErrorContextf(&err, "failed to get list of servers")
 
142
                gooseerrors.AddErrorContext(&err, "failed to get list of servers")
230
143
                return
231
144
        }
232
 
 
233
145
        return resp.Servers, nil
234
146
}
235
147
 
255
167
        var resp struct {
256
168
                Servers []ServerDetail
257
169
        }
258
 
        err = c.authRequest(GET, "compute", OS_API_SERVERS_DETAIL, nil, nil, nil, &resp, []int{http.StatusOK}, nil, nil, nil)
 
170
        requestData := goosehttp.RequestData{RespValue: &resp}
 
171
        err = c.authRequest(GET, "compute", OS_API_SERVERS_DETAIL, nil, &requestData)
259
172
        if err != nil {
260
 
                ErrorContextf(&err, "failed to get list of servers details")
 
173
                gooseerrors.AddErrorContext(&err, "failed to get list of servers details")
261
174
                return
262
175
        }
263
176
 
270
183
                Server ServerDetail
271
184
        }
272
185
        url := fmt.Sprintf("%s/%s", OS_API_SERVERS, serverId)
273
 
        err := c.authRequest(GET, "compute", url, nil, nil, nil, &resp, []int{http.StatusOK}, nil, nil, nil)
 
186
        requestData := goosehttp.RequestData{RespValue: &resp}
 
187
        err := c.authRequest(GET, "compute", url, nil, &requestData)
274
188
        if err != nil {
275
 
                ErrorContextf(&err, "failed to get details for serverId=%s", serverId)
 
189
                gooseerrors.AddErrorContext(&err, "failed to get details for serverId=%s", serverId)
276
190
                return ServerDetail{}, err
277
191
        }
278
192
 
285
199
                Server ServerDetail
286
200
        }
287
201
        url := fmt.Sprintf("%s/%s", OS_API_SERVERS, serverId)
288
 
        err := c.authRequest(DELETE, "compute", url, nil, nil, nil, &resp, []int{http.StatusNoContent}, nil, nil, nil)
 
202
        requestData := goosehttp.RequestData{RespValue: &resp, ExpectedStatus: []int{http.StatusNoContent}}
 
203
        err := c.authRequest(DELETE, "compute", url, nil, &requestData)
289
204
        if err != nil {
290
 
                ErrorContextf(&err, "failed to delete server with serverId=%s", serverId)
 
205
                gooseerrors.AddErrorContext(&err, "failed to delete server with serverId=%s", serverId)
291
206
                return err
292
207
        }
293
208
 
315
230
                encoded := base64.StdEncoding.EncodeToString(data)
316
231
                req.Server.UserData = &encoded
317
232
        }
318
 
        err = c.authRequest(POST, "compute", OS_API_SERVERS, nil, nil, &req, nil, []int{http.StatusAccepted}, nil, nil, nil)
 
233
        requestData := goosehttp.RequestData{ReqValue: req, ExpectedStatus: []int{http.StatusAccepted}}
 
234
        err = c.authRequest(POST, "compute", OS_API_SERVERS, nil, &requestData)
319
235
        if err != nil {
320
 
                ErrorContextf(&err, "failed to run a server with %#v", opts)
 
236
                gooseerrors.AddErrorContext(&err, "failed to run a server with %#v", opts)
321
237
        }
322
238
 
323
239
        return
346
262
        var resp struct {
347
263
                Groups []SecurityGroup `json:"security_groups"`
348
264
        }
349
 
        err = c.authRequest(GET, "compute", OS_API_SECURITY_GROUPS, nil, nil, nil, &resp, []int{http.StatusOK}, nil, nil, nil)
 
265
        requestData := goosehttp.RequestData{RespValue: &resp}
 
266
        err = c.authRequest(GET, "compute", OS_API_SECURITY_GROUPS, nil, &requestData)
350
267
        if err != nil {
351
 
                ErrorContextf(&err, "failed to list security groups")
 
268
                gooseerrors.AddErrorContext(&err, "failed to list security groups")
352
269
                return nil, err
353
270
        }
354
271
 
361
278
                Groups []SecurityGroup `json:"security_groups"`
362
279
        }
363
280
        url := fmt.Sprintf("%s/%s/%s", OS_API_SERVERS, serverId, OS_API_SECURITY_GROUPS)
364
 
        err = c.authRequest(GET, "compute", url, nil, nil, nil, &resp, []int{http.StatusOK}, nil, nil, nil)
 
281
        requestData := goosehttp.RequestData{RespValue: &resp}
 
282
        err = c.authRequest(GET, "compute", url, nil, &requestData)
365
283
        if err != nil {
366
 
                ErrorContextf(&err, "failed to list server (%s) security groups", serverId)
 
284
                gooseerrors.AddErrorContext(&err, "failed to list server (%s) security groups", serverId)
367
285
                return nil, err
368
286
        }
369
287
 
384
302
        var resp struct {
385
303
                SecurityGroup SecurityGroup `json:"security_group"`
386
304
        }
387
 
        err = c.authRequest(POST, "compute", OS_API_SECURITY_GROUPS, nil, nil, &req, &resp, []int{http.StatusOK}, nil, nil, nil)
 
305
        requestData := goosehttp.RequestData{ReqValue: req, RespValue: &resp, ExpectedStatus: []int{http.StatusOK}}
 
306
        err = c.authRequest(POST, "compute", OS_API_SECURITY_GROUPS, nil, &requestData)
388
307
        if err != nil {
389
 
                ErrorContextf(&err, "failed to create a security group with name=%s", name)
 
308
                gooseerrors.AddErrorContext(&err, "failed to create a security group with name=%s", name)
390
309
        }
391
310
        group = resp.SecurityGroup
392
311
 
396
315
func (c *OpenStackClient) DeleteSecurityGroup(groupId int) (err error) {
397
316
 
398
317
        url := fmt.Sprintf("%s/%d", OS_API_SECURITY_GROUPS, groupId)
399
 
        err = c.authRequest(DELETE, "compute", url, nil, nil, nil, nil, []int{http.StatusAccepted}, nil, nil, nil)
 
318
        requestData := goosehttp.RequestData{ExpectedStatus: []int{http.StatusAccepted}}
 
319
        err = c.authRequest(DELETE, "compute", url, nil, &requestData)
400
320
        if err != nil {
401
 
                ErrorContextf(&err, "failed to delete a security group with id=%d", groupId)
 
321
                gooseerrors.AddErrorContext(&err, "failed to delete a security group with id=%d", groupId)
402
322
        }
403
323
 
404
324
        return
424
344
                SecurityGroupRule SecurityGroupRule `json:"security_group_rule"`
425
345
        }
426
346
 
427
 
        err = c.authRequest(POST, "compute", OS_API_SECURITY_GROUP_RULES, nil, nil, &req, &resp, []int{http.StatusOK}, nil, nil, nil)
 
347
        requestData := goosehttp.RequestData{ReqValue: req, RespValue: &resp}
 
348
        err = c.authRequest(POST, "compute", OS_API_SECURITY_GROUP_RULES, nil, &requestData)
428
349
        if err != nil {
429
 
                ErrorContextf(&err, "failed to create a rule for the security group with id=%s", ruleInfo.GroupId)
 
350
                gooseerrors.AddErrorContext(&err, "failed to create a rule for the security group with id=%s", ruleInfo.GroupId)
430
351
        }
431
352
 
432
353
        return resp.SecurityGroupRule, err
435
356
func (c *OpenStackClient) DeleteSecurityGroupRule(ruleId int) (err error) {
436
357
 
437
358
        url := fmt.Sprintf("%s/%d", OS_API_SECURITY_GROUP_RULES, ruleId)
438
 
        err = c.authRequest(DELETE, "compute", url, nil, nil, nil, nil, []int{http.StatusAccepted}, nil, nil, nil)
 
359
        requestData := goosehttp.RequestData{ExpectedStatus: []int{http.StatusAccepted}}
 
360
        err = c.authRequest(DELETE, "compute", url, nil, &requestData)
439
361
        if err != nil {
440
 
                ErrorContextf(&err, "failed to delete a security group rule with id=%d", ruleId)
 
362
                gooseerrors.AddErrorContext(&err, "failed to delete a security group rule with id=%d", ruleId)
441
363
        }
442
364
 
443
365
        return
453
375
        req.AddSecurityGroup.Name = groupName
454
376
 
455
377
        url := fmt.Sprintf("%s/%s/action", OS_API_SERVERS, serverId)
456
 
        err = c.authRequest(POST, "compute", url, nil, nil, &req, nil, []int{http.StatusAccepted}, nil, nil, nil)
 
378
        requestData := goosehttp.RequestData{ReqValue: req, ExpectedStatus: []int{http.StatusAccepted}}
 
379
        err = c.authRequest(POST, "compute", url, nil, &requestData)
457
380
        if err != nil {
458
 
                ErrorContextf(&err, "failed to add security group '%s' from server with id=%s", groupName, serverId)
 
381
                gooseerrors.AddErrorContext(&err, "failed to add security group '%s' from server with id=%s", groupName, serverId)
459
382
        }
460
383
        return
461
384
}
470
393
        req.RemoveSecurityGroup.Name = groupName
471
394
 
472
395
        url := fmt.Sprintf("%s/%s/action", OS_API_SERVERS, serverId)
473
 
        err = c.authRequest(POST, "compute", url, nil, nil, &req, nil, []int{http.StatusAccepted}, nil, nil, nil)
 
396
        requestData := goosehttp.RequestData{ReqValue: req, ExpectedStatus: []int{http.StatusAccepted}}
 
397
        err = c.authRequest(POST, "compute", url, nil, &requestData)
474
398
        if err != nil {
475
 
                ErrorContextf(&err, "failed to remove security group '%s' from server with id=%s", groupName, serverId)
 
399
                gooseerrors.AddErrorContext(&err, "failed to remove security group '%s' from server with id=%s", groupName, serverId)
476
400
        }
477
401
        return
478
402
}
491
415
                FloatingIPs []FloatingIP `json:"floating_ips"`
492
416
        }
493
417
 
494
 
        err = c.authRequest(GET, "compute", OS_API_FLOATING_IPS, nil, nil, nil, &resp, []int{http.StatusOK}, nil, nil, nil)
 
418
        requestData := goosehttp.RequestData{RespValue: &resp}
 
419
        err = c.authRequest(GET, "compute", OS_API_FLOATING_IPS, nil, &requestData)
495
420
        if err != nil {
496
 
                ErrorContextf(&err, "failed to list floating ips")
 
421
                gooseerrors.AddErrorContext(&err, "failed to list floating ips")
497
422
        }
498
423
 
499
424
        return resp.FloatingIPs, err
506
431
        }
507
432
 
508
433
        url := fmt.Sprintf("%s/%d", OS_API_FLOATING_IPS, ipId)
509
 
        err = c.authRequest(GET, "compute", url, nil, nil, nil, &resp, []int{http.StatusOK}, nil, nil, nil)
 
434
        requestData := goosehttp.RequestData{RespValue: &resp}
 
435
        err = c.authRequest(GET, "compute", url, nil, &requestData)
510
436
        if err != nil {
511
 
                ErrorContextf(&err, "failed to get floating ip %d details", ipId)
 
437
                gooseerrors.AddErrorContext(&err, "failed to get floating ip %d details", ipId)
512
438
        }
513
439
 
514
440
        return resp.FloatingIP, err
520
446
                FloatingIP FloatingIP `json:"floating_ip"`
521
447
        }
522
448
 
523
 
        err = c.authRequest(POST, "compute", OS_API_FLOATING_IPS, nil, nil, nil, &resp, []int{http.StatusOK}, nil, nil, nil)
 
449
        requestData := goosehttp.RequestData{RespValue: &resp}
 
450
        err = c.authRequest(POST, "compute", OS_API_FLOATING_IPS, nil, &requestData)
524
451
        if err != nil {
525
 
                ErrorContextf(&err, "failed to allocate a floating ip")
 
452
                gooseerrors.AddErrorContext(&err, "failed to allocate a floating ip")
526
453
        }
527
454
 
528
455
        return resp.FloatingIP, err
531
458
func (c *OpenStackClient) DeleteFloatingIP(ipId int) (err error) {
532
459
 
533
460
        url := fmt.Sprintf("%s/%d", OS_API_FLOATING_IPS, ipId)
534
 
        err = c.authRequest(DELETE, "compute", url, nil, nil, nil, nil, []int{http.StatusAccepted}, nil, nil, nil)
 
461
        requestData := goosehttp.RequestData{ExpectedStatus: []int{http.StatusAccepted}}
 
462
        err = c.authRequest(DELETE, "compute", url, nil, &requestData)
535
463
        if err != nil {
536
 
                ErrorContextf(&err, "failed to delete floating ip %d details", ipId)
 
464
                gooseerrors.AddErrorContext(&err, "failed to delete floating ip %d details", ipId)
537
465
        }
538
466
 
539
467
        return
549
477
        req.AddFloatingIP.Address = address
550
478
 
551
479
        url := fmt.Sprintf("%s/%s/action", OS_API_SERVERS, serverId)
552
 
        err = c.authRequest(POST, "compute", url, nil, nil, &req, nil, []int{http.StatusAccepted}, nil, nil, nil)
 
480
        requestData := goosehttp.RequestData{ReqValue: req, ExpectedStatus: []int{http.StatusAccepted}}
 
481
        err = c.authRequest(POST, "compute", url, nil, &requestData)
553
482
        if err != nil {
554
 
                ErrorContextf(&err, "failed to add floating ip %s to server %s", address, serverId)
 
483
                gooseerrors.AddErrorContext(&err, "failed to add floating ip %s to server %s", address, serverId)
555
484
        }
556
485
 
557
486
        return
567
496
        req.RemoveFloatingIP.Address = address
568
497
 
569
498
        url := fmt.Sprintf("%s/%s/action", OS_API_SERVERS, serverId)
570
 
        err = c.authRequest(POST, "compute", url, nil, nil, &req, nil, []int{http.StatusAccepted}, nil, nil, nil)
 
499
        requestData := goosehttp.RequestData{ReqValue: req, ExpectedStatus: []int{http.StatusAccepted}}
 
500
        err = c.authRequest(POST, "compute", url, nil, &requestData)
571
501
        if err != nil {
572
 
                ErrorContextf(&err, "failed to remove floating ip %s to server %s", address, serverId)
 
502
                gooseerrors.AddErrorContext(&err, "failed to remove floating ip %s to server %s", address, serverId)
573
503
        }
574
504
 
575
505
        return
583
513
        headers := make(http.Header)
584
514
        headers.Add("X-Container-Read", ".r:*")
585
515
        url := fmt.Sprintf("/%s", containerName)
586
 
        err = c.authRequest(PUT, "object-store", url, nil, nil, nil, nil, []int{http.StatusAccepted, http.StatusCreated}, headers, nil, nil)
 
516
        requestData := goosehttp.RequestData{ReqHeaders: headers, ExpectedStatus: []int{http.StatusAccepted, http.StatusCreated}}
 
517
        err = c.authRequest(PUT, "object-store", url, nil, &requestData)
587
518
        if err != nil {
588
 
                ErrorContextf(&err, "failed to create container %s.", containerName)
 
519
                gooseerrors.AddErrorContext(&err, "failed to create container %s.", containerName)
589
520
        }
590
521
 
591
522
        return
594
525
func (c *OpenStackClient) DeleteContainer(containerName string) (err error) {
595
526
 
596
527
        url := fmt.Sprintf("/%s", containerName)
597
 
        err = c.authRequest(DELETE, "object-store", url, nil, nil, nil, nil, []int{http.StatusNoContent}, nil, nil, nil)
 
528
        requestData := goosehttp.RequestData{ExpectedStatus: []int{http.StatusNoContent}}
 
529
        err = c.authRequest(DELETE, "object-store", url, nil, &requestData)
598
530
        if err != nil {
599
 
                ErrorContextf(&err, "failed to delete container %s.", containerName)
 
531
                gooseerrors.AddErrorContext(&err, "failed to delete container %s.", containerName)
600
532
        }
601
533
 
602
534
        return
613
545
        if err != nil {
614
546
                return nil, err
615
547
        }
616
 
        err = c.authRequest(HEAD, "object-store", url, nil, nil, nil, nil, []int{http.StatusOK}, nil, &headers, nil)
 
548
        requestData := goosehttp.RequestData{ReqHeaders: headers, ExpectedStatus: []int{http.StatusOK}}
 
549
        err = c.authRequest(HEAD, "object-store", url, nil, &requestData)
617
550
        if err != nil {
618
 
                ErrorContextf(&err, "failed to HEAD object %s from container %s", objectName, containerName)
 
551
                gooseerrors.AddErrorContext(&err, "failed to HEAD object %s from container %s", objectName, containerName)
619
552
                return nil, err
620
553
        }
621
554
        return headers, nil
627
560
        if err != nil {
628
561
                return nil, err
629
562
        }
630
 
        err = c.authRequest(GET, "object-store", url, nil, nil, nil, nil, []int{http.StatusOK}, nil, nil, &obj)
 
563
        requestData := goosehttp.RequestData{RespData: &obj, ExpectedStatus: []int{http.StatusOK}}
 
564
        err = c.authBinaryRequest(GET, "object-store", url, nil, &requestData)
631
565
        if err != nil {
632
 
                ErrorContextf(&err, "failed to GET object %s content from container %s", objectName, containerName)
 
566
                gooseerrors.AddErrorContext(&err, "failed to GET object %s content from container %s", objectName, containerName)
633
567
                return nil, err
634
568
        }
635
569
        return obj, nil
641
575
        if err != nil {
642
576
                return err
643
577
        }
644
 
        err = c.authRequest(DELETE, "object-store", url, nil, nil, nil, nil, []int{http.StatusAccepted}, nil, nil, nil)
 
578
        requestData := goosehttp.RequestData{ExpectedStatus: []int{http.StatusAccepted}}
 
579
        err = c.authRequest(DELETE, "object-store", url, nil, &requestData)
645
580
        if err != nil {
646
 
                ErrorContextf(&err, "failed to DELETE object %s content from container %s", objectName, containerName)
 
581
                gooseerrors.AddErrorContext(&err, "failed to DELETE object %s content from container %s", objectName, containerName)
647
582
        }
648
583
        return err
649
584
}
654
589
        if err != nil {
655
590
                return err
656
591
        }
657
 
        err = c.authRequest(PUT, "object-store", url, nil, data, nil, nil, []int{http.StatusAccepted}, nil, nil, nil)
 
592
        requestData := goosehttp.RequestData{ReqData: data, ExpectedStatus: []int{http.StatusAccepted}}
 
593
        err = c.authBinaryRequest(PUT, "object-store", url, nil, &requestData)
658
594
        if err != nil {
659
 
                ErrorContextf(&err, "failed to PUT object %s content from container %s", objectName, containerName)
 
595
                gooseerrors.AddErrorContext(&err, "failed to PUT object %s content from container %s", objectName, containerName)
660
596
        }
661
597
        return err
662
598
}
664
600
////////////////////////////////////////////////////////////////////////
665
601
// Private helpers
666
602
 
667
 
// request sends an HTTP request with the given method to the given URL,
668
 
// containing an optional body (serialized to JSON), and returning either
669
 
// an error or the (deserialized) response body
670
 
func (c *OpenStackClient) request(method, url string, rawReqBody []byte, body, resp interface{}, expectedStatus []int, reqHeaders http.Header, respHeaders *http.Header, rawRespBody *[]byte) (err error) {
671
 
        err = nil
672
 
        if c.client == nil {
673
 
                c.client = &http.Client{CheckRedirect: nil}
674
 
        }
675
 
 
676
 
        var (
677
 
                req      *http.Request
678
 
                jsonBody []byte
679
 
        )
680
 
        if rawReqBody != nil {
681
 
                rawReqReader := bytes.NewReader(rawReqBody)
682
 
                req, err = http.NewRequest(method, url, rawReqReader)
683
 
        } else if body != nil {
684
 
                jsonBody, err = json.Marshal(body)
685
 
                if err != nil {
686
 
                        ErrorContextf(&err, "failed marshalling the request body")
687
 
                        return
688
 
                }
689
 
 
690
 
                reqBody := strings.NewReader(string(jsonBody))
691
 
                req, err = http.NewRequest(method, url, reqBody)
692
 
        } else {
693
 
                req, err = http.NewRequest(method, url, nil)
694
 
        }
695
 
        if err != nil {
696
 
                ErrorContextf(&err, "failed creating the request")
697
 
                return
698
 
        }
699
 
        req.Header.Add("Content-Type", "application/json")
700
 
        req.Header.Add("Accept", "application/json")
701
 
        if reqHeaders != nil {
702
 
                for header, values := range reqHeaders {
703
 
                        for _, value := range values {
704
 
                                req.Header.Add(header, value)
705
 
                        }
706
 
                }
707
 
        }
708
 
        if c.IsAuthenticated() {
709
 
                req.Header.Add("X-Auth-Token", c.Token.Id)
710
 
        }
711
 
 
712
 
        rawResp, err := c.client.Do(req)
713
 
        if err != nil {
714
 
                ErrorContextf(&err, "failed executing the request")
715
 
                return
716
 
        }
717
 
        foundStatus := false
718
 
        for _, status := range expectedStatus {
719
 
                if rawResp.StatusCode == status {
720
 
                        foundStatus = true
721
 
                        break
722
 
                }
723
 
        }
724
 
        if !foundStatus && len(expectedStatus) > 0 {
725
 
                defer rawResp.Body.Close()
726
 
                respBody, _ := ioutil.ReadAll(rawResp.Body)
727
 
                err = errors.New(
728
 
                        fmt.Sprintf(
729
 
                                "request (%s) returned unexpected status: %s; response body: %s; request body: %s",
730
 
                                url,
731
 
                                rawResp.Status,
732
 
                                respBody,
733
 
                                string(jsonBody)))
734
 
                return
735
 
        }
736
 
 
737
 
        respBody, err := ioutil.ReadAll(rawResp.Body)
738
 
        defer rawResp.Body.Close()
739
 
        if err != nil {
740
 
                ErrorContextf(&err, "failed reading the response body")
741
 
                return
742
 
        }
743
 
 
744
 
        if len(respBody) > 0 {
745
 
                if rawRespBody != nil {
746
 
                        *rawRespBody = respBody
747
 
                } else if resp != nil {
748
 
                        // resp and rawRespBody are mutually exclusive
749
 
                        err = json.Unmarshal(respBody, &resp)
750
 
                        if err != nil {
751
 
                                ErrorContextf(&err, "failed unmarshaling the response body: %s", respBody)
752
 
                        }
753
 
                }
754
 
                if respHeaders != nil {
755
 
                        *respHeaders = rawResp.Header
756
 
                }
757
 
        } else {
758
 
                resp = nil
759
 
        }
760
 
 
761
 
        return
762
 
}
763
 
 
764
603
// makeUrl prepares a full URL to a service endpoint, with optional
765
604
// URL parts, appended to it and optional query string params. It
766
605
// uses the first endpoint it can find for the given service type
767
606
func (c *OpenStackClient) makeUrl(serviceType string, parts []string, params url.Values) (string, error) {
768
 
        s, ok := c.Services[serviceType]
769
 
        if !ok || len(s.Endpoints) == 0 {
 
607
        url, ok := c.ServiceURLs[serviceType]
 
608
        if !ok {
770
609
                return "", errors.New("no endpoints known for service type: " + serviceType)
771
610
        }
772
 
        url := s.Endpoints[0].PublicURL
773
611
        for _, part := range parts {
774
612
                url += part
775
613
        }
779
617
        return url, nil
780
618
}
781
619
 
782
 
func (c *OpenStackClient) authRequest(method, svcType, apiCall string, params url.Values, rawBody []byte, body, resp interface{}, expectedStatus []int, reqHeaders http.Header, respHeaders *http.Header, respRawBody *[]byte) (err error) {
783
 
 
 
620
func (c *OpenStackClient) setupRequest(svcType, apiCall string, params url.Values, requestData *goosehttp.RequestData) (url string, err error) {
784
621
        if !c.IsAuthenticated() {
785
 
                return errors.New("not authenticated")
786
 
        }
787
 
 
788
 
        url, err := c.makeUrl(svcType, []string{apiCall}, params)
789
 
        if err != nil {
790
 
                ErrorContextf(&err, "cannot find a '%s' node endpoint", svcType)
791
 
                return
792
 
        }
793
 
 
794
 
        if body != nil && resp != nil {
795
 
                err = c.request(method, url, rawBody, &body, &resp, expectedStatus, reqHeaders, respHeaders, respRawBody)
796
 
        } else if resp != nil {
797
 
                err = c.request(method, url, rawBody, nil, &resp, expectedStatus, reqHeaders, respHeaders, respRawBody)
798
 
        } else if body != nil {
799
 
                err = c.request(method, url, rawBody, &body, nil, expectedStatus, reqHeaders, respHeaders, respRawBody)
800
 
        }
801
 
        if err != nil {
802
 
                ErrorContextf(&err, "request failed")
803
 
        }
 
622
                return "", errors.New("not authenticated")
 
623
        }
 
624
 
 
625
        url, err = c.makeUrl(svcType, []string{apiCall}, params)
 
626
        if err != nil {
 
627
                gooseerrors.AddErrorContext(&err, "cannot find a '%s' node endpoint", svcType)
 
628
                return
 
629
        }
 
630
 
 
631
        if c.client == nil {
 
632
                c.client = &goosehttp.GooseHTTPClient{http.Client{CheckRedirect: nil}}
 
633
        }
 
634
 
 
635
        if requestData.ReqHeaders == nil {
 
636
                requestData.ReqHeaders = make(http.Header)
 
637
        }
 
638
        requestData.ReqHeaders.Add("X-Auth-Token", c.TokenId)
 
639
        return
 
640
}
 
641
 
 
642
func (c *OpenStackClient) authRequest(method, svcType, apiCall string, params url.Values, requestData *goosehttp.RequestData) (err error) {
 
643
        url, err := c.setupRequest(svcType, apiCall, params, requestData)
 
644
        if err != nil {
 
645
                return
 
646
        }
 
647
        err = c.client.JsonRequest(method, url, requestData)
 
648
        return
 
649
}
 
650
 
 
651
func (c *OpenStackClient) authBinaryRequest(method, svcType, apiCall string, params url.Values, requestData *goosehttp.RequestData) (err error) {
 
652
        url, err := c.setupRequest(svcType, apiCall, params, requestData)
 
653
        if err != nil {
 
654
                return
 
655
        }
 
656
        err = c.client.BinaryRequest(method, url, requestData)
804
657
        return
805
658
}