-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathhub_membership.go
323 lines (282 loc) · 9.58 KB
/
hub_membership.go
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
package hub
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"github.com/MayaraCloud/terraform-provider-anthos/debug"
"github.com/avast/retry-go"
)
// GetMembership gets details of a hub membership.
// This method also initializes/updates the client component
func (c *Client) GetMembership(membershipID string, checkNotExisting bool) error {
// Call the gkehub api
APIURL := prodAddr + "v1/projects/" + c.projectID + "/locations/" + c.location + "/memberships/" + membershipID
response, err := c.svc.client.Get(APIURL)
if err != nil {
return fmt.Errorf("get request: %w", err)
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("reading get request body: %w", err)
}
// If we are checking if the resource does not exist
// we need a 404 here
if checkNotExisting && response.StatusCode == 404 {
return nil
}
statusOK := response.StatusCode >= 200 && response.StatusCode < 300
if !statusOK {
return fmt.Errorf("Bad %v status code: %v", response.StatusCode, string(body))
}
err = json.Unmarshal(body, &c.Resource)
if err != nil {
return fmt.Errorf("un-marshaling request body: %w", err)
}
if checkNotExisting && response.StatusCode != 404 {
return fmt.Errorf("The resource already exists in the Hub: %v", string(body))
}
return nil
}
// CreateMembership creates a hub membership
// The client object should already contain the
// updated resource component updated in another method
func (c *Client) CreateMembership(membershipID string) error {
// Validate exclusivity if the cluster has a manifest CRD present
if c.K8S.CRManifest != "" {
err := c.ValidateExclusivity(membershipID)
if err != nil {
return fmt.Errorf("Validating exclusivity: %w", err)
}
}
// Calling the creation API
createResponse, err := c.CallCreateMembershipAPI(membershipID)
if err != nil {
return fmt.Errorf("Calling CallCreateMembershipAPI: %w", err)
}
// Wait until we get an ok from CheckOperation
retry.Attempts(60)
err = retry.Do(
func() error {
return c.CheckOperation(createResponse["name"].(string))
})
if err != nil {
return fmt.Errorf("Retry checking CreateMembership operation: %w", err)
}
return nil
}
// CallCreateMembershipAPI creates a hub membership
// The client object should already contain the
// updated resource component updated in another method
func (c *Client) CallCreateMembershipAPI(membershipID string) (HTTPResult, error) {
// Create the json POST request body
var rawBody struct {
Description string `json:"description"`
ExternalID string `json:"externalId"`
}
rawBody.Description = c.Resource.Description
rawBody.ExternalID = c.K8S.UUID
body, err := json.Marshal(rawBody)
if err != nil {
return nil, fmt.Errorf("Marshaling create request body: %w", err)
}
// Create a url object to append parameters to it
APIURL := prodAddr + "v1/projects/" + c.projectID + "/locations/" + c.location + "/memberships"
u, err := url.Parse(APIURL)
if err != nil {
return nil, fmt.Errorf("Parsing %v url: %w", APIURL, err)
}
q := u.Query()
q.Set("alt", "json")
q.Set("membershipId", membershipID)
u.RawQuery = q.Encode()
// Go ahead with the request
response, err := c.svc.client.Post(u.String(), "application/json", bytes.NewBuffer(body))
if err != nil {
return nil, fmt.Errorf("create POST request: %w", err)
}
defer response.Body.Close()
return DecodeHTTPResult(response.Body)
}
// HTTPResult is used to store the result of an http request
type HTTPResult map[string]interface{}
// DecodeHTTPResult decodes an http response body
func DecodeHTTPResult(httpBody io.ReadCloser) (HTTPResult, error) {
var h HTTPResult
err := json.NewDecoder(httpBody).Decode(&h)
if err != nil {
return nil, fmt.Errorf("Decoding http body response: %w", err)
}
return h, nil
}
// CheckOperation checks a hub operation status and returns true if the operation is done
func (c *Client) CheckOperation(operationName string) error {
// Create a url object to append parameters to it
APIURL := prodAddr + "v1/" + operationName
// Create the url parameters
u, err := url.Parse(APIURL)
if err != nil {
return retry.Unrecoverable(fmt.Errorf("Parsing %v url: %w", APIURL, err))
}
q := u.Query()
q.Set("alt", "json")
u.RawQuery = q.Encode()
// Go ahead with the request
response, err := c.svc.client.Get(u.String())
if err != nil {
return retry.Unrecoverable(fmt.Errorf("GET request: %w", err))
}
defer response.Body.Close()
statusOK := response.StatusCode >= 200 && response.StatusCode < 300
if !statusOK {
return retry.Unrecoverable(fmt.Errorf("Bad status code: %v", response.StatusCode))
}
result, err := DecodeHTTPResult(response.Body)
if err != nil {
return retry.Unrecoverable(fmt.Errorf("Calling DecodeHTTPResult: %w", err))
}
if result["done"] == true {
return nil
}
return fmt.Errorf("Failed to check operation: %v", result)
}
// ValidateExclusivity checks the cluster exclusivity against the API
func (c *Client) ValidateExclusivity(membershipID string) error {
// Call the gkehub api
APIURL := prodAddr + "v1beta1/projects/" + c.projectID + "/locations/" + c.location + "/memberships:validateExclusivity"
// Create the url parameters
u, err := url.Parse(APIURL)
if err != nil {
return fmt.Errorf("Parsing %v url: %w", APIURL, err)
}
q := u.Query()
q.Set("crManifest", c.K8S.CRManifest)
q.Set("intendedMembership", membershipID)
q.Set("alt", "json")
u.RawQuery = q.Encode()
// Go ahead with the request
response, err := c.svc.client.Get(u.String())
if err != nil {
return fmt.Errorf("get request: %w", err)
}
defer response.Body.Close()
statusOK := response.StatusCode >= 200 && response.StatusCode < 300
if !statusOK {
return fmt.Errorf("Bad status code: %v", response.Body)
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("reading get request body: %w", err)
}
var result GRCPResponse
err = json.Unmarshal(body, &result)
if err != nil {
return fmt.Errorf("json Un-marshaling body: %w", err)
}
// 0 == OK in gRCP codes, see below.
if result.Status.Code != 0 {
return fmt.Errorf("%v", result.Status.Message)
}
return nil
}
// GRCPResponse follows the https://cloud.google.com/apis/design/errors
// Code must be one of the following
// https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
type GRCPResponse struct {
Status GRCPResponseStatus `json:"status"`
}
// GRCPResponseStatus is the inner GRCPResponse struct
type GRCPResponseStatus struct {
// Code contains the validation result. As such,
// * OK means that exclusivity may be obtained if the manifest produced by
// GenerateExclusivityManifest can successfully be applied.
// * ALREADY_EXISTS means that the Membership CRD is already owned by another
// Hub. See status.message for more information when this occurs
Code int32 `json:"code"`
Message string `json:"message"`
Details map[string]interface{} `json:"details"`
}
// GenerateExclusivity checks the cluster exclusivity against the API
func (c *Client) GenerateExclusivity(membershipID string) error {
// Call the gkehub api
APIURL := prodAddr + "v1beta1/projects/" + c.projectID + "/locations/" + c.location + "/memberships/" + membershipID + ":generateExclusivityManifest"
// Create the url parameters
u, err := url.Parse(APIURL)
if err != nil {
return fmt.Errorf("Parsing %v url: %w", APIURL, err)
}
q := u.Query()
q.Set("name", c.Resource.Name)
q.Set("crManifest", c.K8S.CRManifest)
q.Set("crdManifest", c.K8S.CRDManifest)
q.Set("alt", "json")
u.RawQuery = q.Encode()
// Go ahead with the request
response, err := c.svc.client.Get(u.String())
if err != nil {
return fmt.Errorf("get request: %w", err)
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("reading get request body: %w", err)
}
debug.GoLog("GenerateExclusivity: manifest response: " + string(body))
statusOK := response.StatusCode >= 200 && response.StatusCode < 300
if !statusOK {
return fmt.Errorf("Bad status code: %v", string(body))
}
type manifestResponse struct {
CRDManifest string `json:"crdManifest"`
CRManifest string `json:"crManifest"`
}
var result manifestResponse
err = json.Unmarshal(body, &result)
if err != nil {
return fmt.Errorf("json Un-marshaling body: %w", err)
}
// Populate the client with the manifest and CRD from the gkehub API
c.K8S.CRDManifest = result.CRDManifest
c.K8S.CRManifest = result.CRManifest
if result.CRDManifest == "" && result.CRManifest == "" {
debug.GoLog("GenerateExclusivity: the client received empty strings")
}
return nil
}
// DeleteMembership deletes a hub membership
// The client object should already contain the
// updated resource component updated in another method
func (c *Client) DeleteMembership() error {
// Delete a url object to append parameters to it
APIURL := prodAddr + "v1/" + c.Resource.Name
u, err := url.Parse(APIURL)
if err != nil {
return fmt.Errorf("Parsing %v url: %w", APIURL, err)
}
q := u.Query()
q.Set("alt", "json")
u.RawQuery = q.Encode()
// Go ahead with the request
req, err := http.NewRequest("DELETE", u.String(), nil)
if err != nil {
return fmt.Errorf("Creating Delete request: %w", err)
}
response, err := c.svc.client.Do(req)
if err != nil {
return fmt.Errorf("Sending DELETE request: %w", err)
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("reading get request body: %w", err)
}
statusOK := response.StatusCode >= 200 && response.StatusCode < 300
if !statusOK {
return fmt.Errorf("Bad %v status code: %v", response.StatusCode, string(body))
}
return nil
}