Skip to content

Commit

Permalink
Convert the device data source to metal-go
Browse files Browse the repository at this point in the history
  • Loading branch information
ctreatma committed Feb 23, 2023
1 parent de5ac34 commit 69d885f
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 35 deletions.
12 changes: 6 additions & 6 deletions equinix/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,10 @@ type Config struct {
metal *packngo.Client
metalgo *metalv1.APIClient

ecxUserAgent string
neUserAgent string
metalUserAgent string
ecxUserAgent string
neUserAgent string
metalUserAgent string
metalGoUserAgent string

terraformVersion string
fabricClient *v4.APIClient
Expand Down Expand Up @@ -208,13 +209,12 @@ func (c *Config) NewMetalClient() *packngo.Client {

// NewMetalGoClient returns a new metal-go client for accessing Equinix Metal's API.
func (c *Config) NewMetalGoClient() *metalv1.APIClient {
// TODO: User agent
configuration := metalv1.NewConfiguration()
configuration.Debug = true
// TODO: push auth down into metal-go?
// TODO: support config file for auth in addition to environment variable
configuration.AddDefaultHeader("X-Auth-Token", os.Getenv("METAL_AUTH_TOKEN"))
configuration.UserAgent = c.fullUserAgent("equinix/metal-go")
client := metalv1.NewAPIClient(configuration)
c.metalGoUserAgent = configuration.UserAgent
return client
}

Expand Down
67 changes: 39 additions & 28 deletions equinix/data_source_metal_device.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import (
"sort"
"strings"

metalv1 "github.com/equinix-labs/metal-go/metal/v1"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure"
"github.com/packethost/packngo"
)

func dataSourceMetalDevice() *schema.Resource {
Expand Down Expand Up @@ -201,7 +201,7 @@ func dataSourceMetalDevice() *schema.Resource {
}

func dataSourceMetalDeviceRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Config).metal
client := meta.(*Config).metalgo

hostnameRaw, hostnameOK := d.GetOk("hostname")
projectIdRaw, projectIdOK := d.GetOk("project_id")
Expand All @@ -210,17 +210,22 @@ func dataSourceMetalDeviceRead(d *schema.ResourceData, meta interface{}) error {
if !deviceIdOK && !hostnameOK {
return fmt.Errorf("You must supply device_id or hostname")
}
var device *packngo.Device
var device *metalv1.Device
var proj *metalv1.Project

projectId := projectIdRaw.(string)
proj, _, err := client.ProjectsApi.FindProjectById(nil, projectId).Execute()
if err != nil {
return err
}

if hostnameOK {
if !projectIdOK {
return fmt.Errorf("If you lookup via hostname, you must supply project_id")
}
hostname := hostnameRaw.(string)
projectId := projectIdRaw.(string)

ds, _, err := client.Devices.List(
projectId,
&packngo.ListOptions{Search: hostname, Includes: deviceCommonIncludes})
ds, _, err := client.DevicesApi.FindProjectDevices(nil, projectId).Hostname(hostname).Include(deviceCommonIncludes).Execute()
if err != nil {
return err
}
Expand All @@ -232,26 +237,28 @@ func dataSourceMetalDeviceRead(d *schema.ResourceData, meta interface{}) error {
} else {
deviceId := deviceIdRaw.(string)
var err error
device, _, err = client.Devices.Get(deviceId, deviceReadOptions)
device, _, err = client.DevicesApi.FindDeviceById(nil, deviceId).Execute() // deviceReadOptions ?
if err != nil {
return err
}
}

d.Set("hostname", device.Hostname)
d.Set("project_id", device.Project.ID)
d.Set("device_id", device.ID)
d.Set("project_id", proj.Id)
d.Set("device_id", device.Id)
d.Set("plan", device.Plan.Slug)
d.Set("facility", device.Facility.Code)
if device.Metro != nil {
d.Set("metro", strings.ToLower(device.Metro.Code))
d.Set("metro", strings.ToLower(*device.Metro.Code))
}
d.Set("operating_system", device.OS.Slug)
d.Set("operating_system", device.OperatingSystem.Slug)
d.Set("state", device.State)
d.Set("billing_cycle", device.BillingCycle)
d.Set("ipxe_script_url", device.IPXEScriptURL)
d.Set("always_pxe", device.AlwaysPXE)
d.Set("ipxe_script_url", device.IpxeScriptUrl)
d.Set("always_pxe", device.AlwaysPxe)
d.Set("root_password", device.RootPassword)

// Device schema needs to be updated to define the `storage` field
if device.Storage != nil {
rawStorageBytes, err := json.Marshal(device.Storage)
if err != nil {
Expand All @@ -266,45 +273,49 @@ func dataSourceMetalDeviceRead(d *schema.ResourceData, meta interface{}) error {
}

if device.HardwareReservation != nil {
d.Set("hardware_reservation_id", device.HardwareReservation.ID)
// Device schema needs to be updated to include hardware reservation attributes
//d.Set("hardware_reservation_id", device.HardwareReservation.ID)
}
networkType, err := getMetalGoNetworkType(device)
if err != nil {
return fmt.Errorf("[ERR] Error computing network type for device (%s): %s", d.Id(), err)
}
networkType := device.GetNetworkType()

d.Set("network_type", networkType)

d.Set("tags", device.Tags)

keyIDs := []string{}
for _, k := range device.SSHKeys {
keyIDs = append(keyIDs, path.Base(k.URL))
for _, k := range device.SshKeys {
keyIDs = append(keyIDs, path.Base(k.Href))
}
d.Set("ssh_key_ids", keyIDs)
networkInfo := getNetworkInfo(device.Network)
networkInfo := getMetalGoNetworkInfo(device.IpAddresses)

sort.SliceStable(networkInfo.Networks, func(i, j int) bool {
famI := networkInfo.Networks[i]["family"].(int)
famJ := networkInfo.Networks[j]["family"].(int)
famI := networkInfo.Networks[i]["family"].(int32)
famJ := networkInfo.Networks[j]["family"].(int32)
pubI := networkInfo.Networks[i]["public"].(bool)
pubJ := networkInfo.Networks[j]["public"].(bool)
return getNetworkRank(famI, pubI) < getNetworkRank(famJ, pubJ)
return getNetworkRank(int(famI), pubI) < getNetworkRank(int(famJ), pubJ)
})

d.Set("network", networkInfo.Networks)
d.Set("access_public_ipv4", networkInfo.PublicIPv4)
d.Set("access_private_ipv4", networkInfo.PrivateIPv4)
d.Set("access_public_ipv6", networkInfo.PublicIPv6)

ports := getPorts(device.NetworkPorts)
ports := getMetalGoPorts(device.NetworkPorts)
d.Set("ports", ports)

d.SetId(device.ID)
d.SetId(*device.Id)
return nil
}

func findDeviceByHostname(devices []packngo.Device, hostname string) (*packngo.Device, error) {
results := make([]packngo.Device, 0)
for _, d := range devices {
if d.Hostname == hostname {
func findDeviceByHostname(devices *metalv1.DeviceList, hostname string) (*metalv1.Device, error) {
results := make([]metalv1.Device, 0)
for _, d := range devices.GetDevices() {
if *d.Hostname == hostname {
results = append(results, d)
}
}
Expand Down
41 changes: 41 additions & 0 deletions equinix/data_source_metal_device_bgp_neighbors_acc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package equinix

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAccDataSourceMetalDeviceBgpNeighbors(t *testing.T) {
projectName := fmt.Sprintf("ds-device-%s", acctest.RandString(10))

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceMetalDeviceBgpNeighborsConfig(projectName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet(
"data.equinix_metal_device_bgp_neighbors.test", "bgp_neighbors"),
),
},
},
})
}

func testAccDataSourceMetalDeviceBgpNeighborsConfig(projectName string) string {
return fmt.Sprintf(`
%s
data "equinix_metal_device_bgp_neighbors" "test" {
device_id = equinix_metal_device.test.id
}
output "bgp_neighbors_listing" {
value = data.equinix_metal_device_bgp_neighbors.test.bgp_neighbors
}
`, testDataSourceMetalDeviceConfig_basic(projectName))
}
59 changes: 59 additions & 0 deletions equinix/helpers_device.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package equinix

import (
"encoding/json"
"fmt"
"log"
"strings"
"sync"
"time"

metalv1 "github.com/equinix-labs/metal-go/metal/v1"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
Expand Down Expand Up @@ -93,6 +95,48 @@ func getNetworkInfo(ips []*packngo.IPAddressAssignment) NetworkInfo {
return ni
}

func getMetalGoNetworkType(device *metalv1.Device) (*string, error) {

pgDevice := packngo.Device{}
res, err := device.MarshalJSON()
if err != nil {
json.Unmarshal(res, pgDevice)
networkType := pgDevice.GetNetworkType()
return &networkType, nil
}
return nil, err
}

func getMetalGoNetworkInfo(ips []metalv1.IPAssignment) NetworkInfo {
ni := NetworkInfo{Networks: make([]map[string]interface{}, 0, 1)}
for _, ip := range ips {
network := map[string]interface{}{
"address": *ip.Address,
"gateway": *ip.Gateway,
"family": *ip.AddressFamily,
"cidr": *ip.Cidr,
"public": *ip.Public,
}
ni.Networks = append(ni.Networks, network)

// Initial device IPs are fixed and marked as "Management"
if !ip.HasManagement() || *ip.Management {
if *ip.AddressFamily == int32(4) {
if !ip.HasPublic() || *ip.Public {
ni.Host = *ip.Address
ni.IPv4SubnetSize = int(*ip.Cidr)
ni.PublicIPv4 = *ip.Address
} else {
ni.PrivateIPv4 = *ip.Address
}
} else {
ni.PublicIPv6 = *ip.Address
}
}
}
return ni
}

func getNetworkRank(family int, public bool) int {
switch {
case family == 4 && public:
Expand Down Expand Up @@ -120,6 +164,21 @@ func getPorts(ps []packngo.Port) []map[string]interface{} {
return ret
}

func getMetalGoPorts(ps []metalv1.Port) []map[string]interface{} {
ret := make([]map[string]interface{}, 0, 1)
for _, p := range ps {
port := map[string]interface{}{
"name": *p.Name,
"id": *p.Id,
"type": *p.Type,
"mac": *p.Data.Mac,
"bonded": *p.Data.Bonded,
}
ret = append(ret, port)
}
return ret
}

func hwReservationStateRefreshFunc(client *packngo.Client, reservationId, instanceId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
r, _, err := client.HardwareReservations.Get(reservationId, &packngo.GetOptions{Includes: []string{"device"}})
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.18
require (
github.com/antihax/optional v1.0.0
github.com/equinix-labs/fabric-go v0.4.0
github.com/equinix-labs/metal-go v0.5.0
github.com/equinix-labs/metal-go v0.6.0
github.com/equinix/ecx-go/v2 v2.3.0
github.com/equinix/ne-go v1.7.0
github.com/equinix/oauth2-go v1.0.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ github.com/equinix-labs/fabric-go v0.4.0 h1:YM6jkdPlYJrgUEfCqt1WpXe2gACM5EavRL26
github.com/equinix-labs/fabric-go v0.4.0/go.mod h1:/0uePNYbhu/1qWrxhD011AjU6yjf7r0sZgTCn8TyitI=
github.com/equinix-labs/metal-go v0.5.0 h1:n86lrMi1EAHN+eOpwoPZ37OxBS9cS2Xgup/mqD5Hkwo=
github.com/equinix-labs/metal-go v0.5.0/go.mod h1:uTuxWkVf/wl9VUBYnCtecRtHfdTCeajDwgzluSZBD7U=
github.com/equinix-labs/metal-go v0.6.0 h1:rX5XnPt95HlDMtITSjwQjqFzu1AKQqCult/9uy/lFNw=
github.com/equinix-labs/metal-go v0.6.0/go.mod h1:uTuxWkVf/wl9VUBYnCtecRtHfdTCeajDwgzluSZBD7U=
github.com/equinix/ecx-go/v2 v2.3.0 h1:SOABrI2TP073Mx3gVoWa4qGlot1Z2hECAOY8W4nYDPU=
github.com/equinix/ecx-go/v2 v2.3.0/go.mod h1:FvCdZ3jXU8Z4CPKig2DT+4J2HdwgRK17pIcznM7RXyk=
github.com/equinix/ne-go v1.7.0 h1:Z2KJYaS624kqhZrDG+uLtdCVC/cWs8USgXaNLV7UiwE=
Expand Down

0 comments on commit 69d885f

Please sign in to comment.