-
Notifications
You must be signed in to change notification settings - Fork 0
/
httpclient.go
139 lines (111 loc) · 3.56 KB
/
httpclient.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
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
const maxRetries = 2
const delayBetweenRetries = 5 * time.Second
type AzurePipelinesApiPoolNameResponse struct {
// Because there could be multiple Azure Pipeline pools with the same name, the API returns an array. The objects
// have many more fields, but we only care about the ID, and omit defining the other fields.
Value []struct {
ID int `json:"id"`
} `json:"value"`
}
func getPoolIdFromName(pat, organizationUrl, poolName string, httpClient *http.Client) (int64, error) {
url := fmt.Sprintf("%s/_apis/distributedtask/pools?poolName=%s", organizationUrl, poolName)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return 0, err
}
req.SetBasicAuth("", pat)
if err != nil {
return 0, err
}
response, err := doRequestWithRetries(httpClient, req)
if err != nil {
return 0, err
}
defer response.Body.Close()
bytesResponse, err := io.ReadAll(response.Body)
if err != nil {
return 0, err
}
if !(response.StatusCode >= 200 && response.StatusCode <= 299) {
return 0, fmt.Errorf("Azure DevOps REST API returned error. url: %s status: %d response: %s", url, response.StatusCode, string(bytesResponse))
}
var result AzurePipelinesApiPoolNameResponse
err = json.Unmarshal(bytesResponse, &result)
if err != nil {
return 0, err
}
count := len(result.Value)
if count == 0 {
return 0, fmt.Errorf("agent pool with name `%s` not found in response", poolName)
}
if count != 1 {
return 0, fmt.Errorf("found %d agent pools with name `%s`", count, poolName)
}
poolId := int64(result.Value[0].ID)
return poolId, nil
}
func registerFakeAgent(pat, organizationUrl, agentNamePrefix string, capabilities *map[string]string, poolId int64, httpClient *http.Client) (string, error) {
url := fmt.Sprintf("%s/_apis/distributedtask/pools/%d/agents?api-version=7.0", organizationUrl, poolId)
for {
fakeAgentName := fmt.Sprintf("%s-%s", agentNamePrefix, randomString(8))
requestBodyTemplate := `{
"name": "%s",
"version": "99.999.9",
"osDescription": "Linux 5.15.49-linuxkit-pr #1 SMP PREEMPT Thu May 25 07:27:39 UTC 2023",
"enabled": true,
"status": "offline",
"provisioningState": "Provisioned",
"systemCapabilities": %s
}`
capabilitiesJsonStr, _ := json.Marshal(*capabilities)
requestBody := fmt.Sprintf(requestBodyTemplate, fakeAgentName, capabilitiesJsonStr)
req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(requestBody)))
if err != nil {
return "", err
}
req.SetBasicAuth("", pat)
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/json")
response, err := doRequestWithRetries(httpClient, req)
if err != nil {
return "", err
}
defer response.Body.Close()
if response.StatusCode == 409 {
fmt.Printf("Agent with name '%s' already exists, retrying with different name ...\n", fakeAgentName)
continue // 409 = HTTP "conflict"
}
if response.StatusCode != 200 {
return "", fmt.Errorf("Azure DevOps REST API returned error. url: %s status: %d", url, response.StatusCode)
}
return fakeAgentName, nil
}
}
func doRequestWithRetries(httpClient *http.Client, request *http.Request) (*http.Response, error) {
retriesLeft := maxRetries
var lastError error
for retriesLeft >= 0 {
if retriesLeft != maxRetries {
fmt.Printf("Retrying request in a few seconds. Last error: %+v\n", lastError)
time.Sleep(delayBetweenRetries)
}
response, err := httpClient.Do(request)
if err == nil {
return response, nil
}
lastError = err
retriesLeft -= 1
}
return nil, lastError
}