Skip to content

Commit 4e077c1

Browse files
Monitobremyleone
andcommitted
feat(instance-private-network): Create/Read private network (scaleway#942)
* feat(instance-private-network): Create/Read private network * feat(instance-private-network): makrdown lint * feat(instance-private-network): handle update network * feat(instance-private-network): add setter and getter to private network handler * Update docs/resources/instance_server.md Co-authored-by: Rémy Léone <[email protected]> * feat(instance-pn): remove tags * feat(instance-pn): add check private networks on server * feat(instance-pn): private network handler to private * feat(instance-pn): make creation readably * feat(instance-pn): move handler to helpers Co-authored-by: Rémy Léone <[email protected]>
1 parent 92f7f8f commit 4e077c1

6 files changed

+6252
-2
lines changed

docs/resources/instance_server.md

+34
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,23 @@ resource "scaleway_instance_server" "web" {
110110
}
111111
```
112112

113+
### With private network
114+
115+
```hcl
116+
resource scaleway_vpc_private_network pn01 {
117+
name = "private_network_instance"
118+
}
119+
120+
resource "scaleway_instance_server" "base" {
121+
image = "ubuntu_focal"
122+
type = "DEV1-S"
123+
124+
private_network {
125+
pn_id = scaleway_vpc_private_network.pn01.id
126+
}
127+
}
128+
```
129+
113130
## Arguments Reference
114131

115132
The following arguments are supported:
@@ -167,6 +184,9 @@ attached to the server. Updates to this field will trigger a stop/start of the s
167184
- UTF-8 encoded file content using [file](https://www.terraform.io/docs/configuration/functions/file.html)
168185
- Binary files using [filebase64](https://www.terraform.io/docs/configuration/functions/filebase64.html).
169186

187+
- `private_network` - (Optional) The private network associated with the server.
188+
Use the `pn_id` key to attach a [private_network](https://developers.scaleway.com/en/products/instance/api/#private-nics-a42eea) on your instance.
189+
170190
- `boot_type` - The boot Type of the server. Possible values are: `local`, `bootscript` or `rescue`.
171191

172192
- `bootscript_id` - The ID of the bootscript to use (set boot_type to `bootscript`).
@@ -176,6 +196,20 @@ attached to the server. Updates to this field will trigger a stop/start of the s
176196
- `project_id` - (Defaults to [provider](../index.md#project_id) `project_id`) The ID of the project the server is associated with.
177197

178198

199+
## Private Network
200+
201+
~> **Important:** Updates to `private_network` will recreate a new private network interface.
202+
203+
- `pn_id` - (Required) The private network ID where to connect.
204+
- `mac_address` The private NIC MAC address.
205+
- `status` The private NIC state.
206+
- `zone` - (Defaults to [provider](../index.md#zone) `zone`) The [zone](../guides/regions_and_zones.md#zones) in which the server must be created.
207+
208+
~> **Important:**
209+
210+
- You can only attach an instance in the same [zone](../guides/regions_and_zones.md#zones) as a private network.
211+
- Instance supports maximum 8 different private networks.
212+
179213
## Attributes Reference
180214

181215
In addition to all above arguments, the following attributes are exported:

scaleway/helpers_instance.go

+132
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/dustin/go-humanize"
1010
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1111
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
12+
"github.com/scaleway/scaleway-sdk-go/api/vpc/v1"
1213
"github.com/scaleway/scaleway-sdk-go/scw"
1314
)
1415

@@ -242,3 +243,134 @@ func sanitizeVolumeMap(serverName string, volumes map[string]*instance.VolumeTem
242243

243244
return m
244245
}
246+
247+
func preparePrivateNIC(
248+
ctx context.Context, data interface{},
249+
server *instance.Server, vpcAPI *vpc.API) ([]*instance.CreatePrivateNICRequest, error) {
250+
if data == nil {
251+
return nil, nil
252+
}
253+
254+
var res []*instance.CreatePrivateNICRequest
255+
256+
for _, pn := range data.([]interface{}) {
257+
r := pn.(map[string]interface{})
258+
zonedID, pnExist := r["pn_id"]
259+
privateNetworkID := expandID(zonedID.(string))
260+
if pnExist {
261+
currentPN, err := vpcAPI.GetPrivateNetwork(&vpc.GetPrivateNetworkRequest{
262+
PrivateNetworkID: expandID(privateNetworkID),
263+
Zone: server.Zone,
264+
}, scw.WithContext(ctx))
265+
if err != nil {
266+
return nil, err
267+
}
268+
query := &instance.CreatePrivateNICRequest{
269+
Zone: currentPN.Zone,
270+
ServerID: server.ID,
271+
PrivateNetworkID: currentPN.ID}
272+
res = append(res, query)
273+
}
274+
}
275+
276+
return res, nil
277+
}
278+
279+
type privateNICsHandler struct {
280+
ctx context.Context
281+
instanceAPI *instance.API
282+
serverID string
283+
privateNICsMap map[string]*instance.PrivateNIC
284+
zone scw.Zone
285+
}
286+
287+
func newPrivateNICHandler(ctx context.Context, api *instance.API, server string, zone scw.Zone) (*privateNICsHandler, error) {
288+
handler := &privateNICsHandler{
289+
ctx: ctx,
290+
instanceAPI: api,
291+
serverID: server,
292+
zone: zone}
293+
return handler, handler.flatPrivateNICs()
294+
}
295+
296+
func (ph *privateNICsHandler) flatPrivateNICs() error {
297+
privateNICsMap := make(map[string]*instance.PrivateNIC)
298+
res, err := ph.instanceAPI.ListPrivateNICs(&instance.ListPrivateNICsRequest{Zone: ph.zone, ServerID: ph.serverID})
299+
if err != nil {
300+
return err
301+
}
302+
for _, p := range res.PrivateNics {
303+
privateNICsMap[p.PrivateNetworkID] = p
304+
}
305+
306+
ph.privateNICsMap = privateNICsMap
307+
return nil
308+
}
309+
310+
func (ph *privateNICsHandler) detach(o interface{}) error {
311+
oPtr := expandStringPtr(o)
312+
if oPtr != nil && len(*oPtr) > 0 {
313+
// check if old private network still exist on instance server
314+
if p, ok := ph.privateNICsMap[expandID(*oPtr)]; ok {
315+
// detach private NIC
316+
err := ph.instanceAPI.DeletePrivateNIC(&instance.DeletePrivateNICRequest{
317+
PrivateNicID: expandID(p.ID),
318+
Zone: ph.zone,
319+
ServerID: ph.serverID},
320+
scw.WithContext(ph.ctx))
321+
if err != nil {
322+
return err
323+
}
324+
}
325+
}
326+
327+
return nil
328+
}
329+
330+
func (ph *privateNICsHandler) attach(n interface{}) error {
331+
nPtr := expandStringPtr(n)
332+
if nPtr != nil {
333+
// check if new private network was already attached on instance server
334+
privateNetworkID := expandID(*nPtr)
335+
if _, ok := ph.privateNICsMap[privateNetworkID]; !ok {
336+
_, err := ph.instanceAPI.CreatePrivateNIC(&instance.CreatePrivateNICRequest{
337+
Zone: ph.zone,
338+
ServerID: ph.serverID,
339+
PrivateNetworkID: privateNetworkID})
340+
if err != nil {
341+
return err
342+
}
343+
}
344+
}
345+
346+
return nil
347+
}
348+
349+
func (ph *privateNICsHandler) set(d *schema.ResourceData) error {
350+
raw := d.Get("private_network")
351+
privateNetworks := []map[string]interface{}(nil)
352+
for index := range raw.([]interface{}) {
353+
pnKey := fmt.Sprintf("private_network.%d.pn_id", index)
354+
keyValue := d.Get(pnKey)
355+
keyRaw, err := ph.get(keyValue.(string))
356+
if err != nil {
357+
return err
358+
}
359+
privateNetworks = append(privateNetworks, keyRaw.(map[string]interface{}))
360+
}
361+
return d.Set("private_network", privateNetworks)
362+
}
363+
364+
func (ph *privateNICsHandler) get(key string) (interface{}, error) {
365+
locality, id, err := parseLocalizedID(key)
366+
if err != nil {
367+
return nil, err
368+
}
369+
pn := ph.privateNICsMap[id]
370+
return map[string]interface{}{
371+
"pn_id": key,
372+
"mac_address": pn.MacAddress,
373+
"status": pn.State.String(),
374+
"zone": locality,
375+
}, nil
376+
}

scaleway/helpers_vpc.go

+11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package scaleway
22

33
import (
4+
"fmt"
5+
46
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
57
"github.com/scaleway/scaleway-sdk-go/api/vpc/v1"
68
"github.com/scaleway/scaleway-sdk-go/scw"
@@ -29,3 +31,12 @@ func vpcAPIWithZoneAndID(m interface{}, id string) (*vpc.API, scw.Zone, string,
2931
}
3032
return vpcAPI, zone, ID, err
3133
}
34+
35+
func vpcAPI(m interface{}) (*vpc.API, error) {
36+
meta, ok := m.(*Meta)
37+
if !ok {
38+
return nil, fmt.Errorf("wrong type: %T", m)
39+
}
40+
41+
return vpc.NewAPI(meta.scwClient), nil
42+
}

scaleway/resource_instance_server.go

+104
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"io/ioutil"
99
"strconv"
1010

11+
"github.com/google/go-cmp/cmp"
1112
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
1213
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1314
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
@@ -202,6 +203,34 @@ func resourceScalewayInstanceServer() *schema.Resource {
202203
Type: schema.TypeString,
203204
},
204205
},
206+
"private_network": {
207+
Type: schema.TypeList,
208+
Optional: true,
209+
MaxItems: 8,
210+
Description: "List of private network to connect with your instance",
211+
Elem: &schema.Resource{
212+
Schema: map[string]*schema.Schema{
213+
"pn_id": {
214+
Type: schema.TypeString,
215+
Required: true,
216+
ValidateFunc: validationUUIDorUUIDWithLocality(),
217+
Description: "The Private Network ID",
218+
},
219+
// Computed
220+
"mac_address": {
221+
Type: schema.TypeString,
222+
Description: "MAC address of the NIC",
223+
Computed: true,
224+
},
225+
"status": {
226+
Type: schema.TypeString,
227+
Computed: true,
228+
Description: "The private NIC state",
229+
},
230+
"zone": zoneSchema(),
231+
},
232+
},
233+
},
205234
"zone": zoneSchema(),
206235
"organization_id": organizationIDSchema(),
207236
"project_id": projectIDSchema(),
@@ -363,6 +392,27 @@ func resourceScalewayInstanceServerCreate(ctx context.Context, d *schema.Resourc
363392
return diag.FromErr(err)
364393
}
365394

395+
////
396+
// Private Network
397+
////
398+
if rawPNICs, ok := d.GetOk("private_network"); ok {
399+
vpcAPI, err := vpcAPI(meta)
400+
if err != nil {
401+
return diag.FromErr(err)
402+
}
403+
pnRequest, err := preparePrivateNIC(ctx, rawPNICs, res.Server, vpcAPI)
404+
if err != nil {
405+
return diag.FromErr(err)
406+
}
407+
// compute attachment
408+
for _, q := range pnRequest {
409+
_, err := instanceAPI.CreatePrivateNIC(q, scw.WithContext(ctx))
410+
if err != nil {
411+
return diag.FromErr(err)
412+
}
413+
}
414+
}
415+
366416
return resourceScalewayInstanceServerRead(ctx, d, meta)
367417
}
368418

@@ -498,6 +548,20 @@ func resourceScalewayInstanceServerRead(ctx context.Context, d *schema.ResourceD
498548
_ = d.Set("user_data", userData)
499549
}
500550

551+
////
552+
// Read server private networks
553+
////
554+
ph, err := newPrivateNICHandler(ctx, instanceAPI, ID, zone)
555+
if err != nil {
556+
diag.FromErr(err)
557+
}
558+
559+
// set private networks
560+
err = ph.set(d)
561+
if err != nil {
562+
diag.FromErr(err)
563+
}
564+
501565
return nil
502566
}
503567

@@ -679,6 +743,46 @@ func resourceScalewayInstanceServerUpdate(ctx context.Context, d *schema.Resourc
679743
}
680744
}
681745

746+
////
747+
// Update server private network
748+
////
749+
if d.HasChanges("private_network") {
750+
ph, err := newPrivateNICHandler(ctx, instanceAPI, ID, zone)
751+
if err != nil {
752+
diag.FromErr(err)
753+
}
754+
if raw, ok := d.GetOk("private_network"); ok {
755+
// retrieve all current private network interfaces
756+
for index := range raw.([]interface{}) {
757+
pnKey := fmt.Sprintf("private_network.%d.pn_id", index)
758+
if d.HasChange(pnKey) {
759+
o, n := d.GetChange(pnKey)
760+
if !cmp.Equal(n, o) {
761+
err := ph.detach(o)
762+
if err != nil {
763+
diag.FromErr(err)
764+
}
765+
err = ph.attach(n)
766+
if err != nil {
767+
diag.FromErr(err)
768+
}
769+
}
770+
}
771+
}
772+
} else {
773+
// retrieve old private network config
774+
o, _ := d.GetChange("private_network")
775+
for _, raw := range o.([]interface{}) {
776+
pn, pnExist := raw.(map[string]interface{})
777+
if pnExist {
778+
err := ph.detach(pn["pn_id"])
779+
if err != nil {
780+
diag.FromErr(err)
781+
}
782+
}
783+
}
784+
}
785+
}
682786
////
683787
// Apply changes
684788
////

0 commit comments

Comments
 (0)