@@ -17,7 +17,12 @@ import (
17
17
"net/http"
18
18
"os"
19
19
"path/filepath"
20
+ "sort"
20
21
"strings"
22
+ "time"
23
+
24
+ "go4.org/types"
25
+ revtype "golang.org/x/build/types"
21
26
)
22
27
23
28
var (
@@ -27,10 +32,16 @@ var (
27
32
num = flag .Int ("n" , 0 , "Number of servers to create; if zero, defaults to a value as a function of --staging" )
28
33
tags = flag .String ("tags" , "" , "Comma-separated list of tags. The build key tags should be of the form 'buildkey_linux-arm_HEXHEXHEXHEXHEX'. If empty, it's automatic." )
29
34
staging = flag .Bool ("staging" , false , "If true, deploy staging instances (with staging names and tags) instead of prod." )
35
+ listAll = flag .Bool ("list-all" , false , "If true, list all (prod, staging, other) current Scaleway servers and stop without making changes." )
36
+ list = flag .Bool ("list" , false , "If true, list all prod (or staging, if -staging) servers, including missing ones." )
30
37
)
31
38
32
- // ctype is the Commercial Type of server we use for the builders.
33
- const ctype = "C1"
39
+ const (
40
+ // ctype is the Commercial Type of server we use for the builders.
41
+ ctype = "C1"
42
+
43
+ scalewayAPIBase = "https://api.scaleway.com"
44
+ )
34
45
35
46
func main () {
36
47
flag .Parse ()
@@ -45,7 +56,7 @@ func main() {
45
56
if * staging {
46
57
* num = 5
47
58
} else {
48
- * num = 20
59
+ * num = 50
49
60
}
50
61
}
51
62
if * token == "" {
@@ -62,46 +73,97 @@ func main() {
62
73
if err != nil {
63
74
log .Fatal (err )
64
75
}
76
+ var names []string
65
77
servers := map [string ]* Server {}
66
78
for _ , s := range serverList {
67
79
servers [s .Name ] = s
80
+ names = append (names , s .Name )
81
+ }
82
+ sort .Strings (names )
83
+ if * listAll {
84
+ for _ , name := range names {
85
+ s := servers [name ]
86
+ fmt .Printf ("%s: %v, id=%v, state=%s, created=%v, modified=%v, image=%v\n " ,
87
+ name , s .PublicIP , s .ID , s .State , s .CreationDate , s .ModificationDate , s .Image )
88
+ }
89
+ return
68
90
}
69
-
70
91
for i := 1 ; i <= * num ; i ++ {
71
- name := fmt .Sprintf ("scaleway-prod-%02d" , i )
72
- if * staging {
73
- name = fmt .Sprintf ("scaleway-staging-%02d" , i )
92
+ name := serverName (i )
93
+ if _ , ok := servers [name ]; ! ok {
94
+ servers [name ] = & Server {Name : name }
95
+ names = append (names , name )
74
96
}
75
- _ , ok := servers [name ]
76
- if ! ok {
77
- tags := strings .Split (* tags , "," )
78
- if * staging {
79
- tags = append (tags , "staging" )
80
- }
81
- body , err := json .Marshal (createServerRequest {
82
- Org : * org ,
83
- Name : name ,
84
- Image : * image ,
85
- CommercialType : ctype ,
86
- Tags : tags ,
87
- })
88
- if err != nil {
89
- log .Fatal (err )
90
- }
91
- log .Printf ("Doing req %q for token %q" , body , * token )
92
- req , err := http .NewRequest ("POST" , "https://api.scaleway.com/servers" , bytes .NewReader (body ))
93
- if err != nil {
94
- log .Fatal (err )
97
+ }
98
+ sort .Strings (names )
99
+
100
+ for name , revBuilder := range getConnectedMachines () {
101
+ if _ , ok := servers [name ]; ! ok {
102
+ log .Printf ("Machine connected to farmer.golang.org is unknown to scaleway: %v; ignoring" , name )
103
+ continue
104
+ }
105
+ servers [name ].Connected = revBuilder
106
+ }
107
+
108
+ if * list {
109
+ for _ , name := range names {
110
+ s := servers [name ]
111
+ status := "NOT_CONNECTED"
112
+ if s .Connected != nil {
113
+ status = "ok"
95
114
}
96
- req .Header .Set ("Content-Type" , "application/json" )
97
- req .Header .Set ("X-Auth-Token" , * token )
98
- res , err := http .DefaultClient .Do (req )
99
- if err != nil {
100
- log .Fatal (err )
115
+ fmt .Printf ("%s: %s, %v, id=%v, state=%s, created=%v, modified=%v, image=%v\n " ,
116
+ name , status , s .PublicIP , s .ID , s .State , s .CreationDate , s .ModificationDate , s .Image )
117
+ }
118
+ }
119
+
120
+ for i := 1 ; i <= * num ; i ++ {
121
+ name := serverName (i )
122
+ server := servers [name ]
123
+ if server .Connected != nil {
124
+ continue
125
+ }
126
+ if server .State == "running" {
127
+ if time .Time (server .ModificationDate ).Before (time .Now ().Add (15 * time .Minute )) {
128
+ log .Printf ("rebooting old running-but-disconnected %q server..." , name )
129
+ err := cl .serverAction (server .ID , "reboot" )
130
+ log .Printf ("reboot(%q): %v" , name , err )
131
+ continue
101
132
}
102
- log .Printf ("Create of %v: %v" , i , res .Status )
103
- res .Body .Close ()
133
+ // Started recently. Maybe still booting.
134
+ continue
135
+ }
136
+ if server .State != "" {
137
+ log .Printf ("server %q in state %q; not creating" , name , server .State )
138
+ continue
139
+ }
140
+ tags := strings .Split (* tags , "," )
141
+ if * staging {
142
+ tags = append (tags , "staging" )
143
+ }
144
+ body , err := json .Marshal (createServerRequest {
145
+ Org : * org ,
146
+ Name : name ,
147
+ Image : * image ,
148
+ CommercialType : ctype ,
149
+ Tags : tags ,
150
+ })
151
+ if err != nil {
152
+ log .Fatal (err )
153
+ }
154
+ log .Printf ("Doing req %q for token %q" , body , * token )
155
+ req , err := http .NewRequest ("POST" , scalewayAPIBase + "/servers" , bytes .NewReader (body ))
156
+ if err != nil {
157
+ log .Fatal (err )
158
+ }
159
+ req .Header .Set ("Content-Type" , "application/json" )
160
+ req .Header .Set ("X-Auth-Token" , * token )
161
+ res , err := http .DefaultClient .Do (req )
162
+ if err != nil {
163
+ log .Fatal (err )
104
164
}
165
+ log .Printf ("Create of %v: %v" , i , res .Status )
166
+ res .Body .Close ()
105
167
}
106
168
107
169
serverList , err = cl .Servers ()
@@ -113,7 +175,7 @@ func main() {
113
175
continue
114
176
}
115
177
if s .State == "stopped" {
116
- log .Printf ("Powering on %s = %v" , s .ID , cl .PowerOn (s .ID ))
178
+ log .Printf ("Powering on %s (%s) = %v" , s . Name , s .ID , cl .PowerOn (s .ID ))
117
179
}
118
180
}
119
181
}
@@ -130,12 +192,31 @@ type Client struct {
130
192
Token string
131
193
}
132
194
195
+ // Delete deletes a server. It needs to be powered off in "stopped" state first.
196
+ //
197
+ // This is currently unused. An earlier version of this tool used it briefly before
198
+ // changing to use the reboot action. We might want this later.
199
+ func (c * Client ) Delete (serverID string ) error {
200
+ req , _ := http .NewRequest ("DELETE" , scalewayAPIBase + "/servers/" + serverID , nil )
201
+ req .Header .Set ("Content-Type" , "application/json" )
202
+ req .Header .Set ("X-Auth-Token" , c .Token )
203
+ res , err := http .DefaultClient .Do (req )
204
+ if err != nil {
205
+ return err
206
+ }
207
+ defer res .Body .Close ()
208
+ if res .StatusCode != http .StatusNoContent {
209
+ return fmt .Errorf ("error deleting %s: %v" , serverID , res .Status )
210
+ }
211
+ return nil
212
+ }
213
+
133
214
func (c * Client ) PowerOn (serverID string ) error {
134
215
return c .serverAction (serverID , "poweron" )
135
216
}
136
217
137
218
func (c * Client ) serverAction (serverID , action string ) error {
138
- req , _ := http .NewRequest ("POST" , "https://api.scaleway.com /servers/"+ serverID + "/action" , strings .NewReader (fmt .Sprintf (`{"action":"%s"}` , action )))
219
+ req , _ := http .NewRequest ("POST" , scalewayAPIBase + " /servers/"+ serverID + "/action" , strings .NewReader (fmt .Sprintf (`{"action":"%s"}` , action )))
139
220
req .Header .Set ("Content-Type" , "application/json" )
140
221
req .Header .Set ("X-Auth-Token" , c .Token )
141
222
res , err := http .DefaultClient .Do (req )
@@ -144,21 +225,21 @@ func (c *Client) serverAction(serverID, action string) error {
144
225
}
145
226
defer res .Body .Close ()
146
227
if res .StatusCode / 100 != 2 {
147
- return fmt .Errorf ("Error doing %q on %s: %v" , action , serverID , res .Status )
228
+ return fmt .Errorf ("error doing %q on %s: %v" , action , serverID , res .Status )
148
229
}
149
230
return nil
150
231
}
151
232
152
233
func (c * Client ) Servers () ([]* Server , error ) {
153
- req , _ := http .NewRequest ("GET" , "https://api.scaleway.com /servers" , nil )
234
+ req , _ := http .NewRequest ("GET" , scalewayAPIBase + " /servers" , nil )
154
235
req .Header .Set ("X-Auth-Token" , c .Token )
155
236
res , err := http .DefaultClient .Do (req )
156
237
if err != nil {
157
238
return nil , err
158
239
}
159
240
defer res .Body .Close ()
160
- if res .StatusCode != 200 {
161
- return nil , fmt .Errorf ("Failed to get Server list: %v" , res .Status )
241
+ if res .StatusCode != http . StatusOK {
242
+ return nil , fmt .Errorf ("failed to get Server list: %v" , res .Status )
162
243
}
163
244
var jres struct {
164
245
Servers []* Server `json:"servers"`
@@ -168,25 +249,45 @@ func (c *Client) Servers() ([]*Server, error) {
168
249
}
169
250
170
251
type Server struct {
171
- ID string `json:"id"`
172
- Name string `json:"name"`
173
- PublicIP * IP `json:"public_ip"`
174
- PrivateIP string `json:"private_ip"`
175
- Tags []string `json:"tags"`
176
- State string `json:"state"`
177
- Image * Image `json:"image"`
252
+ ID string `json:"id"`
253
+ Name string `json:"name"`
254
+ PublicIP * IP `json:"public_ip"`
255
+ PrivateIP string `json:"private_ip"`
256
+ Tags []string `json:"tags"`
257
+ State string `json:"state"`
258
+ Image * Image `json:"image"`
259
+ CreationDate types.Time3339 `json:"creation_date"`
260
+ ModificationDate types.Time3339 `json:"modification_date"`
261
+
262
+ // Connected is non-nil if the server is connected to farmer.golang.org.
263
+ // This does not come from the Scaleway API.
264
+ Connected * revtype.ReverseBuilder `json:"-"`
178
265
}
179
266
180
267
type Image struct {
181
268
ID string `json:"id"`
182
269
Name string `json:"name"`
183
270
}
184
271
272
+ func (im * Image ) String () string {
273
+ if im == nil {
274
+ return "<no Image>"
275
+ }
276
+ return im .ID
277
+ }
278
+
185
279
type IP struct {
186
280
ID string `json:"id"`
187
281
Address string `json:"address"`
188
282
}
189
283
284
+ func (ip * IP ) String () string {
285
+ if ip == nil {
286
+ return "<no IP>"
287
+ }
288
+ return ip .Address
289
+ }
290
+
190
291
// defaultBuilderTags returns the default value of the "tags" flag.
191
292
// It returns a comma-separated list of builder tags (each of the form buildkey_$(BUILDER)_$(SECRETHEX)).
192
293
func defaultBuilderTags (baseKeyFile string ) string {
@@ -203,3 +304,31 @@ func defaultBuilderTags(baseKeyFile string) string {
203
304
}
204
305
return strings .Join (tags , "," )
205
306
}
307
+
308
+ func serverName (i int ) string {
309
+ if * staging {
310
+ return fmt .Sprintf ("scaleway-staging-%02d" , i )
311
+ }
312
+ return fmt .Sprintf ("scaleway-prod-%02d" , i )
313
+ }
314
+
315
+ func getConnectedMachines () map [string ]* revtype.ReverseBuilder {
316
+ const reverseURL = "https://farmer.golang.org/status/reverse.json"
317
+ res , err := http .Get (reverseURL )
318
+ if err != nil {
319
+ log .Fatal (err )
320
+ }
321
+ defer res .Body .Close ()
322
+ if res .StatusCode != http .StatusOK {
323
+ log .Fatal ("getting %s: %s" , reverseURL , res .Status )
324
+ }
325
+ var jres revtype.ReverseBuilderStatus
326
+ if err := json .NewDecoder (res .Body ).Decode (& jres ); err != nil {
327
+ log .Fatalf ("reading %s: %v" , reverseURL , err )
328
+ }
329
+ st := jres .HostTypes ["host-linux-arm-scaleway" ]
330
+ if st == nil {
331
+ return nil
332
+ }
333
+ return st .Machines
334
+ }
0 commit comments