diff --git a/equinix/config.go b/equinix/config.go index da3fe0b0d..115aa6b89 100644 --- a/equinix/config.go +++ b/equinix/config.go @@ -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 @@ -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 } diff --git a/equinix/data_source_metal_device.go b/equinix/data_source_metal_device.go index 4b1107a5a..b867404ce 100644 --- a/equinix/data_source_metal_device.go +++ b/equinix/data_source_metal_device.go @@ -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 { @@ -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") @@ -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 } @@ -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 { @@ -266,27 +273,31 @@ 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) @@ -294,17 +305,17 @@ func dataSourceMetalDeviceRead(d *schema.ResourceData, meta interface{}) error { 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) } } diff --git a/equinix/data_source_metal_device_bgp_neighbors_acc_test.go b/equinix/data_source_metal_device_bgp_neighbors_acc_test.go new file mode 100644 index 000000000..20efd3611 --- /dev/null +++ b/equinix/data_source_metal_device_bgp_neighbors_acc_test.go @@ -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)) +} diff --git a/equinix/helpers_device.go b/equinix/helpers_device.go index cd88131c9..3bec153f9 100644 --- a/equinix/helpers_device.go +++ b/equinix/helpers_device.go @@ -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" @@ -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: @@ -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"}}) diff --git a/go.mod b/go.mod index 635ca3e5b..97dbc9436 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 3297f2a5b..f49180b10 100644 --- a/go.sum +++ b/go.sum @@ -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=