Add Primary IP Support (#204)
Signed-off-by: Lukas Kämmerling <lukas.kaemmerling@hetzner-cloud.de>
Lukas Kämmerling authored 1 year, 10 months ago
GitHub committed 1 year, 10 months ago
16 | 16 | |
17 | 17 | test:tests: |
18 | 18 | stage: test |
19 | image: golang:1.17 | |
19 | image: golang:1.18 | |
20 | 20 | script: |
21 | 21 | - go test -v -race ./... |
22 | 22 | except: |
81 | 81 | Volume VolumeClient |
82 | 82 | PlacementGroup PlacementGroupClient |
83 | 83 | RDNS RDNSClient |
84 | PrimaryIP PrimaryIPClient | |
84 | 85 | } |
85 | 86 | |
86 | 87 | // A ClientOption is used to configure a Client. |
186 | 187 | client.Firewall = FirewallClient{client: client} |
187 | 188 | client.PlacementGroup = PlacementGroupClient{client: client} |
188 | 189 | client.RDNS = RDNSClient{client: client} |
190 | client.PrimaryIP = PrimaryIPClient{client: client} | |
189 | 191 | |
190 | 192 | return client |
191 | 193 | } |
1 | 1 | package hcloud |
2 | 2 | |
3 | 3 | // Version is the library's version following Semantic Versioning. |
4 | const Version = "1.34.0" | |
4 | const Version = "1.35.0" |
10 | 10 | Image ImagePricing |
11 | 11 | FloatingIP FloatingIPPricing |
12 | 12 | FloatingIPs []FloatingIPTypePricing |
13 | PrimaryIPs []PrimaryIPPricing | |
13 | 14 | Traffic TrafficPricing |
14 | 15 | ServerBackup ServerBackupPricing |
15 | 16 | ServerTypes []ServerTypePricing |
27 | 28 | Gross string |
28 | 29 | } |
29 | 30 | |
31 | // PrimaryIPPrice represents a price. Net amount and gross amount are | |
32 | // specified as strings and it is the user's responsibility to convert them to | |
33 | // appropriate types for calculations. | |
34 | type PrimaryIPPrice struct { | |
35 | Net string | |
36 | Gross string | |
37 | } | |
38 | ||
30 | 39 | // ImagePricing provides pricing information for imaegs. |
31 | 40 | type ImagePricing struct { |
32 | 41 | PerGBMonth Price |
41 | 50 | type FloatingIPTypePricing struct { |
42 | 51 | Type FloatingIPType |
43 | 52 | Pricings []FloatingIPTypeLocationPricing |
53 | } | |
54 | ||
55 | // PrimaryIPTypePricing defines the schema of pricing information for a primary IP | |
56 | // type at a datacenter. | |
57 | type PrimaryIPTypePricing struct { | |
58 | Datacenter string | |
59 | Hourly PrimaryIPPrice | |
60 | Monthly PrimaryIPPrice | |
61 | } | |
62 | ||
63 | // PrimaryIPTypePricing provides pricing information for PrimaryIPs | |
64 | type PrimaryIPPricing struct { | |
65 | Type string | |
66 | Pricings []PrimaryIPTypePricing | |
44 | 67 | } |
45 | 68 | |
46 | 69 | // FloatingIPTypeLocationPricing provides pricing information for a Floating IP type |
0 | package hcloud | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "context" | |
5 | "encoding/json" | |
6 | "fmt" | |
7 | "net" | |
8 | "net/url" | |
9 | "strconv" | |
10 | "time" | |
11 | ||
12 | "github.com/hetznercloud/hcloud-go/hcloud/schema" | |
13 | ) | |
14 | ||
15 | // PrimaryIP defines a Primary IP | |
16 | type PrimaryIP struct { | |
17 | ID int | |
18 | IP net.IP | |
19 | Network *net.IPNet | |
20 | Labels map[string]string | |
21 | Name string | |
22 | Type PrimaryIPType | |
23 | Protection PrimaryIPProtection | |
24 | DNSPtr map[string]string | |
25 | AssigneeID int | |
26 | AssigneeType string | |
27 | AutoDelete bool | |
28 | Blocked bool | |
29 | Created time.Time | |
30 | Datacenter *Datacenter | |
31 | } | |
32 | ||
33 | // PrimaryIPProtection represents the protection level of a Primary IP. | |
34 | type PrimaryIPProtection struct { | |
35 | Delete bool | |
36 | } | |
37 | ||
38 | // PrimaryIPDNSPTR contains reverse DNS information for a | |
39 | // IPv4 or IPv6 Primary IP. | |
40 | type PrimaryIPDNSPTR struct { | |
41 | DNSPtr string | |
42 | IP string | |
43 | } | |
44 | ||
45 | // GetDNSPtrForIP searches for the dns assigned to the given IP address. | |
46 | // It returns an error if there is no dns set for the given IP address. | |
47 | func (p *PrimaryIP) GetDNSPtrForIP(ip net.IP) (string, error) { | |
48 | dns, ok := p.DNSPtr[ip.String()] | |
49 | if !ok { | |
50 | return "", DNSNotFoundError{ip} | |
51 | } | |
52 | ||
53 | return dns, nil | |
54 | } | |
55 | ||
56 | // PrimaryIPType represents the type of Primary IP. | |
57 | type PrimaryIPType string | |
58 | ||
59 | // PrimaryIPType Primary IP types. | |
60 | const ( | |
61 | PrimaryIPTypeIPv4 PrimaryIPType = "ipv4" | |
62 | PrimaryIPTypeIPv6 PrimaryIPType = "ipv6" | |
63 | ) | |
64 | ||
65 | // PrimaryIPCreateOpts defines the request to | |
66 | // create a Primary IP. | |
67 | type PrimaryIPCreateOpts struct { | |
68 | AssigneeID *int `json:"assignee_id,omitempty"` | |
69 | AssigneeType string `json:"assignee_type"` | |
70 | AutoDelete *bool `json:"auto_delete,omitempty"` | |
71 | Datacenter string `json:"datacenter,omitempty"` | |
72 | Labels map[string]string `json:"labels,omitempty"` | |
73 | Name string `json:"name"` | |
74 | Type PrimaryIPType `json:"type"` | |
75 | } | |
76 | ||
77 | // PrimaryIPCreateResult defines the response | |
78 | // when creating a Primary IP. | |
79 | type PrimaryIPCreateResult struct { | |
80 | PrimaryIP *PrimaryIP | |
81 | Action *Action | |
82 | } | |
83 | ||
84 | // PrimaryIPUpdateOpts defines the request to | |
85 | // update a Primary IP. | |
86 | type PrimaryIPUpdateOpts struct { | |
87 | AutoDelete *bool `json:"auto_delete,omitempty"` | |
88 | Labels *map[string]string `json:"labels,omitempty"` | |
89 | Name string `json:"name,omitempty"` | |
90 | } | |
91 | ||
92 | // PrimaryIPUpdateResult defines the response | |
93 | // when updating a Primary IP. | |
94 | type PrimaryIPUpdateResult struct { | |
95 | PrimaryIP PrimaryIP `json:"primary_ip"` | |
96 | } | |
97 | ||
98 | // PrimaryIPAssignOpts defines the request to | |
99 | // assign a Primary IP to an assignee (usually a server). | |
100 | type PrimaryIPAssignOpts struct { | |
101 | ID int | |
102 | AssigneeID int `json:"assignee_id"` | |
103 | AssigneeType string `json:"assignee_type"` | |
104 | } | |
105 | ||
106 | // PrimaryIPAssignResult defines the response | |
107 | // when assigning a Primary IP to a assignee. | |
108 | type PrimaryIPAssignResult struct { | |
109 | Action schema.Action `json:"action"` | |
110 | } | |
111 | ||
112 | // PrimaryIPChangeDNSPtrOpts defines the request to | |
113 | // change a DNS PTR entry from a Primary IP | |
114 | type PrimaryIPChangeDNSPtrOpts struct { | |
115 | ID int | |
116 | DNSPtr string `json:"dns_ptr"` | |
117 | IP string `json:"ip"` | |
118 | } | |
119 | ||
120 | // PrimaryIPChangeDNSPtrResult defines the response | |
121 | // when assigning a Primary IP to a assignee. | |
122 | type PrimaryIPChangeDNSPtrResult struct { | |
123 | Action schema.Action `json:"action"` | |
124 | } | |
125 | ||
126 | // PrimaryIPChangeProtectionOpts defines the request to | |
127 | // change protection configuration of a Primary IP | |
128 | type PrimaryIPChangeProtectionOpts struct { | |
129 | ID int | |
130 | Delete bool `json:"delete"` | |
131 | } | |
132 | ||
133 | // PrimaryIPChangeProtectionResult defines the response | |
134 | // when changing a protection of a PrimaryIP | |
135 | type PrimaryIPChangeProtectionResult struct { | |
136 | Action schema.Action `json:"action"` | |
137 | } | |
138 | ||
139 | // PrimaryIPClient is a client for the Primary IP API | |
140 | type PrimaryIPClient struct { | |
141 | client *Client | |
142 | } | |
143 | ||
144 | // GetByID retrieves a Primary IP by its ID. If the Primary IP does not exist, nil is returned. | |
145 | func (c *PrimaryIPClient) GetByID(ctx context.Context, id int) (*PrimaryIP, *Response, error) { | |
146 | req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/primary_ips/%d", id), nil) | |
147 | if err != nil { | |
148 | return nil, nil, err | |
149 | } | |
150 | ||
151 | var body schema.PrimaryIPGetResult | |
152 | resp, err := c.client.Do(req, &body) | |
153 | if err != nil { | |
154 | if IsError(err, ErrorCodeNotFound) { | |
155 | return nil, resp, nil | |
156 | } | |
157 | return nil, nil, err | |
158 | } | |
159 | return PrimaryIPFromSchema(body.PrimaryIP), resp, nil | |
160 | } | |
161 | ||
162 | // GetByIP retrieves a Primary IP by its IP Address. If the Primary IP does not exist, nil is returned. | |
163 | func (c *PrimaryIPClient) GetByIP(ctx context.Context, ip string) (*PrimaryIP, *Response, error) { | |
164 | if ip == "" { | |
165 | return nil, nil, nil | |
166 | } | |
167 | primaryIPs, response, err := c.List(ctx, PrimaryIPListOpts{IP: ip}) | |
168 | if len(primaryIPs) == 0 { | |
169 | return nil, response, err | |
170 | } | |
171 | return primaryIPs[0], response, err | |
172 | } | |
173 | ||
174 | // GetByName retrieves a Primary IP by its name. If the Primary IP does not exist, nil is returned. | |
175 | func (c *PrimaryIPClient) GetByName(ctx context.Context, name string) (*PrimaryIP, *Response, error) { | |
176 | if name == "" { | |
177 | return nil, nil, nil | |
178 | } | |
179 | primaryIPs, response, err := c.List(ctx, PrimaryIPListOpts{Name: name}) | |
180 | if len(primaryIPs) == 0 { | |
181 | return nil, response, err | |
182 | } | |
183 | return primaryIPs[0], response, err | |
184 | } | |
185 | ||
186 | // Get retrieves a Primary IP by its ID if the input can be parsed as an integer, otherwise it | |
187 | // retrieves a Primary IP by its name. If the Primary IP does not exist, nil is returned. | |
188 | func (c *PrimaryIPClient) Get(ctx context.Context, idOrName string) (*PrimaryIP, *Response, error) { | |
189 | if id, err := strconv.Atoi(idOrName); err == nil { | |
190 | return c.GetByID(ctx, int(id)) | |
191 | } | |
192 | return c.GetByName(ctx, idOrName) | |
193 | } | |
194 | ||
195 | // PrimaryIPListOpts specifies options for listing Primary IPs. | |
196 | type PrimaryIPListOpts struct { | |
197 | ListOpts | |
198 | Name string | |
199 | IP string | |
200 | Sort []string | |
201 | } | |
202 | ||
203 | func (l PrimaryIPListOpts) values() url.Values { | |
204 | vals := l.ListOpts.values() | |
205 | if l.Name != "" { | |
206 | vals.Add("name", l.Name) | |
207 | } | |
208 | if l.IP != "" { | |
209 | vals.Add("ip", l.IP) | |
210 | } | |
211 | for _, sort := range l.Sort { | |
212 | vals.Add("sort", sort) | |
213 | } | |
214 | return vals | |
215 | } | |
216 | ||
217 | // List returns a list of Primary IPs for a specific page. | |
218 | // | |
219 | // Please note that filters specified in opts are not taken into account | |
220 | // when their value corresponds to their zero value or when they are empty. | |
221 | func (c *PrimaryIPClient) List(ctx context.Context, opts PrimaryIPListOpts) ([]*PrimaryIP, *Response, error) { | |
222 | path := "/primary_ips?" + opts.values().Encode() | |
223 | req, err := c.client.NewRequest(ctx, "GET", path, nil) | |
224 | if err != nil { | |
225 | return nil, nil, err | |
226 | } | |
227 | ||
228 | var body schema.PrimaryIPListResult | |
229 | resp, err := c.client.Do(req, &body) | |
230 | if err != nil { | |
231 | return nil, nil, err | |
232 | } | |
233 | primaryIPs := make([]*PrimaryIP, 0, len(body.PrimaryIPs)) | |
234 | for _, s := range body.PrimaryIPs { | |
235 | primaryIPs = append(primaryIPs, PrimaryIPFromSchema(s)) | |
236 | } | |
237 | return primaryIPs, resp, nil | |
238 | } | |
239 | ||
240 | // All returns all Primary IPs. | |
241 | func (c *PrimaryIPClient) All(ctx context.Context) ([]*PrimaryIP, error) { | |
242 | allPrimaryIPs := []*PrimaryIP{} | |
243 | ||
244 | opts := PrimaryIPListOpts{} | |
245 | opts.PerPage = 50 | |
246 | ||
247 | err := c.client.all(func(page int) (*Response, error) { | |
248 | opts.Page = page | |
249 | primaryIPs, resp, err := c.List(ctx, opts) | |
250 | if err != nil { | |
251 | return resp, err | |
252 | } | |
253 | allPrimaryIPs = append(allPrimaryIPs, primaryIPs...) | |
254 | return resp, nil | |
255 | }) | |
256 | if err != nil { | |
257 | return nil, err | |
258 | } | |
259 | ||
260 | return allPrimaryIPs, nil | |
261 | } | |
262 | ||
263 | // Create creates a Primary IP. | |
264 | func (c *PrimaryIPClient) Create(ctx context.Context, reqBody PrimaryIPCreateOpts) (*PrimaryIPCreateResult, *Response, error) { | |
265 | reqBodyData, err := json.Marshal(reqBody) | |
266 | if err != nil { | |
267 | return &PrimaryIPCreateResult{}, nil, err | |
268 | } | |
269 | ||
270 | req, err := c.client.NewRequest(ctx, "POST", "/primary_ips", bytes.NewReader(reqBodyData)) | |
271 | if err != nil { | |
272 | return &PrimaryIPCreateResult{}, nil, err | |
273 | } | |
274 | ||
275 | var respBody schema.PrimaryIPCreateResponse | |
276 | resp, err := c.client.Do(req, &respBody) | |
277 | if err != nil { | |
278 | return &PrimaryIPCreateResult{}, resp, err | |
279 | } | |
280 | var action *Action | |
281 | if respBody.Action != nil { | |
282 | action = ActionFromSchema(*respBody.Action) | |
283 | } | |
284 | primaryIP := PrimaryIPFromSchema(respBody.PrimaryIP) | |
285 | return &PrimaryIPCreateResult{ | |
286 | PrimaryIP: primaryIP, | |
287 | Action: action, | |
288 | }, resp, nil | |
289 | } | |
290 | ||
291 | // Delete deletes a Primary IP. | |
292 | func (c *PrimaryIPClient) Delete(ctx context.Context, primaryIP *PrimaryIP) (*Response, error) { | |
293 | req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/primary_ips/%d", primaryIP.ID), nil) | |
294 | if err != nil { | |
295 | return nil, err | |
296 | } | |
297 | return c.client.Do(req, nil) | |
298 | } | |
299 | ||
300 | // Update updates a Primary IP. | |
301 | func (c *PrimaryIPClient) Update(ctx context.Context, primaryIP *PrimaryIP, reqBody PrimaryIPUpdateOpts) (*PrimaryIP, *Response, error) { | |
302 | reqBodyData, err := json.Marshal(reqBody) | |
303 | if err != nil { | |
304 | return nil, nil, err | |
305 | } | |
306 | ||
307 | path := fmt.Sprintf("/primary_ips/%d", primaryIP.ID) | |
308 | req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData)) | |
309 | if err != nil { | |
310 | return nil, nil, err | |
311 | } | |
312 | ||
313 | respBody := PrimaryIPUpdateResult{} | |
314 | resp, err := c.client.Do(req, &respBody) | |
315 | if err != nil { | |
316 | return nil, resp, err | |
317 | } | |
318 | return &respBody.PrimaryIP, resp, nil | |
319 | } | |
320 | ||
321 | // Assign a Primary IP to a resource | |
322 | func (c *PrimaryIPClient) Assign(ctx context.Context, opts PrimaryIPAssignOpts) (*Action, *Response, error) { | |
323 | reqBodyData, err := json.Marshal(opts) | |
324 | if err != nil { | |
325 | return nil, nil, err | |
326 | } | |
327 | ||
328 | path := fmt.Sprintf("/primary_ips/%d/actions/assign", opts.ID) | |
329 | req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) | |
330 | if err != nil { | |
331 | return nil, nil, err | |
332 | } | |
333 | ||
334 | var respBody PrimaryIPAssignResult | |
335 | resp, err := c.client.Do(req, &respBody) | |
336 | if err != nil { | |
337 | return nil, resp, err | |
338 | } | |
339 | return ActionFromSchema(respBody.Action), resp, nil | |
340 | } | |
341 | ||
342 | // Unassign a Primary IP from a resource | |
343 | func (c *PrimaryIPClient) Unassign(ctx context.Context, id int) (*Action, *Response, error) { | |
344 | path := fmt.Sprintf("/primary_ips/%d/actions/unassign", id) | |
345 | req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader([]byte{})) | |
346 | if err != nil { | |
347 | return nil, nil, err | |
348 | } | |
349 | ||
350 | var respBody PrimaryIPAssignResult | |
351 | resp, err := c.client.Do(req, &respBody) | |
352 | if err != nil { | |
353 | return nil, resp, err | |
354 | } | |
355 | return ActionFromSchema(respBody.Action), resp, nil | |
356 | } | |
357 | ||
358 | // ChangeDNSPtr Change the reverse DNS from a Primary IP | |
359 | func (c *PrimaryIPClient) ChangeDNSPtr(ctx context.Context, opts PrimaryIPChangeDNSPtrOpts) (*Action, *Response, error) { | |
360 | reqBodyData, err := json.Marshal(opts) | |
361 | if err != nil { | |
362 | return nil, nil, err | |
363 | } | |
364 | ||
365 | path := fmt.Sprintf("/primary_ips/%d/actions/change_dns_ptr", opts.ID) | |
366 | req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) | |
367 | if err != nil { | |
368 | return nil, nil, err | |
369 | } | |
370 | ||
371 | var respBody PrimaryIPChangeDNSPtrResult | |
372 | resp, err := c.client.Do(req, &respBody) | |
373 | if err != nil { | |
374 | return nil, resp, err | |
375 | } | |
376 | return ActionFromSchema(respBody.Action), resp, nil | |
377 | } | |
378 | ||
379 | // ChangeProtection Changes the protection configuration of a Primary IP. | |
380 | func (c *PrimaryIPClient) ChangeProtection(ctx context.Context, opts PrimaryIPChangeProtectionOpts) (*Action, *Response, error) { | |
381 | reqBodyData, err := json.Marshal(opts) | |
382 | if err != nil { | |
383 | return nil, nil, err | |
384 | } | |
385 | ||
386 | path := fmt.Sprintf("/primary_ips/%d/actions/change_protection", opts.ID) | |
387 | req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) | |
388 | if err != nil { | |
389 | return nil, nil, err | |
390 | } | |
391 | ||
392 | var respBody PrimaryIPChangeProtectionResult | |
393 | resp, err := c.client.Do(req, &respBody) | |
394 | if err != nil { | |
395 | return nil, resp, err | |
396 | } | |
397 | return ActionFromSchema(respBody.Action), resp, nil | |
398 | } |
0 | package hcloud | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "encoding/json" | |
5 | "net/http" | |
6 | "testing" | |
7 | ||
8 | "github.com/google/go-cmp/cmp" | |
9 | "github.com/hetznercloud/hcloud-go/hcloud/schema" | |
10 | "github.com/stretchr/testify/assert" | |
11 | ) | |
12 | ||
13 | func TestPrimaryIPClient(t *testing.T) { | |
14 | t.Run("GetByID", func(t *testing.T) { | |
15 | env := newTestEnv() | |
16 | defer env.Teardown() | |
17 | ||
18 | env.Mux.HandleFunc("/primary_ips/1", func(w http.ResponseWriter, r *http.Request) { | |
19 | json.NewEncoder(w).Encode(schema.PrimaryIPGetResult{ | |
20 | PrimaryIP: schema.PrimaryIP{ | |
21 | ID: 1, | |
22 | }, | |
23 | }) | |
24 | }) | |
25 | ||
26 | ctx := context.Background() | |
27 | primaryIP, _, err := env.Client.PrimaryIP.GetByID(ctx, 1) | |
28 | if err != nil { | |
29 | t.Fatal(err) | |
30 | } | |
31 | if primaryIP == nil { | |
32 | t.Fatal("no primary_ip") | |
33 | } | |
34 | if primaryIP.ID != 1 { | |
35 | t.Errorf("unexpected primary_ip ID: %v", primaryIP.ID) | |
36 | } | |
37 | ||
38 | t.Run("via Get", func(t *testing.T) { | |
39 | primaryIP, _, err := env.Client.PrimaryIP.Get(ctx, "1") | |
40 | if err != nil { | |
41 | t.Fatal(err) | |
42 | } | |
43 | if primaryIP == nil { | |
44 | t.Fatal("no primary_ip") | |
45 | } | |
46 | if primaryIP.ID != 1 { | |
47 | t.Errorf("unexpected primary_ip ID: %v", primaryIP.ID) | |
48 | } | |
49 | }) | |
50 | }) | |
51 | ||
52 | t.Run("GetByID (not found)", func(t *testing.T) { | |
53 | env := newTestEnv() | |
54 | defer env.Teardown() | |
55 | ||
56 | env.Mux.HandleFunc("/primary_ips/1", func(w http.ResponseWriter, r *http.Request) { | |
57 | w.Header().Set("Content-Type", "application/json") | |
58 | w.WriteHeader(http.StatusNotFound) | |
59 | json.NewEncoder(w).Encode(schema.ErrorResponse{ | |
60 | Error: schema.Error{ | |
61 | Code: string(ErrorCodeNotFound), | |
62 | }, | |
63 | }) | |
64 | }) | |
65 | ||
66 | ctx := context.Background() | |
67 | primaryIP, _, err := env.Client.PrimaryIP.GetByID(ctx, 1) | |
68 | if err != nil { | |
69 | t.Fatal(err) | |
70 | } | |
71 | if primaryIP != nil { | |
72 | t.Fatal("expected no primary_ip") | |
73 | } | |
74 | }) | |
75 | ||
76 | t.Run("GetByName", func(t *testing.T) { | |
77 | env := newTestEnv() | |
78 | defer env.Teardown() | |
79 | ||
80 | env.Mux.HandleFunc("/primary_ips", func(w http.ResponseWriter, r *http.Request) { | |
81 | if r.URL.RawQuery != "name=fsn1-dc8" { | |
82 | t.Fatal("missing name query") | |
83 | } | |
84 | json.NewEncoder(w).Encode(schema.PrimaryIPListResult{ | |
85 | PrimaryIPs: []schema.PrimaryIP{ | |
86 | { | |
87 | ID: 1, | |
88 | }, | |
89 | }, | |
90 | }) | |
91 | }) | |
92 | ||
93 | ctx := context.Background() | |
94 | primaryIP, _, err := env.Client.PrimaryIP.GetByName(ctx, "fsn1-dc8") | |
95 | if err != nil { | |
96 | t.Fatal(err) | |
97 | } | |
98 | if primaryIP == nil { | |
99 | t.Fatal("no primary_ip") | |
100 | } | |
101 | if primaryIP.ID != 1 { | |
102 | t.Errorf("unexpected primary_ip ID: %v", primaryIP.ID) | |
103 | } | |
104 | ||
105 | t.Run("via Get", func(t *testing.T) { | |
106 | primaryIP, _, err := env.Client.PrimaryIP.Get(ctx, "fsn1-dc8") | |
107 | if err != nil { | |
108 | t.Fatal(err) | |
109 | } | |
110 | if primaryIP == nil { | |
111 | t.Fatal("no primary_ip") | |
112 | } | |
113 | if primaryIP.ID != 1 { | |
114 | t.Errorf("unexpected primary_ip ID: %v", primaryIP.ID) | |
115 | } | |
116 | }) | |
117 | }) | |
118 | ||
119 | t.Run("GetByName (not found)", func(t *testing.T) { | |
120 | env := newTestEnv() | |
121 | defer env.Teardown() | |
122 | ||
123 | env.Mux.HandleFunc("/primary_ips", func(w http.ResponseWriter, r *http.Request) { | |
124 | if r.URL.RawQuery != "name=fsn1-dc8" { | |
125 | t.Fatal("missing name query") | |
126 | } | |
127 | json.NewEncoder(w).Encode(schema.PrimaryIPListResult{ | |
128 | PrimaryIPs: []schema.PrimaryIP{}, | |
129 | }) | |
130 | }) | |
131 | ||
132 | ctx := context.Background() | |
133 | primaryIP, _, err := env.Client.PrimaryIP.GetByName(ctx, "fsn1-dc8") | |
134 | if err != nil { | |
135 | t.Fatal(err) | |
136 | } | |
137 | if primaryIP != nil { | |
138 | t.Fatal("unexpected primary_ip") | |
139 | } | |
140 | }) | |
141 | ||
142 | t.Run("GetByName (empty)", func(t *testing.T) { | |
143 | env := newTestEnv() | |
144 | defer env.Teardown() | |
145 | ||
146 | ctx := context.Background() | |
147 | primaryIP, _, err := env.Client.PrimaryIP.GetByName(ctx, "") | |
148 | if err != nil { | |
149 | t.Fatal(err) | |
150 | } | |
151 | if primaryIP != nil { | |
152 | t.Fatal("unexpected primary_ip") | |
153 | } | |
154 | }) | |
155 | ||
156 | t.Run("GetByIP", func(t *testing.T) { | |
157 | env := newTestEnv() | |
158 | defer env.Teardown() | |
159 | ||
160 | env.Mux.HandleFunc("/primary_ips", func(w http.ResponseWriter, r *http.Request) { | |
161 | if r.URL.RawQuery != "ip=127.0.0.1" { | |
162 | t.Fatal("missing name query") | |
163 | } | |
164 | json.NewEncoder(w).Encode(schema.PrimaryIPListResult{ | |
165 | PrimaryIPs: []schema.PrimaryIP{ | |
166 | { | |
167 | ID: 1, | |
168 | }, | |
169 | }, | |
170 | }) | |
171 | }) | |
172 | ||
173 | ctx := context.Background() | |
174 | primaryIP, _, err := env.Client.PrimaryIP.GetByIP(ctx, "127.0.0.1") | |
175 | if err != nil { | |
176 | t.Fatal(err) | |
177 | } | |
178 | if primaryIP == nil { | |
179 | t.Fatal("no primary_ip") | |
180 | } | |
181 | if primaryIP.ID != 1 { | |
182 | t.Errorf("unexpected primary_ip ID: %v", primaryIP.ID) | |
183 | } | |
184 | }) | |
185 | ||
186 | t.Run("List", func(t *testing.T) { | |
187 | env := newTestEnv() | |
188 | defer env.Teardown() | |
189 | ||
190 | env.Mux.HandleFunc("/primary_ips", func(w http.ResponseWriter, r *http.Request) { | |
191 | if page := r.URL.Query().Get("page"); page != "2" { | |
192 | t.Errorf("expected page 2; got %q", page) | |
193 | } | |
194 | if perPage := r.URL.Query().Get("per_page"); perPage != "50" { | |
195 | t.Errorf("expected per_page 50; got %q", perPage) | |
196 | } | |
197 | if name := r.URL.Query().Get("name"); name != "nbg1-dc3" { | |
198 | t.Errorf("expected name nbg1-dc3; got %q", name) | |
199 | } | |
200 | json.NewEncoder(w).Encode(schema.PrimaryIPListResult{ | |
201 | PrimaryIPs: []schema.PrimaryIP{ | |
202 | {ID: 1}, | |
203 | {ID: 2}, | |
204 | }, | |
205 | }) | |
206 | }) | |
207 | ||
208 | opts := PrimaryIPListOpts{} | |
209 | opts.Page = 2 | |
210 | opts.PerPage = 50 | |
211 | opts.Name = "nbg1-dc3" | |
212 | ||
213 | ctx := context.Background() | |
214 | primaryIPs, _, err := env.Client.PrimaryIP.List(ctx, opts) | |
215 | if err != nil { | |
216 | t.Fatal(err) | |
217 | } | |
218 | if len(primaryIPs) != 2 { | |
219 | t.Fatal("expected 2 primary_ips") | |
220 | } | |
221 | }) | |
222 | ||
223 | t.Run("All", func(t *testing.T) { | |
224 | env := newTestEnv() | |
225 | defer env.Teardown() | |
226 | ||
227 | env.Mux.HandleFunc("/primary_ips", func(w http.ResponseWriter, r *http.Request) { | |
228 | w.Header().Set("Content-Type", "application/json") | |
229 | json.NewEncoder(w).Encode(struct { | |
230 | PrimaryIPs []PrimaryIP `json:"primary_ips"` | |
231 | Meta schema.Meta `json:"meta"` | |
232 | }{ | |
233 | PrimaryIPs: []PrimaryIP{ | |
234 | {ID: 1}, | |
235 | {ID: 2}, | |
236 | {ID: 3}, | |
237 | }, | |
238 | Meta: schema.Meta{ | |
239 | Pagination: &schema.MetaPagination{ | |
240 | Page: 1, | |
241 | LastPage: 1, | |
242 | PerPage: 3, | |
243 | TotalEntries: 3, | |
244 | }, | |
245 | }, | |
246 | }) | |
247 | }) | |
248 | ||
249 | ctx := context.Background() | |
250 | primaryIPs, err := env.Client.PrimaryIP.All(ctx) | |
251 | if err != nil { | |
252 | t.Fatalf("PrimaryIP.List failed: %s", err) | |
253 | } | |
254 | if len(primaryIPs) != 3 { | |
255 | t.Fatalf("expected 3 primary_ips; got %d", len(primaryIPs)) | |
256 | } | |
257 | if primaryIPs[0].ID != 1 || primaryIPs[1].ID != 2 || primaryIPs[2].ID != 3 { | |
258 | t.Errorf("unexpected primary_ips") | |
259 | } | |
260 | }) | |
261 | t.Run("Create", func(t *testing.T) { | |
262 | env := newTestEnv() | |
263 | defer env.Teardown() | |
264 | ||
265 | env.Mux.HandleFunc("/primary_ips", func(w http.ResponseWriter, r *http.Request) { | |
266 | var reqBody PrimaryIPCreateOpts | |
267 | if r.Method != "POST" { | |
268 | t.Error("expected POST") | |
269 | } | |
270 | w.Header().Set("Content-Type", "application/json") | |
271 | expectedReqBody := PrimaryIPCreateOpts{ | |
272 | Name: "my-primary-ip", | |
273 | Type: PrimaryIPTypeIPv4, | |
274 | AssigneeType: "server", | |
275 | Datacenter: "fsn-dc14", | |
276 | Labels: func() map[string]string { | |
277 | labels := map[string]string{"key": "value"} | |
278 | return labels | |
279 | }(), | |
280 | } | |
281 | if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { | |
282 | t.Fatal(err) | |
283 | } | |
284 | if !cmp.Equal(expectedReqBody, reqBody) { | |
285 | t.Log(cmp.Diff(expectedReqBody, reqBody)) | |
286 | t.Error("unexpected request body") | |
287 | } | |
288 | json.NewEncoder(w).Encode(PrimaryIPCreateResult{ | |
289 | PrimaryIP: &PrimaryIP{ID: 1}, | |
290 | Action: &Action{ID: 14}, | |
291 | }) | |
292 | }) | |
293 | ||
294 | ctx := context.Background() | |
295 | opts := PrimaryIPCreateOpts{ | |
296 | Name: "my-primary-ip", | |
297 | Type: PrimaryIPTypeIPv4, | |
298 | AssigneeType: "server", | |
299 | Labels: map[string]string{"key": "value"}, | |
300 | Datacenter: "fsn-dc14", | |
301 | } | |
302 | ||
303 | result, resp, err := env.Client.PrimaryIP.Create(ctx, opts) | |
304 | assert.NoError(t, err) | |
305 | assert.NotNil(t, resp, "no response returned") | |
306 | assert.NotNil(t, result.PrimaryIP, "no primary IP returned") | |
307 | assert.NotNil(t, result.Action, "no action returned") | |
308 | }) | |
309 | t.Run("Update", func(t *testing.T) { | |
310 | env := newTestEnv() | |
311 | defer env.Teardown() | |
312 | ||
313 | env.Mux.HandleFunc("/primary_ips/1", func(w http.ResponseWriter, r *http.Request) { | |
314 | var reqBody PrimaryIPUpdateOpts | |
315 | if r.Method != "PUT" { | |
316 | t.Error("expected PUT") | |
317 | } | |
318 | w.Header().Set("Content-Type", "application/json") | |
319 | autoDelete := true | |
320 | expectedReqBody := PrimaryIPUpdateOpts{ | |
321 | Name: "my-primary-ip", | |
322 | AutoDelete: &autoDelete, | |
323 | Labels: func() *map[string]string { | |
324 | labels := map[string]string{"key": "value"} | |
325 | return &labels | |
326 | }(), | |
327 | } | |
328 | if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { | |
329 | t.Fatal(err) | |
330 | } | |
331 | if !cmp.Equal(expectedReqBody, reqBody) { | |
332 | t.Log(cmp.Diff(expectedReqBody, reqBody)) | |
333 | t.Error("unexpected request body") | |
334 | } | |
335 | json.NewEncoder(w).Encode(PrimaryIPUpdateResult{ | |
336 | PrimaryIP: PrimaryIP{ID: 1}, | |
337 | }) | |
338 | }) | |
339 | ||
340 | ctx := context.Background() | |
341 | labels := map[string]string{"key": "value"} | |
342 | autoDelete := true | |
343 | opts := PrimaryIPUpdateOpts{ | |
344 | Name: "my-primary-ip", | |
345 | AutoDelete: &autoDelete, | |
346 | Labels: &labels, | |
347 | } | |
348 | ||
349 | primaryIP := PrimaryIP{ID: 1} | |
350 | result, resp, err := env.Client.PrimaryIP.Update(ctx, &primaryIP, opts) | |
351 | assert.NoError(t, err) | |
352 | assert.NotNil(t, resp, "no response returned") | |
353 | assert.Equal(t, *result, primaryIP, "no primary IP returned") | |
354 | }) | |
355 | t.Run("Assign", func(t *testing.T) { | |
356 | env := newTestEnv() | |
357 | defer env.Teardown() | |
358 | ||
359 | env.Mux.HandleFunc("/primary_ips/1/actions/assign", func(w http.ResponseWriter, r *http.Request) { | |
360 | var reqBody PrimaryIPAssignOpts | |
361 | if r.Method != "POST" { | |
362 | t.Error("expected POST") | |
363 | } | |
364 | w.Header().Set("Content-Type", "application/json") | |
365 | expectedReqBody := PrimaryIPAssignOpts{ | |
366 | AssigneeType: "server", | |
367 | AssigneeID: 1, | |
368 | ID: 1, | |
369 | } | |
370 | if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { | |
371 | t.Fatal(err) | |
372 | } | |
373 | if !cmp.Equal(expectedReqBody, reqBody) { | |
374 | t.Log(cmp.Diff(expectedReqBody, reqBody)) | |
375 | t.Error("unexpected request body") | |
376 | } | |
377 | json.NewEncoder(w).Encode(PrimaryIPAssignResult{ | |
378 | Action: schema.Action{ID: 1}, | |
379 | }) | |
380 | }) | |
381 | ||
382 | ctx := context.Background() | |
383 | opts := PrimaryIPAssignOpts{ | |
384 | AssigneeType: "server", | |
385 | AssigneeID: 1, | |
386 | ID: 1, | |
387 | } | |
388 | ||
389 | action, resp, err := env.Client.PrimaryIP.Assign(ctx, opts) | |
390 | assert.NoError(t, err) | |
391 | assert.NotNil(t, resp, "no response returned") | |
392 | assert.NotNil(t, action, "no action returned") | |
393 | }) | |
394 | t.Run("Unassign", func(t *testing.T) { | |
395 | env := newTestEnv() | |
396 | defer env.Teardown() | |
397 | ||
398 | env.Mux.HandleFunc("/primary_ips/1/actions/unassign", func(w http.ResponseWriter, r *http.Request) { | |
399 | if r.Method != "POST" { | |
400 | t.Error("expected POST") | |
401 | } | |
402 | w.Header().Set("Content-Type", "application/json") | |
403 | json.NewEncoder(w).Encode(PrimaryIPAssignResult{ | |
404 | Action: schema.Action{ID: 1}, | |
405 | }) | |
406 | }) | |
407 | ||
408 | ctx := context.Background() | |
409 | ||
410 | action, resp, err := env.Client.PrimaryIP.Unassign(ctx, 1) | |
411 | assert.NoError(t, err) | |
412 | assert.NotNil(t, resp, "no response returned") | |
413 | assert.NotNil(t, action, "no action returned") | |
414 | }) | |
415 | t.Run("ChangeDNSPtr", func(t *testing.T) { | |
416 | env := newTestEnv() | |
417 | defer env.Teardown() | |
418 | ||
419 | env.Mux.HandleFunc("/primary_ips/1/actions/change_dns_ptr", func(w http.ResponseWriter, r *http.Request) { | |
420 | var reqBody PrimaryIPChangeDNSPtrOpts | |
421 | if r.Method != "POST" { | |
422 | t.Error("expected POST") | |
423 | } | |
424 | w.Header().Set("Content-Type", "application/json") | |
425 | expectedReqBody := PrimaryIPChangeDNSPtrOpts{ | |
426 | ID: 1, | |
427 | } | |
428 | if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { | |
429 | t.Fatal(err) | |
430 | } | |
431 | if !cmp.Equal(expectedReqBody, reqBody) { | |
432 | t.Log(cmp.Diff(expectedReqBody, reqBody)) | |
433 | t.Error("unexpected request body") | |
434 | } | |
435 | json.NewEncoder(w).Encode(PrimaryIPChangeDNSPtrResult{ | |
436 | Action: schema.Action{ID: 1}, | |
437 | }) | |
438 | }) | |
439 | ||
440 | ctx := context.Background() | |
441 | opts := PrimaryIPChangeDNSPtrOpts{ | |
442 | ID: 1, | |
443 | } | |
444 | ||
445 | action, resp, err := env.Client.PrimaryIP.ChangeDNSPtr(ctx, opts) | |
446 | assert.NoError(t, err) | |
447 | assert.NotNil(t, resp, "no response returned") | |
448 | assert.NotNil(t, action, "no action returned") | |
449 | }) | |
450 | t.Run("ChangeProtection", func(t *testing.T) { | |
451 | env := newTestEnv() | |
452 | defer env.Teardown() | |
453 | ||
454 | env.Mux.HandleFunc("/primary_ips/1/actions/change_protection", func(w http.ResponseWriter, r *http.Request) { | |
455 | var reqBody PrimaryIPChangeProtectionOpts | |
456 | if r.Method != "POST" { | |
457 | t.Error("expected POST") | |
458 | } | |
459 | w.Header().Set("Content-Type", "application/json") | |
460 | expectedReqBody := PrimaryIPChangeProtectionOpts{ | |
461 | ID: 1, | |
462 | Delete: true, | |
463 | } | |
464 | if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { | |
465 | t.Fatal(err) | |
466 | } | |
467 | if !cmp.Equal(expectedReqBody, reqBody) { | |
468 | t.Log(cmp.Diff(expectedReqBody, reqBody)) | |
469 | t.Error("unexpected request body") | |
470 | } | |
471 | json.NewEncoder(w).Encode(PrimaryIPChangeProtectionResult{ | |
472 | Action: schema.Action{ID: 1}, | |
473 | }) | |
474 | }) | |
475 | ||
476 | ctx := context.Background() | |
477 | opts := PrimaryIPChangeProtectionOpts{ | |
478 | ID: 1, | |
479 | Delete: true, | |
480 | } | |
481 | ||
482 | action, resp, err := env.Client.PrimaryIP.ChangeProtection(ctx, opts) | |
483 | assert.NoError(t, err) | |
484 | assert.NotNil(t, resp, "no response returned") | |
485 | assert.NotNil(t, action, "no action returned") | |
486 | }) | |
487 | } |
6 | 6 | Image PricingImage `json:"image"` |
7 | 7 | FloatingIP PricingFloatingIP `json:"floating_ip"` |
8 | 8 | FloatingIPs []PricingFloatingIPType `json:"floating_ips"` |
9 | PrimaryIPs []PricingPrimaryIP `json:"primary_ips"` | |
9 | 10 | Traffic PricingTraffic `json:"traffic"` |
10 | 11 | ServerBackup PricingServerBackup `json:"server_backup"` |
11 | 12 | ServerTypes []PricingServerType `json:"server_types"` |
91 | 92 | type PricingGetResponse struct { |
92 | 93 | Pricing Pricing `json:"pricing"` |
93 | 94 | } |
95 | ||
96 | // PricingPrimaryIPTypePrice defines the schema of pricing information for a primary IP | |
97 | // type at a datacenter. | |
98 | type PricingPrimaryIPTypePrice struct { | |
99 | Datacenter string `json:"datacenter"` | |
100 | PriceHourly Price `json:"price_hourly"` | |
101 | PriceMonthly Price `json:"price_monthly"` | |
102 | } | |
103 | ||
104 | // PricingPrimaryIP define the schema of pricing information for a primary IP at a datacenter | |
105 | type PricingPrimaryIP struct { | |
106 | Type string `json:"type"` | |
107 | Prices []PricingPrimaryIPTypePrice `json:"prices"` | |
108 | } |
0 | package schema | |
1 | ||
2 | import "time" | |
3 | ||
4 | // PrimaryIP defines a Primary IP | |
5 | type PrimaryIP struct { | |
6 | ID int `json:"id"` | |
7 | IP string `json:"ip"` | |
8 | Labels map[string]string `json:"labels"` | |
9 | Name string `json:"name"` | |
10 | Type string `json:"type"` | |
11 | Protection PrimaryIPProtection `json:"protection"` | |
12 | DNSPtr []PrimaryIPDNSPTR `json:"dns_ptr"` | |
13 | AssigneeID int `json:"assignee_id"` | |
14 | AssigneeType string `json:"assignee_type"` | |
15 | AutoDelete bool `json:"auto_delete"` | |
16 | Blocked bool `json:"blocked"` | |
17 | Created time.Time `json:"created"` | |
18 | Datacenter Datacenter `json:"datacenter"` | |
19 | } | |
20 | ||
21 | // PrimaryIPProtection represents the protection level of a Primary IP. | |
22 | type PrimaryIPProtection struct { | |
23 | Delete bool `json:"delete"` | |
24 | } | |
25 | ||
26 | // PrimaryIPDNSPTR contains reverse DNS information for a | |
27 | // IPv4 or IPv6 Primary IP. | |
28 | type PrimaryIPDNSPTR struct { | |
29 | DNSPtr string `json:"dns_ptr"` | |
30 | IP string `json:"ip"` | |
31 | } | |
32 | ||
33 | // PrimaryIPCreateResponse defines the schema of the response | |
34 | // when creating a Primary IP. | |
35 | type PrimaryIPCreateResponse struct { | |
36 | PrimaryIP PrimaryIP `json:"primary_ip"` | |
37 | Action *Action `json:"action"` | |
38 | } | |
39 | ||
40 | // PrimaryIPGetResult defines the response when retrieving a single Primary IP. | |
41 | type PrimaryIPGetResult struct { | |
42 | PrimaryIP PrimaryIP `json:"primary_ip"` | |
43 | } | |
44 | ||
45 | // PrimaryIPListResult defines the response when listing Primary IPs. | |
46 | type PrimaryIPListResult struct { | |
47 | PrimaryIPs []PrimaryIP `json:"primary_ips"` | |
48 | } |
44 | 44 | // ServerPublicNetIPv4 defines the schema of a server's public |
45 | 45 | // network information for an IPv4. |
46 | 46 | type ServerPublicNetIPv4 struct { |
47 | ID int `json:"id"` | |
47 | 48 | IP string `json:"ip"` |
48 | 49 | Blocked bool `json:"blocked"` |
49 | 50 | DNSPtr string `json:"dns_ptr"` |
52 | 53 | // ServerPublicNetIPv6 defines the schema of a server's public |
53 | 54 | // network information for an IPv6. |
54 | 55 | type ServerPublicNetIPv6 struct { |
56 | ID int `json:"id"` | |
55 | 57 | IP string `json:"ip"` |
56 | 58 | Blocked bool `json:"blocked"` |
57 | 59 | DNSPtr []ServerPublicNetIPv6DNSPtr `json:"dns_ptr"` |
108 | 110 | Networks []int `json:"networks,omitempty"` |
109 | 111 | Firewalls []ServerCreateFirewalls `json:"firewalls,omitempty"` |
110 | 112 | PlacementGroup int `json:"placement_group,omitempty"` |
111 | } | |
112 | ||
113 | // ServerCreateFirewall defines which Firewalls to apply when creating a Server. | |
113 | PublicNet *ServerCreatePublicNet `json:"public_net,omitempty"` | |
114 | } | |
115 | ||
116 | // ServerCreatePublicNet defines the public network configuration of a server. | |
117 | type ServerCreatePublicNet struct { | |
118 | EnableIPv4 bool `json:"enable_ipv4"` | |
119 | EnableIPv6 bool `json:"enable_ipv6"` | |
120 | IPv4ID int `json:"ipv4,omitempty"` | |
121 | IPv6ID int `json:"ipv6,omitempty"` | |
122 | } | |
123 | ||
124 | // ServerCreateFirewalls defines which Firewalls to apply when creating a Server. | |
114 | 125 | type ServerCreateFirewalls struct { |
115 | 126 | Firewall int `json:"firewall"` |
116 | 127 | } |
66 | 66 | f.Server = &Server{ID: *s.Server} |
67 | 67 | } |
68 | 68 | if f.Type == FloatingIPTypeIPv4 { |
69 | f.IP = net.ParseIP(s.IP) | |
70 | } else { | |
71 | f.IP, f.Network, _ = net.ParseCIDR(s.IP) | |
72 | } | |
73 | f.DNSPtr = map[string]string{} | |
74 | for _, entry := range s.DNSPtr { | |
75 | f.DNSPtr[entry.IP] = entry.DNSPtr | |
76 | } | |
77 | f.Labels = map[string]string{} | |
78 | for key, value := range s.Labels { | |
79 | f.Labels[key] = value | |
80 | } | |
81 | return f | |
82 | } | |
83 | ||
84 | // PrimaryIPFromSchema converts a schema.PrimaryIP to a PrimaryIP. | |
85 | func PrimaryIPFromSchema(s schema.PrimaryIP) *PrimaryIP { | |
86 | f := &PrimaryIP{ | |
87 | ID: s.ID, | |
88 | Type: PrimaryIPType(s.Type), | |
89 | AutoDelete: s.AutoDelete, | |
90 | ||
91 | Created: s.Created, | |
92 | Blocked: s.Blocked, | |
93 | Protection: PrimaryIPProtection{ | |
94 | Delete: s.Protection.Delete, | |
95 | }, | |
96 | Name: s.Name, | |
97 | AssigneeType: s.AssigneeType, | |
98 | AssigneeID: s.AssigneeID, | |
99 | Datacenter: DatacenterFromSchema(s.Datacenter), | |
100 | } | |
101 | ||
102 | if f.Type == PrimaryIPTypeIPv4 { | |
69 | 103 | f.IP = net.ParseIP(s.IP) |
70 | 104 | } else { |
71 | 105 | f.IP, f.Network, _ = net.ParseCIDR(s.IP) |
200 | 234 | // a ServerPublicNetIPv4. |
201 | 235 | func ServerPublicNetIPv4FromSchema(s schema.ServerPublicNetIPv4) ServerPublicNetIPv4 { |
202 | 236 | return ServerPublicNetIPv4{ |
237 | ID: s.ID, | |
203 | 238 | IP: net.ParseIP(s.IP), |
204 | 239 | Blocked: s.Blocked, |
205 | 240 | DNSPtr: s.DNSPtr, |
210 | 245 | // a ServerPublicNetIPv6. |
211 | 246 | func ServerPublicNetIPv6FromSchema(s schema.ServerPublicNetIPv6) ServerPublicNetIPv6 { |
212 | 247 | ipv6 := ServerPublicNetIPv6{ |
248 | ID: s.ID, | |
213 | 249 | Blocked: s.Blocked, |
214 | 250 | DNSPtr: map[string]string{}, |
215 | 251 | } |
679 | 715 | pricings = append(pricings, p) |
680 | 716 | } |
681 | 717 | p.FloatingIPs = append(p.FloatingIPs, FloatingIPTypePricing{Type: FloatingIPType(floatingIPType.Type), Pricings: pricings}) |
718 | } | |
719 | for _, primaryIPType := range s.PrimaryIPs { | |
720 | var pricings []PrimaryIPTypePricing | |
721 | for _, price := range primaryIPType.Prices { | |
722 | p := PrimaryIPTypePricing{ | |
723 | Datacenter: price.Datacenter, | |
724 | Monthly: PrimaryIPPrice{ | |
725 | Net: price.PriceMonthly.Net, | |
726 | Gross: price.PriceMonthly.Gross, | |
727 | }, | |
728 | Hourly: PrimaryIPPrice{ | |
729 | Net: price.PriceHourly.Net, | |
730 | Gross: price.PriceHourly.Gross, | |
731 | }, | |
732 | } | |
733 | pricings = append(pricings, p) | |
734 | } | |
735 | p.PrimaryIPs = append(p.PrimaryIPs, PrimaryIPPricing{Type: primaryIPType.Type, Pricings: pricings}) | |
682 | 736 | } |
683 | 737 | for _, serverType := range s.ServerTypes { |
684 | 738 | var pricings []ServerTypeLocationPricing |
224 | 224 | }) |
225 | 225 | } |
226 | 226 | |
227 | func TestPrimaryIPFromSchema(t *testing.T) { | |
228 | t.Run("IPv6", func(t *testing.T) { | |
229 | data := []byte(`{ | |
230 | "assignee_id": 17, | |
231 | "assignee_type": "server", | |
232 | "auto_delete": true, | |
233 | "blocked": true, | |
234 | "created": "2017-08-16T17:29:14+00:00", | |
235 | "datacenter": { | |
236 | "description": "Falkenstein DC Park 8", | |
237 | "id": 42, | |
238 | "location": { | |
239 | "city": "Falkenstein", | |
240 | "country": "DE", | |
241 | "description": "Falkenstein DC Park 1", | |
242 | "id": 1, | |
243 | "latitude": 50.47612, | |
244 | "longitude": 12.370071, | |
245 | "name": "fsn1", | |
246 | "network_zone": "eu-central" | |
247 | }, | |
248 | "name": "fsn1-dc8", | |
249 | "server_types": { | |
250 | "available": [], | |
251 | "available_for_migration": [], | |
252 | "supported": [] | |
253 | } | |
254 | }, | |
255 | "dns_ptr": [ | |
256 | { | |
257 | "dns_ptr": "server.example.com", | |
258 | "ip": "fe80::" | |
259 | } | |
260 | ], | |
261 | "id": 4711, | |
262 | "ip": "fe80::/64", | |
263 | "labels": { | |
264 | "key": "value", | |
265 | "key2": "value2" | |
266 | }, | |
267 | "name": "Web Frontend", | |
268 | "protection": { | |
269 | "delete": true | |
270 | }, | |
271 | "type": "ipv6" | |
272 | }`) | |
273 | ||
274 | var s schema.PrimaryIP | |
275 | if err := json.Unmarshal(data, &s); err != nil { | |
276 | t.Fatal(err) | |
277 | } | |
278 | primaryIP := PrimaryIPFromSchema(s) | |
279 | ||
280 | if primaryIP.ID != 4711 { | |
281 | t.Errorf("unexpected ID: %v", primaryIP.ID) | |
282 | } | |
283 | if !primaryIP.Blocked { | |
284 | t.Errorf("unexpected value for Blocked: %v", primaryIP.Blocked) | |
285 | } | |
286 | if !primaryIP.AutoDelete { | |
287 | t.Errorf("unexpected value for AutoDelete: %v", primaryIP.AutoDelete) | |
288 | } | |
289 | if primaryIP.Name != "Web Frontend" { | |
290 | t.Errorf("unexpected name: %v", primaryIP.Name) | |
291 | } | |
292 | ||
293 | if primaryIP.IP.String() != "fe80::" { | |
294 | t.Errorf("unexpected IP: %v", primaryIP.IP) | |
295 | } | |
296 | if primaryIP.Type != PrimaryIPTypeIPv6 { | |
297 | t.Errorf("unexpected Type: %v", primaryIP.Type) | |
298 | } | |
299 | if primaryIP.AssigneeType != "server" { | |
300 | t.Errorf("unexpected AssigneeType: %v", primaryIP.AssigneeType) | |
301 | } | |
302 | if primaryIP.AssigneeID != 17 { | |
303 | t.Errorf("unexpected AssigneeID: %v", primaryIP.AssigneeID) | |
304 | } | |
305 | dnsPTR, err := primaryIP.GetDNSPtrForIP(primaryIP.IP) | |
306 | if err != nil { | |
307 | t.Fatal(err) | |
308 | } | |
309 | if primaryIP.DNSPtr == nil || dnsPTR == "" { | |
310 | t.Errorf("unexpected DNS ptr: %v", primaryIP.DNSPtr) | |
311 | } | |
312 | if primaryIP.Datacenter.Name != "fsn1-dc8" { | |
313 | t.Errorf("unexpected datacenter: %v", primaryIP.Datacenter) | |
314 | } | |
315 | if !primaryIP.Protection.Delete { | |
316 | t.Errorf("unexpected Protection.Delete: %v", primaryIP.Protection.Delete) | |
317 | } | |
318 | if primaryIP.Labels["key"] != "value" || primaryIP.Labels["key2"] != "value2" { | |
319 | t.Errorf("unexpected Labels: %v", primaryIP.Labels) | |
320 | } | |
321 | if !primaryIP.Created.Equal(time.Date(2017, 8, 16, 17, 29, 14, 0, time.UTC)) { | |
322 | t.Errorf("unexpected created date: %v", primaryIP.Created) | |
323 | } | |
324 | }) | |
325 | t.Run("IPv4", func(t *testing.T) { | |
326 | data := []byte(`{ | |
327 | "assignee_id": 17, | |
328 | "assignee_type": "server", | |
329 | "auto_delete": true, | |
330 | "blocked": true, | |
331 | "created": "2017-08-16T17:29:14+00:00", | |
332 | "datacenter": { | |
333 | "description": "Falkenstein DC Park 8", | |
334 | "id": 42, | |
335 | "location": { | |
336 | "city": "Falkenstein", | |
337 | "country": "DE", | |
338 | "description": "Falkenstein DC Park 1", | |
339 | "id": 1, | |
340 | "latitude": 50.47612, | |
341 | "longitude": 12.370071, | |
342 | "name": "fsn1", | |
343 | "network_zone": "eu-central" | |
344 | }, | |
345 | "name": "fsn1-dc8", | |
346 | "server_types": { | |
347 | "available": [], | |
348 | "available_for_migration": [], | |
349 | "supported": [] | |
350 | } | |
351 | }, | |
352 | "dns_ptr": [ | |
353 | { | |
354 | "dns_ptr": "server.example.com", | |
355 | "ip": "127.0.0.1" | |
356 | } | |
357 | ], | |
358 | "id": 4711, | |
359 | "ip": "127.0.0.1", | |
360 | "labels": { | |
361 | "key": "value", | |
362 | "key2": "value2" | |
363 | }, | |
364 | "name": "Web Frontend", | |
365 | "protection": { | |
366 | "delete": true | |
367 | }, | |
368 | "type": "ipv4" | |
369 | }`) | |
370 | ||
371 | var s schema.PrimaryIP | |
372 | if err := json.Unmarshal(data, &s); err != nil { | |
373 | t.Fatal(err) | |
374 | } | |
375 | primaryIP := PrimaryIPFromSchema(s) | |
376 | ||
377 | if primaryIP.ID != 4711 { | |
378 | t.Errorf("unexpected ID: %v", primaryIP.ID) | |
379 | } | |
380 | if !primaryIP.Blocked { | |
381 | t.Errorf("unexpected value for Blocked: %v", primaryIP.Blocked) | |
382 | } | |
383 | if !primaryIP.AutoDelete { | |
384 | t.Errorf("unexpected value for AutoDelete: %v", primaryIP.AutoDelete) | |
385 | } | |
386 | if primaryIP.Name != "Web Frontend" { | |
387 | t.Errorf("unexpected name: %v", primaryIP.Name) | |
388 | } | |
389 | ||
390 | if primaryIP.IP.String() != "127.0.0.1" { | |
391 | t.Errorf("unexpected IP: %v", primaryIP.IP) | |
392 | } | |
393 | if primaryIP.Type != PrimaryIPTypeIPv4 { | |
394 | t.Errorf("unexpected Type: %v", primaryIP.Type) | |
395 | } | |
396 | if primaryIP.AssigneeType != "server" { | |
397 | t.Errorf("unexpected AssigneeType: %v", primaryIP.AssigneeType) | |
398 | } | |
399 | if primaryIP.AssigneeID != 17 { | |
400 | t.Errorf("unexpected AssigneeID: %v", primaryIP.AssigneeID) | |
401 | } | |
402 | dnsPTR, err := primaryIP.GetDNSPtrForIP(primaryIP.IP) | |
403 | if err != nil { | |
404 | t.Fatal(err) | |
405 | } | |
406 | if primaryIP.DNSPtr == nil || dnsPTR == "" { | |
407 | t.Errorf("unexpected DNS ptr: %v", primaryIP.DNSPtr) | |
408 | } | |
409 | if primaryIP.Datacenter.Name != "fsn1-dc8" { | |
410 | t.Errorf("unexpected datacenter: %v", primaryIP.Datacenter) | |
411 | } | |
412 | if !primaryIP.Protection.Delete { | |
413 | t.Errorf("unexpected Protection.Delete: %v", primaryIP.Protection.Delete) | |
414 | } | |
415 | if primaryIP.Labels["key"] != "value" || primaryIP.Labels["key2"] != "value2" { | |
416 | t.Errorf("unexpected Labels: %v", primaryIP.Labels) | |
417 | } | |
418 | if !primaryIP.Created.Equal(time.Date(2017, 8, 16, 17, 29, 14, 0, time.UTC)) { | |
419 | t.Errorf("unexpected created date: %v", primaryIP.Created) | |
420 | } | |
421 | }) | |
422 | } | |
423 | ||
227 | 424 | func TestISOFromSchema(t *testing.T) { |
228 | 425 | data := []byte(`{ |
229 | 426 | "id": 4711, |
358 | 555 | "status": "running", |
359 | 556 | "created": "2017-08-16T17:29:14+00:00", |
360 | 557 | "public_net": { |
361 | "ipv4": { | |
362 | "ip": "1.2.3.4", | |
363 | "blocked": false, | |
364 | "dns_ptr": "server01.example.com" | |
365 | }, | |
558 | "ipv4": null, | |
366 | 559 | "ipv6": { |
367 | 560 | "ip": "2a01:4f8:1c11:3400::/64", |
368 | 561 | "blocked": false, |
474 | 667 | if !server.Created.Equal(time.Date(2017, 8, 16, 17, 29, 14, 0, time.UTC)) { |
475 | 668 | t.Errorf("unexpected created date: %v", server.Created) |
476 | 669 | } |
477 | if server.PublicNet.IPv4.IP.String() != "1.2.3.4" { | |
478 | t.Errorf("unexpected public net IPv4 IP: %v", server.PublicNet.IPv4.IP) | |
670 | if !server.PublicNet.IPv4.IsUnspecified() { | |
671 | t.Errorf("unexpected public net IPv4: %v", server.PublicNet.IPv4) | |
672 | } | |
673 | if server.PublicNet.IPv6.IP.String() != "2a01:4f8:1c11:3400::" { | |
674 | t.Errorf("unexpected public net IPv6 IP: %v", server.PublicNet.IPv6.IP) | |
479 | 675 | } |
480 | 676 | if server.ServerType.ID != 2 { |
481 | 677 | t.Errorf("unexpected server type ID: %v", server.ServerType.ID) |
576 | 772 | func TestServerPublicNetFromSchema(t *testing.T) { |
577 | 773 | data := []byte(`{ |
578 | 774 | "ipv4": { |
775 | "id": 1, | |
579 | 776 | "ip": "1.2.3.4", |
580 | 777 | "blocked": false, |
581 | 778 | "dns_ptr": "server.example.com" |
582 | 779 | }, |
583 | 780 | "ipv6": { |
781 | "id": 2, | |
584 | 782 | "ip": "2a01:4f8:1c19:1403::/64", |
585 | 783 | "blocked": false, |
586 | 784 | "dns_ptr": [] |
599 | 797 | t.Fatal(err) |
600 | 798 | } |
601 | 799 | publicNet := ServerPublicNetFromSchema(s) |
602 | ||
800 | if publicNet.IPv4.ID != 1 { | |
801 | t.Errorf("unexpected IPv4 ID: %v", publicNet.IPv4.ID) | |
802 | } | |
603 | 803 | if publicNet.IPv4.IP.String() != "1.2.3.4" { |
604 | 804 | t.Errorf("unexpected IPv4 IP: %v", publicNet.IPv4.IP) |
805 | } | |
806 | if publicNet.IPv6.ID != 2 { | |
807 | t.Errorf("unexpected IPv6 ID: %v", publicNet.IPv6.ID) | |
605 | 808 | } |
606 | 809 | if publicNet.IPv6.Network.String() != "2a01:4f8:1c19:1403::/64" { |
607 | 810 | t.Errorf("unexpected IPv6 IP: %v", publicNet.IPv6.IP) |
1836 | 2039 | "type": "ipv4" |
1837 | 2040 | } |
1838 | 2041 | ], |
2042 | "primary_ips": [ | |
2043 | { | |
2044 | "prices": [ | |
2045 | { | |
2046 | "datacenter": "fsn1-dc8", | |
2047 | "price_hourly": { | |
2048 | "gross": "1.1900000000000000", | |
2049 | "net": "1.0000000000" | |
2050 | }, | |
2051 | "price_monthly": { | |
2052 | "gross": "1.1900000000000000", | |
2053 | "net": "1.0000000000" | |
2054 | } | |
2055 | } | |
2056 | ], | |
2057 | "type": "ipv4" | |
2058 | } | |
2059 | ], | |
1839 | 2060 | "traffic": { |
1840 | 2061 | "price_per_tb": { |
1841 | 2062 | "net": "1", |
1948 | 2169 | } |
1949 | 2170 | if p.Pricings[0].Monthly.Gross != "1.19" { |
1950 | 2171 | t.Errorf("unexpected Monthly.Gross: %v", p.Pricings[0].Monthly.Gross) |
2172 | } | |
2173 | } | |
2174 | } | |
2175 | ||
2176 | if len(pricing.PrimaryIPs) != 1 { | |
2177 | t.Errorf("unexpected number of Primary IPs: %d", len(pricing.PrimaryIPs)) | |
2178 | } else { | |
2179 | ip := pricing.PrimaryIPs[0] | |
2180 | ||
2181 | if ip.Type != "ipv4" { | |
2182 | t.Errorf("unexpected .Type: %s", ip.Type) | |
2183 | } | |
2184 | if len(ip.Pricings) != 1 { | |
2185 | t.Errorf("unexpected number of prices: %d", len(ip.Pricings)) | |
2186 | } else { | |
2187 | if ip.Pricings[0].Datacenter != "fsn1-dc8" { | |
2188 | t.Errorf("unexpected Datacenter: %v", ip.Pricings[0].Datacenter) | |
2189 | } | |
2190 | if ip.Pricings[0].Monthly.Net != "1.0000000000" { | |
2191 | t.Errorf("unexpected Monthly.Net: %v", ip.Pricings[0].Monthly.Net) | |
2192 | } | |
2193 | if ip.Pricings[0].Monthly.Gross != "1.1900000000000000" { | |
2194 | t.Errorf("unexpected Monthly.Gross: %v", ip.Pricings[0].Monthly.Gross) | |
2195 | } | |
2196 | if ip.Pricings[0].Hourly.Net != "1.0000000000" { | |
2197 | t.Errorf("unexpected Hourly.Net: %v", ip.Pricings[0].Hourly.Net) | |
2198 | } | |
2199 | if ip.Pricings[0].Hourly.Gross != "1.1900000000000000" { | |
2200 | t.Errorf("unexpected Hourly.Gross: %v", ip.Pricings[0].Hourly.Gross) | |
1951 | 2201 | } |
1952 | 2202 | } |
1953 | 2203 | } |
97 | 97 | |
98 | 98 | // ServerPublicNetIPv4 represents a server's public IPv4 address. |
99 | 99 | type ServerPublicNetIPv4 struct { |
100 | ID int | |
100 | 101 | IP net.IP |
101 | 102 | Blocked bool |
102 | 103 | DNSPtr string |
103 | 104 | } |
104 | 105 | |
106 | func (n *ServerPublicNetIPv4) IsUnspecified() bool { | |
107 | return n.IP == nil || n.IP.Equal(net.IPv4zero) | |
108 | } | |
109 | ||
105 | 110 | // ServerPublicNetIPv6 represents a Server's public IPv6 network and address. |
106 | 111 | type ServerPublicNetIPv6 struct { |
112 | ID int | |
107 | 113 | IP net.IP |
108 | 114 | Network *net.IPNet |
109 | 115 | Blocked bool |
110 | 116 | DNSPtr map[string]string |
117 | } | |
118 | ||
119 | func (n *ServerPublicNetIPv6) IsUnspecified() bool { | |
120 | return n.IP == nil || n.IP.Equal(net.IPv6unspecified) | |
111 | 121 | } |
112 | 122 | |
113 | 123 | // ServerPrivateNet defines the schema of a Server's private network information. |
119 | 129 | } |
120 | 130 | |
121 | 131 | // DNSPtrForIP returns the reverse dns pointer of the ip address. |
122 | func (s *ServerPublicNetIPv6) DNSPtrForIP(ip net.IP) string { | |
123 | return s.DNSPtr[ip.String()] | |
132 | func (n *ServerPublicNetIPv6) DNSPtrForIP(ip net.IP) string { | |
133 | return n.DNSPtr[ip.String()] | |
124 | 134 | } |
125 | 135 | |
126 | 136 | // ServerFirewallStatus represents a Firewall and its status on a Server's |
307 | 317 | Networks []*Network |
308 | 318 | Firewalls []*ServerCreateFirewall |
309 | 319 | PlacementGroup *PlacementGroup |
320 | PublicNet *ServerCreatePublicNet | |
321 | } | |
322 | ||
323 | type ServerCreatePublicNet struct { | |
324 | EnableIPv4 bool | |
325 | EnableIPv6 bool | |
326 | IPv4 *PrimaryIP | |
327 | IPv6 *PrimaryIP | |
310 | 328 | } |
311 | 329 | |
312 | 330 | // ServerCreateFirewall defines which Firewalls to apply when creating a Server. |
327 | 345 | } |
328 | 346 | if o.Location != nil && o.Datacenter != nil { |
329 | 347 | return errors.New("location and datacenter are mutually exclusive") |
348 | } | |
349 | if o.PublicNet != nil { | |
350 | if !o.PublicNet.EnableIPv4 && !o.PublicNet.EnableIPv6 && len(o.Networks) == 0 { | |
351 | return errors.New("missing networks when EnableIPv4 and EnableIPv6 is false") | |
352 | } | |
330 | 353 | } |
331 | 354 | return nil |
332 | 355 | } |
376 | 399 | reqBody.Firewalls = append(reqBody.Firewalls, schema.ServerCreateFirewalls{ |
377 | 400 | Firewall: firewall.Firewall.ID, |
378 | 401 | }) |
402 | } | |
403 | ||
404 | if opts.PublicNet != nil { | |
405 | reqBody.PublicNet = &schema.ServerCreatePublicNet{ | |
406 | EnableIPv4: opts.PublicNet.EnableIPv4, | |
407 | EnableIPv6: opts.PublicNet.EnableIPv6, | |
408 | } | |
409 | if opts.PublicNet.IPv4 != nil { | |
410 | reqBody.PublicNet.IPv4ID = opts.PublicNet.IPv4.ID | |
411 | } | |
412 | if opts.PublicNet.IPv6 != nil { | |
413 | reqBody.PublicNet.IPv6ID = opts.PublicNet.IPv6.ID | |
414 | } | |
379 | 415 | } |
380 | 416 | if opts.Location != nil { |
381 | 417 | if opts.Location.ID != 0 { |
445 | 445 | {ID: 1}, |
446 | 446 | {ID: 2}, |
447 | 447 | }, |
448 | }) | |
449 | if err != nil { | |
450 | t.Fatal(err) | |
451 | } | |
452 | if result.Server == nil { | |
453 | t.Fatal("no server") | |
454 | } | |
455 | if result.Server.ID != 1 { | |
456 | t.Errorf("unexpected server ID: %v", result.Server.ID) | |
457 | } | |
458 | if len(result.NextActions) != 1 || result.NextActions[0].ID != 2 { | |
459 | t.Errorf("unexpected next actions: %v", result.NextActions) | |
460 | } | |
461 | } | |
462 | ||
463 | func TestServersCreateWithPrivateNetworkOnly(t *testing.T) { | |
464 | env := newTestEnv() | |
465 | defer env.Teardown() | |
466 | ||
467 | env.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { | |
468 | var reqBody schema.ServerCreateRequest | |
469 | if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { | |
470 | t.Fatal(err) | |
471 | } | |
472 | if len(reqBody.Networks) != 2 || reqBody.Networks[0] != 1 || reqBody.Networks[1] != 2 { | |
473 | t.Errorf("unexpected Networks: %v", reqBody.Networks) | |
474 | } | |
475 | if reqBody.PublicNet.EnableIPv4 != false { | |
476 | t.Errorf("unexpected PublicNet.EnableIPv4: %v", reqBody.PublicNet.EnableIPv4) | |
477 | } | |
478 | if reqBody.PublicNet.EnableIPv6 != false { | |
479 | t.Errorf("unexpected PublicNet.EnableIPv6: %v", reqBody.PublicNet.EnableIPv6) | |
480 | } | |
481 | if reqBody.PublicNet.IPv4ID != 0 { | |
482 | t.Errorf("unexpected PublicNet.IPv4: %v", reqBody.PublicNet.IPv4ID) | |
483 | } | |
484 | if reqBody.PublicNet.IPv6ID != 0 { | |
485 | t.Errorf("unexpected PublicNet.IPv6: %v", reqBody.PublicNet.IPv6ID) | |
486 | } | |
487 | json.NewEncoder(w).Encode(schema.ServerCreateResponse{ | |
488 | Server: schema.Server{ | |
489 | ID: 1, | |
490 | }, | |
491 | NextActions: []schema.Action{ | |
492 | {ID: 2}, | |
493 | }, | |
494 | }) | |
495 | }) | |
496 | ||
497 | ctx := context.Background() | |
498 | result, _, err := env.Client.Server.Create(ctx, ServerCreateOpts{ | |
499 | Name: "test", | |
500 | ServerType: &ServerType{ID: 1}, | |
501 | Image: &Image{ID: 2}, | |
502 | Networks: []*Network{ | |
503 | {ID: 1}, | |
504 | {ID: 2}, | |
505 | }, | |
506 | PublicNet: &ServerCreatePublicNet{ | |
507 | EnableIPv4: false, | |
508 | EnableIPv6: false, | |
509 | }, | |
510 | }) | |
511 | if err != nil { | |
512 | t.Fatal(err) | |
513 | } | |
514 | if result.Server == nil { | |
515 | t.Fatal("no server") | |
516 | } | |
517 | if result.Server.ID != 1 { | |
518 | t.Errorf("unexpected server ID: %v", result.Server.ID) | |
519 | } | |
520 | if len(result.NextActions) != 1 || result.NextActions[0].ID != 2 { | |
521 | t.Errorf("unexpected next actions: %v", result.NextActions) | |
522 | } | |
523 | } | |
524 | ||
525 | func TestServersCreateWithIPv6Only(t *testing.T) { | |
526 | env := newTestEnv() | |
527 | defer env.Teardown() | |
528 | ||
529 | env.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { | |
530 | var reqBody schema.ServerCreateRequest | |
531 | if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { | |
532 | t.Fatal(err) | |
533 | } | |
534 | if reqBody.PublicNet.EnableIPv4 != false { | |
535 | t.Errorf("unexpected PublicNet.EnableIPv4: %v", reqBody.PublicNet.EnableIPv4) | |
536 | } | |
537 | if reqBody.PublicNet.EnableIPv6 != true { | |
538 | t.Errorf("unexpected PublicNet.EnableIPv6: %v", reqBody.PublicNet.EnableIPv6) | |
539 | } | |
540 | json.NewEncoder(w).Encode(schema.ServerCreateResponse{ | |
541 | Server: schema.Server{ | |
542 | ID: 1, | |
543 | }, | |
544 | NextActions: []schema.Action{ | |
545 | {ID: 2}, | |
546 | }, | |
547 | }) | |
548 | }) | |
549 | ||
550 | ctx := context.Background() | |
551 | result, _, err := env.Client.Server.Create(ctx, ServerCreateOpts{ | |
552 | Name: "test", | |
553 | ServerType: &ServerType{ID: 1}, | |
554 | Image: &Image{ID: 2}, | |
555 | PublicNet: &ServerCreatePublicNet{EnableIPv4: false, EnableIPv6: true}, | |
556 | }) | |
557 | if err != nil { | |
558 | t.Fatal(err) | |
559 | } | |
560 | if result.Server == nil { | |
561 | t.Fatal("no server") | |
562 | } | |
563 | if result.Server.ID != 1 { | |
564 | t.Errorf("unexpected server ID: %v", result.Server.ID) | |
565 | } | |
566 | if len(result.NextActions) != 1 || result.NextActions[0].ID != 2 { | |
567 | t.Errorf("unexpected next actions: %v", result.NextActions) | |
568 | } | |
569 | } | |
570 | ||
571 | func TestServersCreateWithDefaultPublicNet(t *testing.T) { | |
572 | env := newTestEnv() | |
573 | defer env.Teardown() | |
574 | ||
575 | env.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { | |
576 | var reqBody schema.ServerCreateRequest | |
577 | if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { | |
578 | t.Fatal(err) | |
579 | } | |
580 | if reqBody.PublicNet != nil { | |
581 | t.Errorf("unexpected PublicNet: %v", reqBody.PublicNet) | |
582 | } | |
583 | json.NewEncoder(w).Encode(schema.ServerCreateResponse{ | |
584 | Server: schema.Server{ | |
585 | ID: 1, | |
586 | }, | |
587 | NextActions: []schema.Action{ | |
588 | {ID: 2}, | |
589 | }, | |
590 | }) | |
591 | }) | |
592 | ||
593 | ctx := context.Background() | |
594 | result, _, err := env.Client.Server.Create(ctx, ServerCreateOpts{ | |
595 | Name: "test", | |
596 | ServerType: &ServerType{ID: 1}, | |
597 | Image: &Image{ID: 2}, | |
448 | 598 | }) |
449 | 599 | if err != nil { |
450 | 600 | t.Fatal(err) |