From ff85c5588c97e518340aa9114d56e5ea5a9900dd Mon Sep 17 00:00:00 2001 From: Charles Treatman Date: Mon, 30 Jan 2023 15:15:18 -0600 Subject: [PATCH 01/11] Use `metal-go` for the device data source This updates the device data source to use `metal-go` instead of `packngo`. As part of this migration, the device data source gains the new `sos_hostname` attribute which enables customers to reduce their reliance on the deprecated `facility` attribute, which was previously necessary in order to compute the SOS hostname for a device. --- equinix/data_source_metal_device.go | 71 ++++++++++++++++------------- equinix/helpers_device.go | 58 +++++++++++++++++++++++ 2 files changed, 98 insertions(+), 31 deletions(-) diff --git a/equinix/data_source_metal_device.go b/equinix/data_source_metal_device.go index f656068a1..d3ebc3f3d 100644 --- a/equinix/data_source_metal_device.go +++ b/equinix/data_source_metal_device.go @@ -8,9 +8,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 { @@ -198,12 +198,17 @@ func dataSourceMetalDevice() *schema.Resource { }, }, }, + "sos_hostname": { + Type: schema.TypeString, + Description: "The hostname to use for [Serial over SSH](https://deploy.equinix.com/developers/docs/metal/resilience-recovery/serial-over-ssh/) access to the device", + Computed: true, + }, }, } } func dataSourceMetalDeviceRead(ctx context.Context, 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") @@ -212,7 +217,8 @@ func dataSourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta if !deviceIdOK && !hostnameOK { return fmt.Errorf("You must supply device_id or hostname") } - var device *packngo.Device + var device *metalv1.Device + if hostnameOK { if !projectIdOK { return fmt.Errorf("If you lookup via hostname, you must supply project_id") @@ -220,9 +226,7 @@ func dataSourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta hostname := hostnameRaw.(string) projectId := projectIdRaw.(string) - ds, _, err := client.Devices.List( - projectId, - &packngo.ListOptions{Search: hostname, Includes: deviceCommonIncludes}) + ds, _, err := client.DevicesApi.FindProjectDevices(ctx, projectId).Hostname(hostname).Include(deviceCommonIncludes).Execute() if err != nil { return err } @@ -234,26 +238,28 @@ func dataSourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta } else { deviceId := deviceIdRaw.(string) var err error - device, _, err = client.Devices.Get(deviceId, deviceReadOptions) + device, _, err = client.DevicesApi.FindDeviceById(ctx, deviceId).Include(deviceCommonIncludes).Execute() 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("hostname", device.GetHostname()) + d.Set("project_id", device.Project.GetId()) + d.Set("device_id", device.GetId()) 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.GetCode())) } - d.Set("operating_system", device.OS.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("root_password", device.RootPassword) + d.Set("operating_system", device.OperatingSystem.GetSlug()) + d.Set("state", device.GetState()) + d.Set("billing_cycle", device.GetBillingCycle()) + d.Set("ipxe_script_url", device.GetIpxeScriptUrl()) + d.Set("always_pxe", device.GetAlwaysPxe()) + d.Set("root_password", device.GetRootPassword()) + d.Set("sos_hostname", device.GetSos()) + if device.Storage != nil { rawStorageBytes, err := json.Marshal(device.Storage) if err != nil { @@ -268,27 +274,30 @@ func dataSourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta } if device.HardwareReservation != nil { - d.Set("hardware_reservation_id", device.HardwareReservation.ID) + d.Set("hardware_reservation_id", device.HardwareReservation.GetId()) + } + 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) @@ -296,17 +305,17 @@ func dataSourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta 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.GetId()) 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) } } diff --git a/equinix/helpers_device.go b/equinix/helpers_device.go index d7a1903f5..8ffdc8c43 100644 --- a/equinix/helpers_device.go +++ b/equinix/helpers_device.go @@ -2,6 +2,7 @@ package equinix import ( "context" + "encoding/json" "errors" "fmt" "log" @@ -128,6 +129,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: @@ -170,6 +213,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.GetName(), + "id": p.GetId(), + "type": p.GetType(), + "mac": p.Data.GetMac(), + "bonded": p.Data.GetBonded(), + } + ret = append(ret, port) + } + return ret +} + func hwReservationStateRefreshFunc(client *packngo.Client, reservationId, instanceId string) retry.StateRefreshFunc { return func() (interface{}, string, error) { r, _, err := client.HardwareReservations.Get(reservationId, &packngo.GetOptions{Includes: []string{"device"}}) From 4a6d54f01d68e04c9c8b5c91aff6fd80723fc466 Mon Sep 17 00:00:00 2001 From: Charles Treatman Date: Wed, 30 Aug 2023 15:03:03 -0500 Subject: [PATCH 02/11] Revert "Revert "[WIP] Use metal-go for device resource"" This reverts commit a26f1b945d5f089dd9cac7b086e5ca75e579abe2. --- equinix/resource_metal_device.go | 65 ++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/equinix/resource_metal_device.go b/equinix/resource_metal_device.go index 1596ff188..d0fb9ad0a 100644 --- a/equinix/resource_metal_device.go +++ b/equinix/resource_metal_device.go @@ -410,6 +410,11 @@ func resourceMetalDevice() *schema.Resource { }, }, }, + "sos_hostname": { + Type: schema.TypeString, + Description: "The hostname to use for [Serial over SSH](https://deploy.equinix.com/developers/docs/metal/resilience-recovery/serial-over-ssh/) access to the device", + Computed: true, + }, }, CustomizeDiff: customdiff.Sequence( customdiff.ForceNewIf("custom_data", reinstallDisabledAndNoChangesAllowed("custom_data")), @@ -606,10 +611,10 @@ func resourceMetalDeviceCreate(ctx context.Context, d *schema.ResourceData, meta } func resourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) error { - meta.(*Config).addModuleToMetalUserAgent(d) - client := meta.(*Config).metal + meta.(*Config).addModuleToMetalGoUserAgent(d) + client := meta.(*Config).metalgo - device, _, err := client.Devices.Get(d.Id(), deviceReadOptions) + device, _, err := client.DevicesApi.FindDeviceById(context.Background(), d.Id()).Include(deviceCommonIncludes).Execute() if err != nil { err = friendlyError(err) @@ -625,23 +630,24 @@ func resourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta i return err } - d.Set("hostname", device.Hostname) - d.Set("plan", device.Plan.Slug) - d.Set("deployed_facility", device.Facility.Code) - d.Set("facilities", []string{device.Facility.Code}) + d.Set("hostname", device.GetHostname()) + d.Set("plan", device.Plan.GetSlug()) + d.Set("deployed_facility", device.Facility.GetCode()) + d.Set("facilities", []string{device.Facility.GetCode()}) if device.Metro != nil { - d.Set("metro", device.Metro.Code) - } - d.Set("operating_system", device.OS.Slug) - d.Set("state", device.State) - d.Set("billing_cycle", device.BillingCycle) - d.Set("locked", device.Locked) - d.Set("created", device.Created) - d.Set("updated", device.Updated) - d.Set("ipxe_script_url", device.IPXEScriptURL) - d.Set("always_pxe", device.AlwaysPXE) - d.Set("root_password", device.RootPassword) - d.Set("project_id", device.Project.ID) + d.Set("metro", device.Metro.GetCode()) + } + d.Set("operating_system", device.OperatingSystem.GetSlug()) + d.Set("state", device.GetState()) + d.Set("billing_cycle", device.GetBillingCycle()) + d.Set("locked", device.GetLocked()) + d.Set("created", device.GetCreatedAt()) + d.Set("updated", device.GetUpdatedAt()) + d.Set("ipxe_script_url", device.GetIpxeScriptUrl()) + d.Set("always_pxe", device.GetAlwaysPxe()) + d.Set("root_password", device.GetRootPassword()) + d.Set("project_id", device.Project.GetId()) + d.Set("sos_hostname", device.GetSos()) if device.Storage != nil { rawStorageBytes, err := json.Marshal(device.Storage) if err != nil { @@ -655,10 +661,13 @@ func resourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta i d.Set("storage", storageString) } if device.HardwareReservation != nil { - d.Set("deployed_hardware_reservation_id", device.HardwareReservation.ID) + d.Set("deployed_hardware_reservation_id", device.HardwareReservation.GetId()) } - networkType := device.GetNetworkType() + networkType, err := getMetalGoNetworkType(device) + if err != nil { + return fmt.Errorf("[ERR] Error computing network type for device (%s): %s", d.Id(), err) + } d.Set("network_type", networkType) wfrd := "wait_for_reservation_deprovision" @@ -676,18 +685,18 @@ func resourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta i 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) @@ -695,7 +704,7 @@ func resourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta i 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) if networkInfo.Host != "" { From eb0f974f87c4dc8a1ed02fa0615f162ef3ede35a Mon Sep 17 00:00:00 2001 From: Charles Treatman Date: Fri, 8 Sep 2023 12:08:28 -0500 Subject: [PATCH 03/11] Replicate friendlyError for metal-go responses --- equinix/errors.go | 40 ++++++++++++++++++++------------ equinix/resource_metal_device.go | 4 ++-- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/equinix/errors.go b/equinix/errors.go index 61c03c5a4..c3c546762 100644 --- a/equinix/errors.go +++ b/equinix/errors.go @@ -22,25 +22,35 @@ func friendlyError(err error) error { if 0 == len(errors) { errors = Errors{e.SingleError} } - er := &ErrorResponse{ - StatusCode: resp.StatusCode, - Errors: errors, - } - respHead := resp.Header - - // this checks if the error comes from API (and not from cache/LB) - if len(errors) > 0 { - ct := respHead.Get("Content-Type") - xrid := respHead.Get("X-Request-Id") - if strings.Contains(ct, "application/json") && len(xrid) > 0 { - er.IsAPIError = true - } - } - return er + + return convertToFriendlyError(errors, resp) } return err } +func friendlyErrorForMetalGo(err error, resp *http.Response) error { + errors := Errors([]string{err.Error()}) + return convertToFriendlyError(errors, resp) +} + +func convertToFriendlyError(errors Errors, resp *http.Response) error { + er := &ErrorResponse{ + StatusCode: resp.StatusCode, + Errors: errors, + } + respHead := resp.Header + + // this checks if the error comes from API (and not from cache/LB) + if len(errors) > 0 { + ct := respHead.Get("Content-Type") + xrid := respHead.Get("X-Request-Id") + if strings.Contains(ct, "application/json") && len(xrid) > 0 { + er.IsAPIError = true + } + } + return er +} + func isForbidden(err error) bool { r, ok := err.(*packngo.ErrorResponse) if ok && r.Response != nil { diff --git a/equinix/resource_metal_device.go b/equinix/resource_metal_device.go index d0fb9ad0a..84adbc59b 100644 --- a/equinix/resource_metal_device.go +++ b/equinix/resource_metal_device.go @@ -614,9 +614,9 @@ func resourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta i meta.(*Config).addModuleToMetalGoUserAgent(d) client := meta.(*Config).metalgo - device, _, err := client.DevicesApi.FindDeviceById(context.Background(), d.Id()).Include(deviceCommonIncludes).Execute() + device, resp, err := client.DevicesApi.FindDeviceById(context.Background(), d.Id()).Include(deviceCommonIncludes).Execute() if err != nil { - err = friendlyError(err) + err = friendlyErrorForMetalGo(err, resp) // If the device somehow already destroyed, mark as successfully gone. // Checking d.IsNewResource prevents the creation of a resource from failing From 243f372aacd6b2fae7ec52016bf8e2373aebaea8 Mon Sep 17 00:00:00 2001 From: Charles Treatman Date: Thu, 14 Sep 2023 15:03:59 -0500 Subject: [PATCH 04/11] clean up pointer derefs and rename helpers to use a common suffix --- equinix/helpers_device.go | 32 ++++++++++++++++---------------- equinix/resource_metal_device.go | 6 +++--- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/equinix/helpers_device.go b/equinix/helpers_device.go index 8ffdc8c43..3cec2a4fa 100644 --- a/equinix/helpers_device.go +++ b/equinix/helpers_device.go @@ -129,7 +129,7 @@ func getNetworkInfo(ips []*packngo.IPAddressAssignment) NetworkInfo { return ni } -func getMetalGoNetworkType(device *metalv1.Device) (*string, error) { +func getNetworkTypeMetalGo(device *metalv1.Device) (*string, error) { pgDevice := packngo.Device{} res, err := device.MarshalJSON() @@ -141,30 +141,30 @@ func getMetalGoNetworkType(device *metalv1.Device) (*string, error) { return nil, err } -func getMetalGoNetworkInfo(ips []metalv1.IPAssignment) NetworkInfo { +func getNetworkInfoMetalGo(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, + "address": ip.GetAddress(), + "gateway": ip.GetGateway(), + "family": ip.GetAddressFamily(), + "cidr": ip.GetCidr(), + "public": ip.GetPublic(), } 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 + if ip.GetManagement() { + if ip.GetAddressFamily() == 4 { + if ip.GetPublic() { + ni.Host = ip.GetAddress() + ni.IPv4SubnetSize = int(ip.GetCidr()) + ni.PublicIPv4 = ip.GetAddress() } else { - ni.PrivateIPv4 = *ip.Address + ni.PrivateIPv4 = ip.GetAddress() } } else { - ni.PublicIPv6 = *ip.Address + ni.PublicIPv6 = ip.GetAddress() } } } @@ -213,7 +213,7 @@ func getPorts(ps []packngo.Port) []map[string]interface{} { return ret } -func getMetalGoPorts(ps []metalv1.Port) []map[string]interface{} { +func getPortsMetalGo(ps []metalv1.Port) []map[string]interface{} { ret := make([]map[string]interface{}, 0, 1) for _, p := range ps { port := map[string]interface{}{ diff --git a/equinix/resource_metal_device.go b/equinix/resource_metal_device.go index 84adbc59b..843295d9e 100644 --- a/equinix/resource_metal_device.go +++ b/equinix/resource_metal_device.go @@ -664,7 +664,7 @@ func resourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta i d.Set("deployed_hardware_reservation_id", device.HardwareReservation.GetId()) } - networkType, err := getMetalGoNetworkType(device) + networkType, err := getNetworkTypeMetalGo(device) if err != nil { return fmt.Errorf("[ERR] Error computing network type for device (%s): %s", d.Id(), err) } @@ -689,7 +689,7 @@ func resourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta i keyIDs = append(keyIDs, path.Base(k.Href)) } d.Set("ssh_key_ids", keyIDs) - networkInfo := getMetalGoNetworkInfo(device.IpAddresses) + networkInfo := getNetworkInfoMetalGo(device.IpAddresses) sort.SliceStable(networkInfo.Networks, func(i, j int) bool { famI := networkInfo.Networks[i]["family"].(int32) @@ -704,7 +704,7 @@ func resourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta i d.Set("access_private_ipv4", networkInfo.PrivateIPv4) d.Set("access_public_ipv6", networkInfo.PublicIPv6) - ports := getMetalGoPorts(device.NetworkPorts) + ports := getPortsMetalGo(device.NetworkPorts) d.Set("ports", ports) if networkInfo.Host != "" { From 7b79a2341bfffdbdf77722d396ee8743c8866d2f Mon Sep 17 00:00:00 2001 From: Charles Treatman Date: Fri, 22 Sep 2023 10:51:34 -0500 Subject: [PATCH 05/11] Document the sos_hostname attribute --- docs/data-sources/equinix_metal_device.md | 1 + docs/resources/equinix_metal_device.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/data-sources/equinix_metal_device.md b/docs/data-sources/equinix_metal_device.md index 0b1150988..1acc0c656 100644 --- a/docs/data-sources/equinix_metal_device.md +++ b/docs/data-sources/equinix_metal_device.md @@ -70,6 +70,7 @@ In addition to all arguments above, the following attributes are exported: * `ports` - List of ports assigned to the device. See [Ports Attribute](#ports-attribute) below for more details. * `root_password` - Root password to the server (if still available). +* `sos_hostname` - The hostname to use for [Serial over SSH](https://deploy.equinix.com/developers/docs/metal/resilience-recovery/serial-over-ssh/) access to the device * `ssh_key_ids` - List of IDs of SSH keys deployed in the device, can be both user or project SSH keys. * `state` - The state of the device. * `tags` - Tags attached to the device. diff --git a/docs/resources/equinix_metal_device.md b/docs/resources/equinix_metal_device.md index d540aa5f5..f03faf68b 100644 --- a/docs/resources/equinix_metal_device.md +++ b/docs/resources/equinix_metal_device.md @@ -285,6 +285,7 @@ See [network_types guide](../guides/network_types.md) for more info. more details. * `project_id` - The ID of the project the device belongs to. * `root_password` - Root password to the server (disabled after 24 hours). +* `sos_hostname` - The hostname to use for [Serial over SSH](https://deploy.equinix.com/developers/docs/metal/resilience-recovery/serial-over-ssh/) access to the device * `ssh_key_ids` - List of IDs of SSH keys deployed in the device, can be both user and project SSH keys. * `state` - The status of the device. * `tags` - Tags attached to the device. From 8eb1424f48a8be8d134ae49b6d199c6af9e98db4 Mon Sep 17 00:00:00 2001 From: Charles Treatman Date: Fri, 22 Sep 2023 10:54:56 -0500 Subject: [PATCH 06/11] update device data source helper references to match names from the resource PR --- equinix/data_source_metal_device.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/equinix/data_source_metal_device.go b/equinix/data_source_metal_device.go index d3ebc3f3d..0566f0d2f 100644 --- a/equinix/data_source_metal_device.go +++ b/equinix/data_source_metal_device.go @@ -276,7 +276,7 @@ func dataSourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta if device.HardwareReservation != nil { d.Set("hardware_reservation_id", device.HardwareReservation.GetId()) } - networkType, err := getMetalGoNetworkType(device) + networkType, err := getNetworkTypeMetalGo(device) if err != nil { return fmt.Errorf("[ERR] Error computing network type for device (%s): %s", d.Id(), err) } @@ -290,7 +290,7 @@ func dataSourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta keyIDs = append(keyIDs, path.Base(k.Href)) } d.Set("ssh_key_ids", keyIDs) - networkInfo := getMetalGoNetworkInfo(device.IpAddresses) + networkInfo := getNetworkInfoMetalGo(device.IpAddresses) sort.SliceStable(networkInfo.Networks, func(i, j int) bool { famI := networkInfo.Networks[i]["family"].(int32) @@ -305,7 +305,7 @@ func dataSourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta d.Set("access_private_ipv4", networkInfo.PrivateIPv4) d.Set("access_public_ipv6", networkInfo.PublicIPv6) - ports := getMetalGoPorts(device.NetworkPorts) + ports := getPortsMetalGo(device.NetworkPorts) d.Set("ports", ports) d.SetId(device.GetId()) From ff04f9c17ccfd0af740c03a14e7561c823f5fd02 Mon Sep 17 00:00:00 2001 From: Charles Treatman Date: Fri, 22 Sep 2023 11:53:41 -0500 Subject: [PATCH 07/11] convert time to RFC3339 to match what packngo did --- equinix/resource_metal_device.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/equinix/resource_metal_device.go b/equinix/resource_metal_device.go index 843295d9e..17522d7b2 100644 --- a/equinix/resource_metal_device.go +++ b/equinix/resource_metal_device.go @@ -641,8 +641,8 @@ func resourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta i d.Set("state", device.GetState()) d.Set("billing_cycle", device.GetBillingCycle()) d.Set("locked", device.GetLocked()) - d.Set("created", device.GetCreatedAt()) - d.Set("updated", device.GetUpdatedAt()) + d.Set("created", device.GetCreatedAt().Format(time.RFC3339)) + d.Set("updated", device.GetUpdatedAt().Format(time.RFC3339)) d.Set("ipxe_script_url", device.GetIpxeScriptUrl()) d.Set("always_pxe", device.GetAlwaysPxe()) d.Set("root_password", device.GetRootPassword()) From 743119eae923687fca98ff211b3a9835b891271f Mon Sep 17 00:00:00 2001 From: Charles Treatman Date: Fri, 22 Sep 2023 12:32:53 -0500 Subject: [PATCH 08/11] fix error check in networktype shim --- equinix/helpers_device.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/equinix/helpers_device.go b/equinix/helpers_device.go index 3cec2a4fa..7109b088a 100644 --- a/equinix/helpers_device.go +++ b/equinix/helpers_device.go @@ -130,13 +130,13 @@ func getNetworkInfo(ips []*packngo.IPAddressAssignment) NetworkInfo { } func getNetworkTypeMetalGo(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 + if err == nil { + if err = json.Unmarshal(res, &pgDevice); err == nil { + networkType := pgDevice.GetNetworkType() + return &networkType, nil + } } return nil, err } From f80eb681be9bb8448d061c32f0d49c8ba4bd4099 Mon Sep 17 00:00:00 2001 From: Charles Treatman Date: Fri, 29 Sep 2023 14:16:10 -0500 Subject: [PATCH 09/11] consolidate helper functions that were duplicated across PRs --- equinix/helpers_device.go | 81 +++++++++------------------------------ 1 file changed, 18 insertions(+), 63 deletions(-) diff --git a/equinix/helpers_device.go b/equinix/helpers_device.go index 7109b088a..fd37d88f7 100644 --- a/equinix/helpers_device.go +++ b/equinix/helpers_device.go @@ -73,26 +73,26 @@ func getNetworkInfoMetalGo(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, + "address": ip.GetAddress(), + "gateway": ip.GetGateway(), + "family": ip.GetAddressFamily(), + "cidr": ip.GetCidr(), + "public": ip.GetPublic(), } ni.Networks = append(ni.Networks, network) // Initial device IPs are fixed and marked as "Management" - if *ip.Management { - if *ip.AddressFamily == 4 { - if *ip.Public { - ni.Host = *ip.Address - ni.IPv4SubnetSize = int(*ip.Cidr) - ni.PublicIPv4 = *ip.Address + if ip.GetManagement() { + if ip.GetAddressFamily() == 4 { + if ip.GetPublic() { + ni.Host = ip.GetAddress() + ni.IPv4SubnetSize = int(ip.GetCidr()) + ni.PublicIPv4 = ip.GetAddress() } else { - ni.PrivateIPv4 = *ip.Address + ni.PrivateIPv4 = ip.GetAddress() } } else { - ni.PublicIPv6 = *ip.Address + ni.PublicIPv6 = ip.GetAddress() } } } @@ -141,36 +141,6 @@ func getNetworkTypeMetalGo(device *metalv1.Device) (*string, error) { return nil, err } -func getNetworkInfoMetalGo(ips []metalv1.IPAssignment) NetworkInfo { - ni := NetworkInfo{Networks: make([]map[string]interface{}, 0, 1)} - for _, ip := range ips { - network := map[string]interface{}{ - "address": ip.GetAddress(), - "gateway": ip.GetGateway(), - "family": ip.GetAddressFamily(), - "cidr": ip.GetCidr(), - "public": ip.GetPublic(), - } - ni.Networks = append(ni.Networks, network) - - // Initial device IPs are fixed and marked as "Management" - if ip.GetManagement() { - if ip.GetAddressFamily() == 4 { - if ip.GetPublic() { - ni.Host = ip.GetAddress() - ni.IPv4SubnetSize = int(ip.GetCidr()) - ni.PublicIPv4 = ip.GetAddress() - } else { - ni.PrivateIPv4 = ip.GetAddress() - } - } else { - ni.PublicIPv6 = ip.GetAddress() - } - } - } - return ni -} - func getNetworkRank(family int, public bool) int { switch { case family == 4 && public: @@ -187,11 +157,11 @@ func getPortsMetalGo(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, + "name": p.GetName(), + "id": p.GetId(), + "type": p.GetType(), + "mac": p.Data.GetMac(), + "bonded": p.Data.GetBonded(), } ret = append(ret, port) } @@ -213,21 +183,6 @@ func getPorts(ps []packngo.Port) []map[string]interface{} { return ret } -func getPortsMetalGo(ps []metalv1.Port) []map[string]interface{} { - ret := make([]map[string]interface{}, 0, 1) - for _, p := range ps { - port := map[string]interface{}{ - "name": p.GetName(), - "id": p.GetId(), - "type": p.GetType(), - "mac": p.Data.GetMac(), - "bonded": p.Data.GetBonded(), - } - ret = append(ret, port) - } - return ret -} - func hwReservationStateRefreshFunc(client *packngo.Client, reservationId, instanceId string) retry.StateRefreshFunc { return func() (interface{}, string, error) { r, _, err := client.HardwareReservations.Get(reservationId, &packngo.GetOptions{Includes: []string{"device"}}) From cfb80a7dfc0a2afb2f99c364c9d9da8106071002 Mon Sep 17 00:00:00 2001 From: Charles Treatman Date: Fri, 29 Sep 2023 14:39:54 -0500 Subject: [PATCH 10/11] remove no-longer-needed MetalGo suffix from device helpers --- equinix/data_source_metal_device.go | 6 ++-- equinix/helpers_device.go | 56 ++++------------------------- equinix/resource_metal_device.go | 7 ++-- 3 files changed, 12 insertions(+), 57 deletions(-) diff --git a/equinix/data_source_metal_device.go b/equinix/data_source_metal_device.go index 0566f0d2f..22b619d46 100644 --- a/equinix/data_source_metal_device.go +++ b/equinix/data_source_metal_device.go @@ -276,7 +276,7 @@ func dataSourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta if device.HardwareReservation != nil { d.Set("hardware_reservation_id", device.HardwareReservation.GetId()) } - networkType, err := getNetworkTypeMetalGo(device) + networkType, err := getNetworkType(device) if err != nil { return fmt.Errorf("[ERR] Error computing network type for device (%s): %s", d.Id(), err) } @@ -290,7 +290,7 @@ func dataSourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta keyIDs = append(keyIDs, path.Base(k.Href)) } d.Set("ssh_key_ids", keyIDs) - networkInfo := getNetworkInfoMetalGo(device.IpAddresses) + networkInfo := getNetworkInfo(device.IpAddresses) sort.SliceStable(networkInfo.Networks, func(i, j int) bool { famI := networkInfo.Networks[i]["family"].(int32) @@ -305,7 +305,7 @@ func dataSourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta d.Set("access_private_ipv4", networkInfo.PrivateIPv4) d.Set("access_public_ipv6", networkInfo.PublicIPv6) - ports := getPortsMetalGo(device.NetworkPorts) + ports := getPorts(device.NetworkPorts) d.Set("ports", ports) d.SetId(device.GetId()) diff --git a/equinix/helpers_device.go b/equinix/helpers_device.go index fd37d88f7..6d452fbfb 100644 --- a/equinix/helpers_device.go +++ b/equinix/helpers_device.go @@ -69,7 +69,7 @@ type NetworkInfo struct { PrivateIPv4 string } -func getNetworkInfoMetalGo(ips []metalv1.IPAssignment) NetworkInfo { +func getNetworkInfo(ips []metalv1.IPAssignment) NetworkInfo { ni := NetworkInfo{Networks: make([]map[string]interface{}, 0, 1)} for _, ip := range ips { network := map[string]interface{}{ @@ -99,37 +99,7 @@ func getNetworkInfoMetalGo(ips []metalv1.IPAssignment) NetworkInfo { return ni } -func getNetworkInfo(ips []*packngo.IPAddressAssignment) 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.Management { - if ip.AddressFamily == 4 { - if ip.Public { - ni.Host = ip.Address - ni.IPv4SubnetSize = ip.CIDR - ni.PublicIPv4 = ip.Address - } else { - ni.PrivateIPv4 = ip.Address - } - } else { - ni.PublicIPv6 = ip.Address - } - } - } - return ni -} - -func getNetworkTypeMetalGo(device *metalv1.Device) (*string, error) { +func getNetworkType(device *metalv1.Device) (*string, error) { pgDevice := packngo.Device{} res, err := device.MarshalJSON() if err == nil { @@ -153,7 +123,7 @@ func getNetworkRank(family int, public bool) int { return 3 } -func getPortsMetalGo(ps []metalv1.Port) []map[string]interface{} { +func getPorts(ps []metalv1.Port) []map[string]interface{} { ret := make([]map[string]interface{}, 0, 1) for _, p := range ps { port := map[string]interface{}{ @@ -168,21 +138,6 @@ func getPortsMetalGo(ps []metalv1.Port) []map[string]interface{} { return ret } -func getPorts(ps []packngo.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) retry.StateRefreshFunc { return func() (interface{}, string, error) { r, _, err := client.HardwareReservations.Get(reservationId, &packngo.GetOptions{Includes: []string{"device"}}) @@ -284,7 +239,7 @@ func ipAddressSchema() *schema.Resource { } func getDeviceMap(device metalv1.Device) map[string]interface{} { - networkInfo := getNetworkInfoMetalGo(device.IpAddresses) + networkInfo := getNetworkInfo(device.IpAddresses) sort.SliceStable(networkInfo.Networks, func(i, j int) bool { famI := int(*networkInfo.Networks[i]["family"].(*int32)) famJ := int(*networkInfo.Networks[j]["family"].(*int32)) @@ -296,7 +251,7 @@ func getDeviceMap(device metalv1.Device) map[string]interface{} { for _, k := range device.SshKeys { keyIDs = append(keyIDs, path.Base(k.GetHref())) } - ports := getPortsMetalGo(device.NetworkPorts) + ports := getPorts(device.NetworkPorts) return map[string]interface{}{ "hostname": device.GetHostname(), @@ -319,5 +274,6 @@ func getDeviceMap(device metalv1.Device) map[string]interface{} { "network": networkInfo.Networks, "ssh_key_ids": keyIDs, "ports": ports, + "sos_hostname": device.GetSos(), } } diff --git a/equinix/resource_metal_device.go b/equinix/resource_metal_device.go index 17522d7b2..756dadc26 100644 --- a/equinix/resource_metal_device.go +++ b/equinix/resource_metal_device.go @@ -27,7 +27,6 @@ var ( var ( deviceCommonIncludes = []string{"project", "metro", "facility", "hardware_reservation"} - deviceReadOptions = &packngo.GetOptions{Includes: deviceCommonIncludes} ) func resourceMetalDevice() *schema.Resource { @@ -664,7 +663,7 @@ func resourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta i d.Set("deployed_hardware_reservation_id", device.HardwareReservation.GetId()) } - networkType, err := getNetworkTypeMetalGo(device) + networkType, err := getNetworkType(device) if err != nil { return fmt.Errorf("[ERR] Error computing network type for device (%s): %s", d.Id(), err) } @@ -689,7 +688,7 @@ func resourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta i keyIDs = append(keyIDs, path.Base(k.Href)) } d.Set("ssh_key_ids", keyIDs) - networkInfo := getNetworkInfoMetalGo(device.IpAddresses) + networkInfo := getNetworkInfo(device.IpAddresses) sort.SliceStable(networkInfo.Networks, func(i, j int) bool { famI := networkInfo.Networks[i]["family"].(int32) @@ -704,7 +703,7 @@ func resourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta i d.Set("access_private_ipv4", networkInfo.PrivateIPv4) d.Set("access_public_ipv6", networkInfo.PublicIPv6) - ports := getPortsMetalGo(device.NetworkPorts) + ports := getPorts(device.NetworkPorts) d.Set("ports", ports) if networkInfo.Host != "" { From e378823920598a78d4ee9957d7730eb7a2db2c99 Mon Sep 17 00:00:00 2001 From: Charles Treatman Date: Thu, 5 Oct 2023 18:43:27 -0500 Subject: [PATCH 11/11] clean up some lingering pointer operations --- equinix/helpers_device.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/equinix/helpers_device.go b/equinix/helpers_device.go index 6d452fbfb..fcf26c1f8 100644 --- a/equinix/helpers_device.go +++ b/equinix/helpers_device.go @@ -241,10 +241,10 @@ func ipAddressSchema() *schema.Resource { func getDeviceMap(device metalv1.Device) map[string]interface{} { networkInfo := getNetworkInfo(device.IpAddresses) sort.SliceStable(networkInfo.Networks, func(i, j int) bool { - famI := int(*networkInfo.Networks[i]["family"].(*int32)) - famJ := int(*networkInfo.Networks[j]["family"].(*int32)) - pubI := *networkInfo.Networks[i]["public"].(*bool) - pubJ := *networkInfo.Networks[j]["public"].(*bool) + famI := int(networkInfo.Networks[i]["family"].(int32)) + famJ := int(networkInfo.Networks[j]["family"].(int32)) + pubI := networkInfo.Networks[i]["public"].(bool) + pubJ := networkInfo.Networks[j]["public"].(bool) return getNetworkRank(famI, pubI) < getNetworkRank(famJ, pubJ) }) keyIDs := []string{}