Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(instance-private-network): Create/Read private network #942

Merged
merged 13 commits into from
Nov 30, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions docs/resources/instance_server.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,24 @@ resource "scaleway_instance_server" "web" {
}
```

### With private network

```hcl
resource scaleway_vpc_private_network pn01 {
name = "private_network_instance"
}

resource "scaleway_instance_server" "base" {
image = "ubuntu_focal"
type = "DEV1-S"

tags = [ "private_network" ]
private_network {
pn_id = scaleway_vpc_private_network.pn01.id
}
}
```

## Arguments Reference

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

- `private_network` - (Optional) The private network associated with the server.
Use the `pn_id` key to attach a [private_network](https://developers.scaleway.com/en/products/instance/api/#private-nics-a42eea) on your instance.

- `boot_type` - The boot Type of the server. Possible values are: `local`, `bootscript` or `rescue`.

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


## Private Network

~> **Important:** Updates to `private_network` will recreate the attachment Instance.

- `pn_id` - (Required) The private network ID where to connect.
- `mac_address` The private NIC MAC address.
- `status` The private NIC state.
- `zone` - (Defaults to [provider](../index.md#zone) `zone`) The [zone](../guides/regions_and_zones.md#zones) in which the server should be created.



## Attributes Reference

In addition to all above arguments, the following attributes are exported:
Expand Down
31 changes: 31 additions & 0 deletions scaleway/helpers_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/dustin/go-humanize"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/api/vpc/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)

Expand Down Expand Up @@ -242,3 +243,33 @@ func sanitizeVolumeMap(serverName string, volumes map[string]*instance.VolumeTem

return m
}

func preparePrivateNIC(
ctx context.Context, data interface{},
server *instance.Server, vpcAPI *vpc.API) ([]*instance.CreatePrivateNICRequest, error) {
if data == nil {
return nil, nil
}

var res []*instance.CreatePrivateNICRequest

for _, pn := range data.([]interface{}) {
r := pn.(map[string]interface{})
zonedID, pnExist := r["pn_id"]
privateNetworkID := expandID(zonedID.(string))
if pnExist {
currentPN, err := vpcAPI.GetPrivateNetwork(&vpc.GetPrivateNetworkRequest{
PrivateNetworkID: expandID(privateNetworkID),
Zone: server.Zone,
}, scw.WithContext(ctx))
if err != nil {
return nil, err
}
query := &instance.CreatePrivateNICRequest{
Zone: currentPN.Zone, ServerID: server.ID, PrivateNetworkID: currentPN.ID}
res = append(res, query)
}
}

return res, nil
}
11 changes: 11 additions & 0 deletions scaleway/helpers_vpc.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package scaleway

import (
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/scaleway/scaleway-sdk-go/api/vpc/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
Expand Down Expand Up @@ -29,3 +31,12 @@ func vpcAPIWithZoneAndID(m interface{}, id string) (*vpc.API, scw.Zone, string,
}
return vpcAPI, zone, ID, err
}

func vpcAPI(m interface{}) (*vpc.API, error) {
meta, ok := m.(*Meta)
if !ok {
return nil, fmt.Errorf("wrong type: %T", m)
}

return vpc.NewAPI(meta.scwClient), nil
}
197 changes: 197 additions & 0 deletions scaleway/resource_instance_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"io/ioutil"
"strconv"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
Expand Down Expand Up @@ -202,6 +203,34 @@ func resourceScalewayInstanceServer() *schema.Resource {
Type: schema.TypeString,
},
},
"private_network": {
Type: schema.TypeList,
Optional: true,
MaxItems: 8,
Description: "List of private network to connect with your instance",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"pn_id": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validationUUIDorUUIDWithLocality(),
Description: "The Private Network ID",
},
// Computed
"mac_address": {
Type: schema.TypeString,
Description: "MAC address of the NIC",
Computed: true,
},
"status": {
Type: schema.TypeString,
Computed: true,
Description: "The private NIC state",
},
"zone": zoneSchema(),
},
},
},
"zone": zoneSchema(),
"organization_id": organizationIDSchema(),
"project_id": projectIDSchema(),
Expand Down Expand Up @@ -363,6 +392,27 @@ func resourceScalewayInstanceServerCreate(ctx context.Context, d *schema.Resourc
return diag.FromErr(err)
}

////
// Private Network
////
if rawPNICs, ok := d.GetOk("private_network"); ok {
vpcAPI, err := vpcAPI(meta)
if err != nil {
return diag.FromErr(err)
}
pnRequest, err := preparePrivateNIC(ctx, rawPNICs, res.Server, vpcAPI)
if err != nil {
return diag.FromErr(err)
}
// compute attachment
for _, q := range pnRequest {
_, err := instanceAPI.CreatePrivateNIC(q, scw.WithContext(ctx))
if err != nil {
return diag.FromErr(err)
}
}
}

return resourceScalewayInstanceServerRead(ctx, d, meta)
}

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

////
// Read server private networks
////
ph, err := newPrivateNICHandler(ctx, instanceAPI, ID, zone)
if err != nil {
diag.FromErr(err)
}

// set private networks
err = ph.set(d)
if err != nil {
diag.FromErr(err)
}

return nil
}

Expand Down Expand Up @@ -679,6 +743,46 @@ func resourceScalewayInstanceServerUpdate(ctx context.Context, d *schema.Resourc
}
}

////
// Update server private network
////
if d.HasChanges("private_network") {
ph, err := newPrivateNICHandler(ctx, instanceAPI, ID, zone)
if err != nil {
diag.FromErr(err)
}
if raw, ok := d.GetOk("private_network"); ok {
// retrieve all current private network interfaces
for index := range raw.([]interface{}) {
pnKey := fmt.Sprintf("private_network.%d.pn_id", index)
if d.HasChange(pnKey) {
o, n := d.GetChange(pnKey)
if !cmp.Equal(n, o) {
err := ph.detach(o)
if err != nil {
diag.FromErr(err)
}
err = ph.attach(n)
if err != nil {
diag.FromErr(err)
}
}
}
}
} else {
// retrieve old private network config
o, _ := d.GetChange("private_network")
for _, raw := range o.([]interface{}) {
pn, pnExist := raw.(map[string]interface{})
if pnExist {
err := ph.detach(pn["pn_id"])
if err != nil {
diag.FromErr(err)
}
}
}
}
}
////
// Apply changes
////
Expand All @@ -702,6 +806,99 @@ func resourceScalewayInstanceServerUpdate(ctx context.Context, d *schema.Resourc
return append(warnings, resourceScalewayInstanceServerRead(ctx, d, meta)...)
}

type PrivateNICsHandler struct {
ctx context.Context
instanceAPI *instance.API
serverID string
privateNICsMap map[string]*instance.PrivateNIC
zone scw.Zone
}

func newPrivateNICHandler(ctx context.Context, api *instance.API, server string, zone scw.Zone) (*PrivateNICsHandler, error) {
handler := &PrivateNICsHandler{ctx: ctx, instanceAPI: api, serverID: server, zone: zone}
return handler, handler.flatPrivateNICs()
}

func (ph *PrivateNICsHandler) flatPrivateNICs() error {
privateNICsMap := make(map[string]*instance.PrivateNIC)
res, err := ph.instanceAPI.ListPrivateNICs(&instance.ListPrivateNICsRequest{Zone: ph.zone, ServerID: ph.serverID})
if err != nil {
return err
}
for _, p := range res.PrivateNics {
privateNICsMap[p.PrivateNetworkID] = p
}

ph.privateNICsMap = privateNICsMap
return nil
}

func (ph *PrivateNICsHandler) detach(o interface{}) error {
oPtr := expandStringPtr(o)
if oPtr != nil && len(*oPtr) > 0 {
// check if old private network still exist on instance server
if p, ok := ph.privateNICsMap[expandID(*oPtr)]; ok {
// detach private NIC
err := ph.instanceAPI.DeletePrivateNIC(&instance.DeletePrivateNICRequest{
PrivateNicID: expandID(p.ID),
Zone: ph.zone,
ServerID: ph.serverID}, scw.WithContext(ph.ctx))
if err != nil {
return err
}
}
}

return nil
}

func (ph *PrivateNICsHandler) attach(n interface{}) error {
nPtr := expandStringPtr(n)
if nPtr != nil {
// check if new private network was already attached on instance server
privateNetworkID := expandID(*nPtr)
if _, ok := ph.privateNICsMap[privateNetworkID]; !ok {
_, err := ph.instanceAPI.CreatePrivateNIC(&instance.CreatePrivateNICRequest{
Zone: ph.zone,
ServerID: ph.serverID, PrivateNetworkID: privateNetworkID})
if err != nil {
return err
}
}
}

return nil
}

func (ph *PrivateNICsHandler) set(d *schema.ResourceData) error {
raw := d.Get("private_network")
privateNetworks := []map[string]interface{}(nil)
for index := range raw.([]interface{}) {
pnKey := fmt.Sprintf("private_network.%d.pn_id", index)
keyValue := d.Get(pnKey)
keyRaw, err := ph.get(keyValue.(string))
if err != nil {
return err
}
privateNetworks = append(privateNetworks, keyRaw.(map[string]interface{}))
}
return d.Set("private_network", privateNetworks)
}

func (ph *PrivateNICsHandler) get(key string) (interface{}, error) {
locality, id, err := parseLocalizedID(key)
if err != nil {
return nil, err
}
pn := ph.privateNICsMap[id]
return map[string]interface{}{
"pn_id": key,
"mac_address": pn.MacAddress,
"status": pn.State.String(),
"zone": locality,
}, nil
}

func resourceScalewayInstanceServerDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
instanceAPI, zone, ID, err := instanceAPIWithZoneAndID(meta, d.Id())
if err != nil {
Expand Down
Loading