~bteleaga/gwacl/windowsgwacl

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// Copyright 2013 Canonical Ltd.  This software is licensed under the
// GNU Lesser General Public License version 3 (see the file COPYING).

package gwacl

import (
    "errors"
    "fmt"
    "launchpad.net/gwacl/fork/http"
    "launchpad.net/gwacl/fork/tls"
    "net/url"
    "strings"
)

type x509Session struct {
    subscriptionId string
    client         *http.Client
    baseURL        *url.URL
    retryPolicy    RetryPolicy
}

// httpRedirectErr is a unique error used to prevent
// net/http from automatically following redirects.
// See commentary on CheckRedirect in newX509Session.
var httpRedirectErr = errors.New("redirect")

// newX509Session creates and returns a new x509Session based on credentials
// and X509 certificate files.
// For testing purposes, certFile can be passed as the empty string and it
// will be ignored.
func newX509Session(subscriptionId, certFile, location string, retryPolicy RetryPolicy) (*x509Session, error) {
    certs := []tls.Certificate{}
    if certFile != "" {
        cert, err := tls.LoadX509KeyPair(certFile, certFile)
        if err != nil {
            return nil, err
        }
        certs = append(certs, cert)
    }
    return newX509SessionCerts(subscriptionId, certs, location, retryPolicy)
}

// newX509SessionCertData creates and returns a new x509Session based on credentials
// and X509 certificate byte arrays.
func newX509SessionCertData(subscriptionId string, cert, key []byte, location string, retryPolicy RetryPolicy) (*x509Session, error) {
    certs := []tls.Certificate{}
    if cert != nil && key != nil {
        cert, err := tls.X509KeyPair(cert, key)
        if err != nil {
            return nil, err
        }
        certs = append(certs, cert)
    }
    return newX509SessionCerts(subscriptionId, certs, location, retryPolicy)
}

// newX509SessionCerts creates and returns a new x509Session based on credentials
// and X509 certificate files.
func newX509SessionCerts(subscriptionId string, certs []tls.Certificate, location string, retryPolicy RetryPolicy) (*x509Session, error) {
    client := http.Client{
        Transport: &http.Transport{
            TLSClientConfig: &tls.Config{
                Certificates: certs,
            },
            // See https://code.google.com/p/go/issues/detail?id=4677
            // We need to force the connection to close each time so that we don't
            // hit the above Go bug.
            DisableKeepAlives: true,
        },
        // See https://code.google.com/p/go/issues/detail?id=4800
        // We need to follow temporary redirects (307s), while
        // retaining headers. We also need to follow redirects
        // for POST and DELETE automatically.
        CheckRedirect: func(req *http.Request, via []*http.Request) error {
            return httpRedirectErr
        },
    }

    endpointURL := GetEndpoint(location).ManagementAPI()
    baseURL, err := url.Parse(endpointURL)
    if err != nil {
        panic(fmt.Errorf("cannot parse Azure endpoint URL '%s' - %v", endpointURL, err))
    }

    session := x509Session{
        subscriptionId: subscriptionId,
        client:         &client,
        baseURL:        baseURL,
        retryPolicy:    retryPolicy,
    }
    return &session, nil
}

// composeURL puts together a URL for an item on the Azure API based on
// the starting point used by the session, and a given relative path from
// there.
func (session *x509Session) composeURL(path string) string {
    if strings.HasPrefix(path, "/") {
        panic(fmt.Errorf("got absolute API path '%s' instead of relative one", path))
    }
    escapedID := url.QueryEscape(session.subscriptionId)
    pathURL, err := url.Parse(escapedID + "/" + path)
    if err != nil {
        panic(err)
    }
    return session.baseURL.ResolveReference(pathURL).String()
}

// _X509Dispatcher is the function used to dispatch requests.  We call the
// function through this pointer, not directly, so that tests can inject
// fakes.
var _X509Dispatcher = performX509Request

// getServerError returns a ServerError matching the given server response
// status, or nil if the server response indicates success.
func (session *x509Session) getServerError(status int, body []byte, description string) error {
    if status < http.StatusOK || status >= http.StatusMultipleChoices {
        return newHTTPError(status, body, description)
    }
    return nil
}

// get performs a GET request to the Azure management API.
// It returns the response body and/or an error.  If the error is a
// ServerError, the returned body will be the one received from the server.
// For any other kind of error, the returned body will be nil.
func (session *x509Session) get(path, apiVersion string) (*x509Response, error) {
    request := newX509RequestGET(session.composeURL(path), apiVersion)
    response, err := _X509Dispatcher(session, request)
    if err != nil {
        return nil, err
    }
    err = session.getServerError(response.StatusCode, response.Body, "GET request failed")
    return response, err
}

// post performs a POST request to the Azure management API.
// It returns the response body and/or an error.  If the error is a
// ServerError, the returned body will be the one received from the server.
// For any other kind of error, the returned body will be nil.
// Be aware that Azure may perform POST operations asynchronously.  If you are
// not sure, call blockUntilCompleted() on the response.
func (session *x509Session) post(path, apiVersion string, body []byte, contentType string) (*x509Response, error) {
    request := newX509RequestPOST(session.composeURL(path), apiVersion, body, contentType)
    response, err := _X509Dispatcher(session, request)
    if err != nil {
        return nil, err
    }
    err = session.getServerError(response.StatusCode, response.Body, "POST request failed")
    return response, err
}

// delete performs a DELETE request to the Azure management API.
// Be aware that Azure may perform DELETE operations asynchronously.  If you
// are not sure, call blockUntilCompleted() on the response.
func (session *x509Session) delete(path, apiVersion string) (*x509Response, error) {
    request := newX509RequestDELETE(session.composeURL(path), apiVersion)
    response, err := _X509Dispatcher(session, request)
    if err != nil {
        return response, err
    }
    err = session.getServerError(response.StatusCode, response.Body, "DELETE request failed")
    return response, err
}

// put performs a PUT request to the Azure management API.
// Be aware that Azure may perform PUT operations asynchronously.  If you are
// not sure, call blockUntilCompleted() on the response.
func (session *x509Session) put(path, apiVersion string, body []byte, contentType string) (*x509Response, error) {
    request := newX509RequestPUT(session.composeURL(path), apiVersion, body, contentType)
    response, err := _X509Dispatcher(session, request)
    if err != nil {
        return nil, err
    }
    err = session.getServerError(response.StatusCode, response.Body, "PUT request failed")
    return response, err
}