Codebase list golang-github-hetznercloud-hcloud-go / 4de881b
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
13 changed file(s) with 1492 addition(s) and 15 deletion(s). Raw diff Collapse all Expand all
1616
1717 test:tests:
1818 stage: test
19 image: golang:1.17
19 image: golang:1.18
2020 script:
2121 - go test -v -race ./...
2222 except:
8181 Volume VolumeClient
8282 PlacementGroup PlacementGroupClient
8383 RDNS RDNSClient
84 PrimaryIP PrimaryIPClient
8485 }
8586
8687 // A ClientOption is used to configure a Client.
186187 client.Firewall = FirewallClient{client: client}
187188 client.PlacementGroup = PlacementGroupClient{client: client}
188189 client.RDNS = RDNSClient{client: client}
190 client.PrimaryIP = PrimaryIPClient{client: client}
189191
190192 return client
191193 }
11 package hcloud
22
33 // Version is the library's version following Semantic Versioning.
4 const Version = "1.34.0"
4 const Version = "1.35.0"
1010 Image ImagePricing
1111 FloatingIP FloatingIPPricing
1212 FloatingIPs []FloatingIPTypePricing
13 PrimaryIPs []PrimaryIPPricing
1314 Traffic TrafficPricing
1415 ServerBackup ServerBackupPricing
1516 ServerTypes []ServerTypePricing
2728 Gross string
2829 }
2930
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
3039 // ImagePricing provides pricing information for imaegs.
3140 type ImagePricing struct {
3241 PerGBMonth Price
4150 type FloatingIPTypePricing struct {
4251 Type FloatingIPType
4352 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
4467 }
4568
4669 // 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 }
66 Image PricingImage `json:"image"`
77 FloatingIP PricingFloatingIP `json:"floating_ip"`
88 FloatingIPs []PricingFloatingIPType `json:"floating_ips"`
9 PrimaryIPs []PricingPrimaryIP `json:"primary_ips"`
910 Traffic PricingTraffic `json:"traffic"`
1011 ServerBackup PricingServerBackup `json:"server_backup"`
1112 ServerTypes []PricingServerType `json:"server_types"`
9192 type PricingGetResponse struct {
9293 Pricing Pricing `json:"pricing"`
9394 }
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 }
4444 // ServerPublicNetIPv4 defines the schema of a server's public
4545 // network information for an IPv4.
4646 type ServerPublicNetIPv4 struct {
47 ID int `json:"id"`
4748 IP string `json:"ip"`
4849 Blocked bool `json:"blocked"`
4950 DNSPtr string `json:"dns_ptr"`
5253 // ServerPublicNetIPv6 defines the schema of a server's public
5354 // network information for an IPv6.
5455 type ServerPublicNetIPv6 struct {
56 ID int `json:"id"`
5557 IP string `json:"ip"`
5658 Blocked bool `json:"blocked"`
5759 DNSPtr []ServerPublicNetIPv6DNSPtr `json:"dns_ptr"`
108110 Networks []int `json:"networks,omitempty"`
109111 Firewalls []ServerCreateFirewalls `json:"firewalls,omitempty"`
110112 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.
114125 type ServerCreateFirewalls struct {
115126 Firewall int `json:"firewall"`
116127 }
6666 f.Server = &Server{ID: *s.Server}
6767 }
6868 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 {
69103 f.IP = net.ParseIP(s.IP)
70104 } else {
71105 f.IP, f.Network, _ = net.ParseCIDR(s.IP)
200234 // a ServerPublicNetIPv4.
201235 func ServerPublicNetIPv4FromSchema(s schema.ServerPublicNetIPv4) ServerPublicNetIPv4 {
202236 return ServerPublicNetIPv4{
237 ID: s.ID,
203238 IP: net.ParseIP(s.IP),
204239 Blocked: s.Blocked,
205240 DNSPtr: s.DNSPtr,
210245 // a ServerPublicNetIPv6.
211246 func ServerPublicNetIPv6FromSchema(s schema.ServerPublicNetIPv6) ServerPublicNetIPv6 {
212247 ipv6 := ServerPublicNetIPv6{
248 ID: s.ID,
213249 Blocked: s.Blocked,
214250 DNSPtr: map[string]string{},
215251 }
679715 pricings = append(pricings, p)
680716 }
681717 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})
682736 }
683737 for _, serverType := range s.ServerTypes {
684738 var pricings []ServerTypeLocationPricing
224224 })
225225 }
226226
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
227424 func TestISOFromSchema(t *testing.T) {
228425 data := []byte(`{
229426 "id": 4711,
358555 "status": "running",
359556 "created": "2017-08-16T17:29:14+00:00",
360557 "public_net": {
361 "ipv4": {
362 "ip": "1.2.3.4",
363 "blocked": false,
364 "dns_ptr": "server01.example.com"
365 },
558 "ipv4": null,
366559 "ipv6": {
367560 "ip": "2a01:4f8:1c11:3400::/64",
368561 "blocked": false,
474667 if !server.Created.Equal(time.Date(2017, 8, 16, 17, 29, 14, 0, time.UTC)) {
475668 t.Errorf("unexpected created date: %v", server.Created)
476669 }
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)
479675 }
480676 if server.ServerType.ID != 2 {
481677 t.Errorf("unexpected server type ID: %v", server.ServerType.ID)
576772 func TestServerPublicNetFromSchema(t *testing.T) {
577773 data := []byte(`{
578774 "ipv4": {
775 "id": 1,
579776 "ip": "1.2.3.4",
580777 "blocked": false,
581778 "dns_ptr": "server.example.com"
582779 },
583780 "ipv6": {
781 "id": 2,
584782 "ip": "2a01:4f8:1c19:1403::/64",
585783 "blocked": false,
586784 "dns_ptr": []
599797 t.Fatal(err)
600798 }
601799 publicNet := ServerPublicNetFromSchema(s)
602
800 if publicNet.IPv4.ID != 1 {
801 t.Errorf("unexpected IPv4 ID: %v", publicNet.IPv4.ID)
802 }
603803 if publicNet.IPv4.IP.String() != "1.2.3.4" {
604804 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)
605808 }
606809 if publicNet.IPv6.Network.String() != "2a01:4f8:1c19:1403::/64" {
607810 t.Errorf("unexpected IPv6 IP: %v", publicNet.IPv6.IP)
18362039 "type": "ipv4"
18372040 }
18382041 ],
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 ],
18392060 "traffic": {
18402061 "price_per_tb": {
18412062 "net": "1",
19482169 }
19492170 if p.Pricings[0].Monthly.Gross != "1.19" {
19502171 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)
19512201 }
19522202 }
19532203 }
9797
9898 // ServerPublicNetIPv4 represents a server's public IPv4 address.
9999 type ServerPublicNetIPv4 struct {
100 ID int
100101 IP net.IP
101102 Blocked bool
102103 DNSPtr string
103104 }
104105
106 func (n *ServerPublicNetIPv4) IsUnspecified() bool {
107 return n.IP == nil || n.IP.Equal(net.IPv4zero)
108 }
109
105110 // ServerPublicNetIPv6 represents a Server's public IPv6 network and address.
106111 type ServerPublicNetIPv6 struct {
112 ID int
107113 IP net.IP
108114 Network *net.IPNet
109115 Blocked bool
110116 DNSPtr map[string]string
117 }
118
119 func (n *ServerPublicNetIPv6) IsUnspecified() bool {
120 return n.IP == nil || n.IP.Equal(net.IPv6unspecified)
111121 }
112122
113123 // ServerPrivateNet defines the schema of a Server's private network information.
119129 }
120130
121131 // 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()]
124134 }
125135
126136 // ServerFirewallStatus represents a Firewall and its status on a Server's
307317 Networks []*Network
308318 Firewalls []*ServerCreateFirewall
309319 PlacementGroup *PlacementGroup
320 PublicNet *ServerCreatePublicNet
321 }
322
323 type ServerCreatePublicNet struct {
324 EnableIPv4 bool
325 EnableIPv6 bool
326 IPv4 *PrimaryIP
327 IPv6 *PrimaryIP
310328 }
311329
312330 // ServerCreateFirewall defines which Firewalls to apply when creating a Server.
327345 }
328346 if o.Location != nil && o.Datacenter != nil {
329347 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 }
330353 }
331354 return nil
332355 }
376399 reqBody.Firewalls = append(reqBody.Firewalls, schema.ServerCreateFirewalls{
377400 Firewall: firewall.Firewall.ID,
378401 })
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 }
379415 }
380416 if opts.Location != nil {
381417 if opts.Location.ID != 0 {
445445 {ID: 1},
446446 {ID: 2},
447447 },
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},
448598 })
449599 if err != nil {
450600 t.Fatal(err)