~gophers/goose/unstable-001

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package client

import (
	"errors"
	"fmt"
	gooseerrors "launchpad.net/~gophers/goose/unstable-001/errors"
	goosehttp "launchpad.net/~gophers/goose/unstable-001/http"
	"launchpad.net/~gophers/goose/unstable-001/identity"
	"net/http"
)

const (
	apiTokens = "/tokens"

	// The HTTP request methods.
	GET    = "GET"
	POST   = "POST"
	PUT    = "PUT"
	DELETE = "DELETE"
	HEAD   = "HEAD"
	COPY   = "COPY"
)

type Client interface {
	MakeServiceURL(serviceType string, parts []string) (string, error)
	SendRequest(method, svcType, apiCall string, requestData *goosehttp.RequestData,
		context string, contextArgs ...interface{}) (err error)
}

type OpenStackClient struct {
	client *goosehttp.Client

	creds *identity.Credentials
	auth  identity.Authenticator

	//TODO - store service urls by region.
	ServiceURLs map[string]string
	Token       string
	TenantId    string
	UserId      string
}

func NewClient(creds *identity.Credentials, auth_method identity.AuthMethod) *OpenStackClient {
	client_creds := *creds
	client_creds.URL = client_creds.URL + apiTokens
	client := OpenStackClient{creds: &client_creds}
	switch auth_method {
	default:
		panic(fmt.Errorf("Invalid identity authorisation method: %d", auth_method))
	case identity.AuthLegacy:
		client.auth = &identity.Legacy{}
	case identity.AuthUserPass:
		client.auth = &identity.UserPass{}
	}
	return &client
}

func (c *OpenStackClient) Authenticate() (err error) {
	err = nil
	if c.auth == nil {
		return fmt.Errorf("Authentication method has not been specified")
	}
	authDetails, err := c.auth.Auth(c.creds)
	if err != nil {
		err = gooseerrors.AddContext(err, "authentication failed")
		return
	}

	c.Token = authDetails.Token
	c.TenantId = authDetails.TenantId
	c.UserId = authDetails.UserId
	c.ServiceURLs = authDetails.ServiceURLs
	return nil
}

func (c *OpenStackClient) IsAuthenticated() bool {
	return c.Token != ""
}

// MakeServiceURL prepares a full URL to a service endpoint, with optional
// URL parts. It uses the first endpoint it can find for the given service type.
func (c *OpenStackClient) MakeServiceURL(serviceType string, parts []string) (string, error) {
	url, ok := c.ServiceURLs[serviceType]
	if !ok {
		return "", errors.New("no endpoints known for service type: " + serviceType)
	}
	for _, part := range parts {
		url += part
	}
	return url, nil
}

func (c *OpenStackClient) SendRequest(method, svcType, apiCall string, requestData *goosehttp.RequestData,
	context string, contextArgs ...interface{}) (err error) {
	if c.creds != nil && !c.IsAuthenticated() {
		err = c.Authenticate()
		if err != nil {
			err = gooseerrors.AddContext(err, context, contextArgs...)
			return
		}
	}

	url, err := c.MakeServiceURL(svcType, []string{apiCall})
	if err != nil {
		err = gooseerrors.AddContext(err, "cannot find a '%s' node endpoint", svcType)
		return
	}

	if c.client == nil {
		c.client = &goosehttp.Client{http.Client{CheckRedirect: nil}, c.Token}
	}
	if requestData.ReqValue != nil || requestData.RespValue != nil {
		err = c.client.JsonRequest(method, url, requestData)
	} else {
		err = c.client.BinaryRequest(method, url, requestData)
	}
	if err != nil {
		err = gooseerrors.AddContext(err, context, contextArgs...)
	}
	return
}