From 2680a89c11b77d2a0b5b2439aa9815c4af1d2f1e Mon Sep 17 00:00:00 2001 From: Marques Johansson Date: Fri, 18 Jun 2021 18:33:32 -0400 Subject: [PATCH 1/5] add Equinix Metal provider Signed-off-by: Marques Johansson --- README.md | 6 +- discover.go | 2 + go.mod | 5 +- go.sum | 8 +- .../equinixmetal/equinixmetal_discover.go | 175 ++++++++++++++ .../equinixmetal_discover_test.go | 226 ++++++++++++++++++ 6 files changed, 415 insertions(+), 7 deletions(-) create mode 100644 provider/equinixmetal/equinixmetal_discover.go create mode 100644 provider/equinixmetal/equinixmetal_discover_test.go diff --git a/README.md b/README.md index 50ae1882..26131e1a 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ function. * Triton [Config options](https://github.com/hashicorp/go-discover/blob/8b3ddf4/provider/triton/triton_discover.go#L17-L27) * vSphere [Config options](https://github.com/hashicorp/go-discover/blob/8b3ddf4/provider/vsphere/vsphere_discover.go#L145-L157) * Packet [Config options](https://github.com/hashicorp/go-discover/blob/8b3ddf4/provider/packet/packet_discover.go#L25-L40) + * Equinix Metal [Config options](https://github.com/hashicorp/go-discover/blob/master/provider/equinixmetal/equinixmetal_discover.go#L25-L35) The following providers are implemented in the go-discover/provider subdirectory but aren't automatically registered. If you want to support these providers, @@ -44,7 +45,7 @@ register them manually: HashiCorp maintains acceptance tests that regularly allocate and run tests with real resources to verify the behavior of several of these providers. Those -currently are: Amazon AWS, Microsoft Azure, Google Cloud, DigitalOcean, Triton, Scaleway, AliBaba Cloud, vSphere, and Packet.net. +currently are: Amazon AWS, Microsoft Azure, Google Cloud, DigitalOcean, Triton, Scaleway, AliBaba Cloud, vSphere, and Equinix Metal (Packet). ### Config Example @@ -91,6 +92,9 @@ provider=vsphere category_name=consul-role tag_name=consul-server host=... user= # Packet provider=packet auth_token=token project=uuid url=... address_type=... +# Equinix Metal +provider=equinixmetal auth_token=token project=uuid url=... address_type=... + # Kubernetes provider=k8s label_selector="app = consul-server" ``` diff --git a/discover.go b/discover.go index 369fd415..61826a5c 100644 --- a/discover.go +++ b/discover.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/go-discover/provider/aws" "github.com/hashicorp/go-discover/provider/azure" "github.com/hashicorp/go-discover/provider/digitalocean" + "github.com/hashicorp/go-discover/provider/equinixmetal" "github.com/hashicorp/go-discover/provider/gce" "github.com/hashicorp/go-discover/provider/linode" "github.com/hashicorp/go-discover/provider/mdns" @@ -59,6 +60,7 @@ var Providers = map[string]Provider{ "triton": &triton.Provider{}, "vsphere": &vsphere.Provider{}, "packet": &packet.Provider{}, + "equinixmetal": &equinixmetal.Provider{}, } // Discover looks up metadata in different cloud environments. diff --git a/go.mod b/go.mod index 510efa99..7260e732 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/aws/aws-sdk-go v1.25.41 github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661 github.com/digitalocean/godo v1.7.5 - github.com/dnaeon/go-vcr v1.0.1 // indirect github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect github.com/googleapis/gnostic v0.2.0 // indirect github.com/gophercloud/gophercloud v0.1.0 @@ -23,13 +22,13 @@ require ( github.com/linode/linodego v0.7.1 github.com/mitchellh/go-homedir v1.1.0 github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 - github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c + github.com/packethost/packngo v0.15.0 github.com/pkg/errors v0.8.0 // indirect github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/sirupsen/logrus v1.0.6 // indirect github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d - github.com/stretchr/testify v1.4.0 + github.com/stretchr/testify v1.5.1 github.com/tencentcloud/tencentcloud-sdk-go v1.0.162 github.com/vmware/govmomi v0.18.0 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 diff --git a/go.sum b/go.sum index 5e516ced..79195b36 100644 --- a/go.sum +++ b/go.sum @@ -161,8 +161,8 @@ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c h1:vwpFWvAO8DeIZfFeqASzZfsxuWPno9ncAebBEP0N3uE= -github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otzZQXgoO96RTzDB/Hycg0qZcXZsWJGJRSXbmEIJ+4M= +github.com/packethost/packngo v0.15.0 h1:nyiVHJAhQdt37Vf141vvm83niZHRKbBs9FmFB+QRgd4= +github.com/packethost/packngo v0.15.0/go.mod h1:YrtUNN9IRjjqN6zK+cy2IYoi3EjHfoWTWxJkI1I1Vk0= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -182,8 +182,9 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/tencentcloud/tencentcloud-sdk-go v1.0.162 h1:8fDzz4GuVg4skjY2B0nMN7h6uN61EDVkuLyI2+qGHhI= github.com/tencentcloud/tencentcloud-sdk-go v1.0.162/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI= github.com/vmware/govmomi v0.18.0 h1:f7QxSmP7meCtoAmiKZogvVbLInT+CZx6Px6K5rYsJZo= @@ -194,6 +195,7 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/provider/equinixmetal/equinixmetal_discover.go b/provider/equinixmetal/equinixmetal_discover.go new file mode 100644 index 00000000..3183d3fb --- /dev/null +++ b/provider/equinixmetal/equinixmetal_discover.go @@ -0,0 +1,175 @@ +package equinixmetal + +import ( + "fmt" + "log" + "os" + "strings" + + "github.com/packethost/packngo" +) + +const baseURL = "https://api.equinix.com/metal/v1/" + +var ( + addressTypes = []string{packngo.PublicIPv4, packngo.PrivateIPv4, packngo.PublicIPv6} + defaultAddressType = packngo.PrivateIPv4 +) + +// Provider struct +type Provider struct { + userAgent string +} + +// SetUserAgent setter +func (p *Provider) SetUserAgent(s string) { + p.userAgent = s +} + +// Help function +func (p *Provider) Help() string { + return fmt.Sprintf(`Equinix Metal: + provider: "equinixmetal" + project: UUID of metal project. Required + auth_token: Equinix Metal authentication token. Required + url: Equinix Metal REST URL. Optional + address_type: Address type, one of: %s. Defaults to %q. Optional + facility: Filter for specific facility (Examples: "sv15,ny5") + metro: Filter for specific metro (Examples: "sv,ny") + tag: Filter by tag (Examples: "tag1,tag2") + + Variables can also be provided by environmental variables: + export METAL_PROJECT for project + export METAL_URL for url + export METAL_AUTH_TOKEN for auth_token +`, strings.Join(addressTypes, ", "), defaultAddressType) +} + +// Addrs function +func (p *Provider) Addrs(args map[string]string, l *log.Logger) ([]string, error) { + authToken := argsOrEnv(args, "auth_token", "METAL_AUTH_TOKEN") + projectID := argsOrEnv(args, "project", "METAL_PROJECT") + metalURL := argsOrEnv(args, "url", "METAL_URL") + addressType := args["address_type"] + metalFacilities := args["facility"] + metalMetros := args["metro"] + metalTags := args["tag"] + + if !Include(addressTypes, addressType) { + l.Printf("[INFO] discover-metal: Address type %s is not supported. Valid values are {%s}. Falling back to '%s'", addressType, strings.Join(addressTypes, ","), defaultAddressType) + addressType = defaultAddressType + } + + includeMetros := includeArgs(metalMetros) + includeFacilities := includeArgs(metalFacilities) + includeTags := includeArgs(metalTags) + + c, err := client(p.userAgent, metalURL, authToken) + if err != nil { + return nil, fmt.Errorf("discover-metal: Initializing Equinix Metal client failed: %s", err) + } + + var devices []packngo.Device + + if projectID == "" { + return nil, fmt.Errorf("discover-metal: 'project' parameter must be provided") + } + + getOpts := &packngo.GetOptions{} + getOpts.Including("facility", "metro", "ip_addresses") + getOpts.Excluding("ssh_keys", "project") + devices, _, err = c.Devices.List(projectID, getOpts) + if err != nil { + return nil, fmt.Errorf("discover-metal: Fetching Equinix Metal devices failed: %s", err) + } + + var addrs []string + for _, d := range devices { + + if len(includeFacilities) > 0 && !Include(includeFacilities, d.Facility.Code) { + continue + } + + if len(includeMetros) > 0 && !Include(includeMetros, d.Metro.Code) { + continue + } + + if len(includeTags) > 0 && !Any(d.Tags, func(v string) bool { return Include(includeTags, v) }) { + continue + } + + for _, n := range d.Network { + if ipMatchesType(addressType, n) { + addrs = append(addrs, n.Address) + } + } + } + return addrs, nil +} + +func ipMatchesType(addressType string, n *packngo.IPAddressAssignment) bool { + switch addressType { + case packngo.PublicIPv4: + return n.Public && n.AddressFamily == 4 + case packngo.PublicIPv6: + return n.Public && n.AddressFamily == 6 + case packngo.PrivateIPv4: + return !n.Public && n.AddressFamily == 4 + default: + return false + } +} + +func client(useragent, url, token string) (*packngo.Client, error) { + if url == "" { + url = baseURL + } + + client, err := packngo.NewClientWithBaseURL(useragent, token, nil, url) + if err == nil { + client.UserAgent = fmt.Sprintf("%s %s", useragent, client.UserAgent) + } + return client, err +} + +func argsOrEnv(args map[string]string, key, env string) string { + if value := args[key]; value != "" { + return value + } + return os.Getenv(env) +} + +func includeArgs(s string) []string { + var include []string + for _, localstring := range strings.Split(s, ",") { + if len(localstring) > 0 { + include = append(include, localstring) + } + } + return include +} + +// Index returns the first index of the target string t, or -1 if no match is found. +func Index(vs []string, t string) int { + for i, v := range vs { + if v == t { + return i + } + } + return -1 +} + +// Include returns true if the target string t is in the slice. +func Include(vs []string, t string) bool { + return Index(vs, t) >= 0 +} + +//Any returns true if one of the strings in the slice satisfies the predicate f. +func Any(vs []string, f func(string) bool) bool { + for _, v := range vs { + if f(v) { + return true + } + } + return false +} diff --git a/provider/equinixmetal/equinixmetal_discover_test.go b/provider/equinixmetal/equinixmetal_discover_test.go new file mode 100644 index 00000000..746bed61 --- /dev/null +++ b/provider/equinixmetal/equinixmetal_discover_test.go @@ -0,0 +1,226 @@ +package equinixmetal_test + +import ( + "log" + "os" + "testing" + + discover "github.com/hashicorp/go-discover" + "github.com/hashicorp/go-discover/provider/equinixmetal" +) + +var _ discover.Provider = (*equinixmetal.Provider)(nil) +var _ discover.ProviderWithUserAgent = (*equinixmetal.Provider)(nil) + +func TestAddrsDefault(t *testing.T) { + args := discover.Config{ + "provider": "metal", + "auth_token": os.Getenv("METAL_AUTH_TOKEN"), + "project": os.Getenv("METAL_PROJECT"), + } + + if args["auth_token"] == "" { + t.Skip("Equinix Metal credentials missing") + } + + if args["project"] == "" { + t.Skip("Equinix Metal project UUID missing") + } + + p := equinixmetal.Provider{} + + l := log.New(os.Stderr, "", log.LstdFlags) + addrs, err := p.Addrs(args, l) + + if err != nil { + t.Fatal(err) + } + + if len(addrs) != 4 { + t.Fatalf("bad: %v", addrs) + } +} + +func TestAddrsPublicV6(t *testing.T) { + args := discover.Config{ + "provider": "metal", + "auth_token": os.Getenv("METAL_AUTH_TOKEN"), + "project": os.Getenv("METAL_PROJECT"), + "address_type": "public_v6", + } + + if args["auth_token"] == "" { + t.Skip("Equinix Metal credentials missing") + } + + if args["project"] == "" { + t.Skip("Equinix Metal project UUID missing") + } + + p := equinixmetal.Provider{} + + l := log.New(os.Stderr, "", log.LstdFlags) + addrs, err := p.Addrs(args, l) + + if err != nil { + t.Fatal(err) + } + + if len(addrs) != 4 { + t.Fatalf("bad: %v", addrs) + } +} + +func TestAddrsPublicV4(t *testing.T) { + args := discover.Config{ + "provider": "metal", + "auth_token": os.Getenv("METAL_AUTH_TOKEN"), + "project": os.Getenv("METAL_PROJECT"), + "address_type": "public_v4", + } + + if args["auth_token"] == "" { + t.Skip("Equinix Metal credentials missing") + } + + if args["project"] == "" { + t.Skip("Equinix Metal project UUID missing") + } + + p := equinixmetal.Provider{} + + l := log.New(os.Stderr, "", log.LstdFlags) + addrs, err := p.Addrs(args, l) + + if err != nil { + t.Fatal(err) + } + + if len(addrs) != 4 { + t.Fatalf("bad: %v", addrs) + } +} + +func TestAddrsFacilityInclude(t *testing.T) { + args := discover.Config{ + "provider": "metal", + "auth_token": os.Getenv("METAL_AUTH_TOKEN"), + "project": os.Getenv("METAL_PROJECT"), + "address_type": "private_v4", + "facility": "ewr1", + } + + if args["auth_token"] == "" { + t.Skip("Equinix Metal credentials missing") + } + + if args["project"] == "" { + t.Skip("Equinix Metal project UUID missing") + } + + p := equinixmetal.Provider{} + + l := log.New(os.Stderr, "", log.LstdFlags) + addrs, err := p.Addrs(args, l) + + if err != nil { + t.Fatal(err) + } + + if len(addrs) != 1 { + t.Fatalf("bad: %v", addrs) + } +} + +func TestAddrsFacilityIncludeMulti(t *testing.T) { + args := discover.Config{ + "provider": "metal", + "auth_token": os.Getenv("METAL_AUTH_TOKEN"), + "project": os.Getenv("METAL_PROJECT"), + "address_type": "private_v4", + "facility": "ewr1,ams1", + } + + if args["auth_token"] == "" { + t.Skip("Equinix Metal credentials missing") + } + + if args["project"] == "" { + t.Skip("Equinix Metal project UUID missing") + } + + p := equinixmetal.Provider{} + + l := log.New(os.Stderr, "", log.LstdFlags) + addrs, err := p.Addrs(args, l) + + if err != nil { + t.Fatal(err) + } + + if len(addrs) != 2 { + t.Fatalf("bad: %v", addrs) + } +} + +func TestAddrsTagInclude(t *testing.T) { + args := discover.Config{ + "provider": "metal", + "auth_token": os.Getenv("METAL_AUTH_TOKEN"), + "project": os.Getenv("METAL_PROJECT"), + "address_type": "private_v4", + "tag": "tag1", + } + + if args["auth_token"] == "" { + t.Skip("Equinix Metal credentials missing") + } + + if args["project"] == "" { + t.Skip("Equinix Metal project UUID missing") + } + + p := equinixmetal.Provider{} + + l := log.New(os.Stderr, "", log.LstdFlags) + addrs, err := p.Addrs(args, l) + + if err != nil { + t.Fatal(err) + } + + if len(addrs) != 2 { + t.Fatalf("bad: %v", addrs) + } +} + +func TestAddrsTagIncludeMulti(t *testing.T) { + args := discover.Config{ + "provider": "metal", + "auth_token": os.Getenv("METAL_AUTH_TOKEN"), + "project": os.Getenv("METAL_PROJECT"), + "address_type": "private_v4", + "tag": "tag1,tag2", + } + + if args["auth_token"] == "" { + t.Skip("Equinix Metal credentials missing") + } + + if args["project"] == "" { + t.Skip("Equinix Metal project UUID missing") + } + + p := equinixmetal.Provider{} + + l := log.New(os.Stderr, "", log.LstdFlags) + addrs, err := p.Addrs(args, l) + + if err != nil { + t.Fatal(err) + } + + if len(addrs) != 3 { + t.Fatalf("bad: %v", addrs) + } +} From 5e1cd9bc836af51801480b1ce44c5afa550de8fe Mon Sep 17 00:00:00 2001 From: Marques Johansson Date: Fri, 18 Jun 2021 18:34:13 -0400 Subject: [PATCH 2/5] add packngo vendor for equinixmetal provider Signed-off-by: Marques Johansson --- .../github.com/packethost/packngo/.gitignore | 30 + .../packethost/packngo/CHANGELOG.md | 12 + .../packethost/packngo/CONTRIBUTING.md | 119 ++++ .../vendor/github.com/packethost/packngo/DCO | 37 ++ .../github.com/packethost/packngo/LICENSE.txt | 56 ++ .../github.com/packethost/packngo/Makefile | 24 + .../github.com/packethost/packngo/OWNERS.md | 26 + .../github.com/packethost/packngo/README.md | 151 +++++ .../github.com/packethost/packngo/RELEASE.md | 40 ++ .../github.com/packethost/packngo/SUPPORT.md | 17 + .../packethost/packngo/api_call_options.go | 194 ++++++ .../github.com/packethost/packngo/apikeys.go | 176 +++++ .../github.com/packethost/packngo/batches.go | 106 +++ .../packethost/packngo/bgp_configs.go | 85 +++ .../packethost/packngo/bgp_sessions.go | 99 +++ .../packethost/packngo/billing_address.go | 7 + .../packethost/packngo/capacities.go | 103 +++ .../packethost/packngo/code-of-conduct.md | 3 + .../packethost/packngo/connections.go | 218 +++++++ .../packethost/packngo/device_ports.go | 323 +++++++++ .../github.com/packethost/packngo/devices.go | 616 ++++++++++++++++++ .../github.com/packethost/packngo/doc.go | 3 + .../github.com/packethost/packngo/email.go | 89 +++ .../github.com/packethost/packngo/events.go | 81 +++ .../packethost/packngo/facilities.go | 54 ++ .../github.com/packethost/packngo/go.mod | 9 + .../github.com/packethost/packngo/go.sum | 21 + .../packngo/hardware_reservations.go | 92 +++ .../github.com/packethost/packngo/ip.go | 245 +++++++ .../github.com/packethost/packngo/metros.go | 42 ++ .../packethost/packngo/notifications.go | 94 +++ .../packethost/packngo/operatingsystems.go | 42 ++ .../packethost/packngo/organizations.go | 168 +++++ .../github.com/packethost/packngo/packngo.go | 422 ++++++++++++ .../packethost/packngo/payment_methods.go | 71 ++ .../github.com/packethost/packngo/plans.go | 145 +++++ .../github.com/packethost/packngo/ports.go | 208 ++++++ .../github.com/packethost/packngo/projects.go | 182 ++++++ .../github.com/packethost/packngo/rate.go | 12 + .../packethost/packngo/spotmarket.go | 92 +++ .../packethost/packngo/spotmarketrequest.go | 120 ++++ .../github.com/packethost/packngo/sshkeys.go | 143 ++++ .../packethost/packngo/timestamp.go | 38 ++ .../packethost/packngo/two_factor_auth.go | 56 ++ .../github.com/packethost/packngo/user.go | 101 +++ .../github.com/packethost/packngo/utils.go | 156 +++++ .../github.com/packethost/packngo/version.go | 36 + .../packethost/packngo/virtualcircuits.go | 102 +++ .../packethost/packngo/virtualnetworks.go | 108 +++ .../github.com/packethost/packngo/volumes.go | 231 +++++++ .../github.com/packethost/packngo/vpn.go | 74 +++ provider/equinixmetal/vendor/vendor.json | 13 + 52 files changed, 5692 insertions(+) create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/.gitignore create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/CHANGELOG.md create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/CONTRIBUTING.md create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/DCO create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/LICENSE.txt create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/Makefile create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/OWNERS.md create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/README.md create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/RELEASE.md create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/SUPPORT.md create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/api_call_options.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/apikeys.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/batches.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/bgp_configs.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/bgp_sessions.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/billing_address.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/capacities.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/code-of-conduct.md create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/connections.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/device_ports.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/devices.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/doc.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/email.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/events.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/facilities.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/go.mod create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/go.sum create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/hardware_reservations.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/ip.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/metros.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/notifications.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/operatingsystems.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/organizations.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/packngo.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/payment_methods.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/plans.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/ports.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/projects.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/rate.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/spotmarket.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/spotmarketrequest.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/sshkeys.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/timestamp.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/two_factor_auth.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/user.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/utils.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/version.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/virtualcircuits.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/virtualnetworks.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/volumes.go create mode 100644 provider/equinixmetal/vendor/github.com/packethost/packngo/vpn.go create mode 100644 provider/equinixmetal/vendor/vendor.json diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/.gitignore b/provider/equinixmetal/vendor/github.com/packethost/packngo/.gitignore new file mode 100644 index 00000000..7cc2daad --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/.gitignore @@ -0,0 +1,30 @@ +### Go template +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +*.swp + +# IDEs +.idea**/** +.vscode diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/CHANGELOG.md b/provider/equinixmetal/vendor/github.com/packethost/packngo/CHANGELOG.md new file mode 100644 index 00000000..53b6de27 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +This project adheres to [Semantic +Versioning](http://semver.org/spec/v2.0.0.html). + +All notable changes to this project will be documented at +. Drafts release notes may be +used to track features that will be available in future releases. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/), +breaking changes, additions, removals, and fixes should be pointed out in the +release notes. diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/CONTRIBUTING.md b/provider/equinixmetal/vendor/github.com/packethost/packngo/CONTRIBUTING.md new file mode 100644 index 00000000..368e3a4a --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/CONTRIBUTING.md @@ -0,0 +1,119 @@ +# Contributing + +Thanks for your interest in improving this project! Before we get technical, +make sure you have reviewed the [code of conduct](code-of-conduct.md), +[Developer Certificate of Origin](DCO), and [OWNERS](OWNERS.md) files. Code will +be licensed according to [LICENSE.txt](LICENSE.txt). + +## Pull Requests + +When creating a pull request, please refer to an open issue. If there is no +issue open for the pull request you are creating, please create one. Frequently, +pull requests may be merged or closed while the underlying issue being addressed +is not fully addressed. Issues are a place to discuss the problem in need of a +solution. Pull requests are a place to discuss an implementation of one +particular answer to that problem. A pull request may not address all (or any) +of the problems expressed in the issue, so it is important to track these +separately. + +## Code Quality + +### Documentation + +All public functions and variables should include at least a short description +of the functionality they provide. Comments should be formatted according to +. + +Documentation at will be +generated from these comments. + +The documentation provided for packngo fields and functions should be at or +better than the quality provided at . +When the API documentation provides a lengthy description, a linking to the +related API documentation will benefit users. + +### Linters + +`golangci-lint` is used to verify that the style of the code remains consistent. + +Before committing, it's a good idea to run `goimports -w .`. +([goimports](https://pkg.go.dev/golang.org/x/tools/cmd/goimports?tab=doc)) and +`gofmt -w *.go`. ([gofmt](https://golang.org/cmd/gofmt/)) + +`make lint` can be used to verify style before creating a pull request. + +## Building and Testing + +The [Makefile](./Makefile) contains the targets to build, lint and test: + +```sh +make build +make lint +make test +``` + +These normally will be run in a docker image of golang. To run locally, just run +with `BUILD=local`: + +```sh +make build BUILD=local +make lint BUILD=local +make test BUILD=local +``` + +### Acceptance Tests + +If you want to run tests against the actual Equinix Metal API, you must set the +environment variable `PACKET_TEST_ACTUAL_API` to a non-empty string and set +`PACKNGO_TEST_RECORDER` to `disabled`. The device tests wait for the device +creation, so it's best to run a few in parallel. + +To run a particular test, you can do + +```sh +PACKNGO_TEST_ACTUAL_API=1 go test -v -run=TestAccDeviceBasic --timeout=2h +``` + +If you want to see HTTP requests, set the `PACKNGO_DEBUG` env var to non-empty +string, for example: + +```sh +PACKNGO_DEBUG=1 PACKNGO_TEST_ACTUAL_API=1 go test -v -run=TestAccVolumeUpdate +``` + +### Test Fixtures + +By default, `go test ./...` will skip most of the tests unless +`PACKNGO_TEST_ACTUAL_API` is non-empty. + +With the `PACKNGO_TEST_ACTUAL_API` environment variable set, tests will be run +against the Equinix Metal API, creating real infrastructure and incurring costs. + +The `PACKNGO_TEST_RECORDER` variable can be used to record and playback API +responses to test code changes without the delay and costs of making actual API +calls. When unset, `PACKNGO_TEST_RECORDER` acts as though it was set to +`disabled`. This is the default behavior. This default behavior may change in +the future once fixtures are available for all tests. + +When `PACKNGO_TEST_RECORDER` is set to `play`, tests will playback API responses +from recorded HTTP response fixtures. This is idea for refactoring and making +changes to request and response handling without introducing changes to the data +sent or received by the Equinix Metal API. + +When adding support for new end-points, recorded test sessions should be added. +Record the HTTP interactions to fixtures by setting the environment variable +`PACKNGO_TEST_RECORDER` to `record`. + +The fixtures are automatically named according to the test they were run from. +They are placed in `fixtures/`. The API token used during authentication is +automatically removed from these fixtures. Nonetheless, caution should be +exercised before committing any fixtures into the project. Account details +includes API tokens, contact, and payment details could easily be leaked by +committing fixtures that haven't been thoroughly reviewed. + +### Automation (CI/CD) + +Today, Drone tests pull requests using tests defined in +[.drone.yml](.drone.yml). + +See [RELEASE.md](RELEASE.md) for details on the release process. diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/DCO b/provider/equinixmetal/vendor/github.com/packethost/packngo/DCO new file mode 100644 index 00000000..068953d4 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/DCO @@ -0,0 +1,37 @@ +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. + diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/LICENSE.txt b/provider/equinixmetal/vendor/github.com/packethost/packngo/LICENSE.txt new file mode 100644 index 00000000..57c50110 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/LICENSE.txt @@ -0,0 +1,56 @@ +Copyright (c) 2014 The packngo AUTHORS. All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +====================== +Portions of the client are based on code at: +https://github.com/google/go-github/ and +https://github.com/digitalocean/godo + +Copyright (c) 2013 The go-github AUTHORS. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/Makefile b/provider/equinixmetal/vendor/github.com/packethost/packngo/Makefile new file mode 100644 index 00000000..90ceffa3 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/Makefile @@ -0,0 +1,24 @@ +IMG ?= golang:1.15 + +# enable go modules, disabled CGO + +GOENV ?= GO111MODULE=on CGO_ENABLED=0 +export GO111MODULE=on +export CGO_ENABLED=0 + +# we build in a docker image, unless we are set to BUILD=local +GO ?= docker run --rm -v $(PWD):/app -w /app $(IMG) env $(GOENV) +ifeq ($(BUILD),local) +GO = +endif + + +build: + $(GO) go build -i -v ./... + +lint: + @docker run --rm -v $(CURDIR):/app -w /app golangci/golangci-lint:v1.34.1 golangci-lint run -v + +test: + $(GO) test ./... + diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/OWNERS.md b/provider/equinixmetal/vendor/github.com/packethost/packngo/OWNERS.md new file mode 100644 index 00000000..8b002e98 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/OWNERS.md @@ -0,0 +1,26 @@ +# Owners + +This project is governed by [Equinix Metal] and benefits from a community of users that +collaborate and contribute to its use in Go powered projects, such as the [Equinix Metal +Terraform provider], [Docker machine driver], Kubernetes drivers for [CSI] and [CCM], +the [Equinix Metal CLI], and others. + +Members of the Equinix Metal Github organization will strive to triage issues in a +timely manner, see [SUPPORT.md] for details. + +See the [packethost/standards glossary] for more details about this file. + +## Maintainers + +Maintainers of this repository are defined within the [CODEOWNERS] file. + +[Equinix Metal]: https://metal.equinix.com +[Equinix Metal Terraform provider]: https://github.com/packethost/terraform-provider-packet +[Docker machine driver]: https://github.com/packethost/docker-machine-driver-packet +[CSI]: https://github.com/packethost/csi-packet +[CCM]: https://github.com/packethost/packet-ccm +[Equinix Metal CLI]: https://github.com/packethost/packet-cli +[SUPPORT.md]: SUPPORT.md +[packethost/standards +glossary]: https://github.com/packethost/standards/blob/master/glossary.md#ownersmd +[CODEOWNERS]: CODEOWNERS diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/README.md b/provider/equinixmetal/vendor/github.com/packethost/packngo/README.md new file mode 100644 index 00000000..0678b780 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/README.md @@ -0,0 +1,151 @@ +# packngo + +[![](https://img.shields.io/badge/stability-maintained-green.svg)](https://github.com/packethost/standards/blob/master/maintained-statement.md) +[![Release](https://img.shields.io/github/v/release/packethost/packngo)](https://github.com/packethost/packngo/releases/latest) +[![GoDoc](https://godoc.org/github.com/packethost/packngo?status.svg)](https://godoc.org/github.com/packethost/packngo) +[![Go Report Card](https://goreportcard.com/badge/github.com/packethost/packngo)](https://goreportcard.com/report/github.com/packethost/packngo) +[![Slack](https://slack.equinixmetal.com/badge.svg)](https://slack.equinixmetal.com/) +[![Twitter Follow](https://img.shields.io/twitter/follow/equinixmetal.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=equinixmetal) + +A Golang client for the Equinix Metal API. ([Packet is now Equinix Metal](https://blog.equinix.com/blog/2020/10/06/equinix-metal-metal-and-more/)) + +## Installation + +To import this library into your Go project: + +```go +import "github.com/packethost/packngo" +``` + +Reference a particular version with: + +```sh +go get github.com/packethost/packngo@v0.2.0 +``` + +## Stability and Compatibility + +This repository is [Maintained](https://github.com/packethost/standards/blob/master/maintained-statement.md) meaning that this software is supported by Equinix Metal and its community - available to use in production environments. + +Packngo is currently provided with a major version of [v0](https://blog.golang.org/v2-go-modules). We'll try to avoid breaking changes to this library, but they will certainly happen as we work towards a stable v1 library. See [CHANGELOG.md](CHANGELOG.md) for details on the latest additions, removals, fixes, and breaking changes. + +While packngo provides an interface to most of the [Equinix Metal API](https://metal.equinix.com/developers/api/), the API is regularly adding new features. To request or contribute support for more API end-points or added fields, [create an issue](https://github.com/packethost/packngo/issues/new). + +See [SUPPORT.md](SUPPORT.md) for any other issues. + +## Usage + +To authenticate to the Equinix Metal API, you must have your API token exported in env var `PACKET_AUTH_TOKEN`. + +This code snippet initializes Equinix Metal API client, and lists your Projects: + +```go +package main + +import ( + "log" + + "github.com/packethost/packngo" +) + +func main() { + c, err := packngo.NewClient() + if err != nil { + log.Fatal(err) + } + + ps, _, err := c.Projects.List(nil) + if err != nil { + log.Fatal(err) + } + for _, p := range ps { + log.Println(p.ID, p.Name) + } +} + +``` + +This library is used by the official [terraform-provider-packet](https://github.com/packethost/terraform-provider-packet). + +You can also learn a lot from the `*_test.go` sources. Almost all out tests touch the Equinix Metal API, so you can see how auth, querying and POSTing works. For example [devices_test.go](devices_test.go). + +
+Linked Resources + +### Linked resources in Get\* and List\* functions + +The Equinix Metal API includes references to related entities for a wide selection of resource types, indicated by `href` fields. The Equinix Metal API allows for these entities to be included in the API response, saving the user from making more round-trip API requests. This is useful for linked resources, e.g members of a project, devices in a project. Similarly, by excluding entities that are included by default, you can reduce the API response time and payload size. + +Control of this behavior is provided through [common attributes](https://metal.equinix.com/developers/api/common-parameters/) that can be used to toggle, by field name, which referenced resources will be included as values in API responses. The API exposes this feature through `?include=` and `?exclude=` query parameters which accept a comma-separated list of field names. These field names can be dotted to reference nested entities. + +Most of the packngo `Get` functions take references to `GetOptions` parameters (or `ListOptions` for `List` functions). These types include an `Include` and `Exclude` slice that will be converted to query parameters upon request. + +For example, if you want to list users in a project, you can fetch the project via `Projects.Get(pid, nil)` call. The result of this call will be a `Project` which has a `Users []User` attribute. The items in the `[]User` slice only have a non-zero URL attribute, the rest of the fields will be type defaults. You can then parse the ID of the User resources and fetch them consequently. + +Optionally, you can use the ListOptions struct in the project fetch call to include the Users (`members` JSON tag). Then, every item in the `[]User` slice will have all (not only the `Href`) attributes populated. + +```go +Projects.Get(pid, &packngo.ListOptions{Includes: []{'members'}}) +``` + +The following is a more comprehensive illustration of Includes and Excludes. + +```go +import ( + "log" + + "github.com/packethost/packngo" +) + +func listProjectsAndUsers(lo *packngo.ListOptions) { + c, err := packngo.NewClient() + if err != nil { + log.Fatal(err) + } + + ps, _, err := c.Projects.List(lo) + if err != nil { + log.Fatal(err) + } + log.Printf("Listing for listOptions %+v\n", lo) + for _, p := range ps { + log.Printf("project resource %s has %d users", p.Name, len(p.Users)) + for _, u := range p.Users { + if u.Email != "" && u.FullName != "" { + log.Printf(" user %s has email %s\n", u.FullName, u.Email) + } else { + log.Printf(" only got user link %s\n", u.URL) + } + } + } +} + +func main() { + loMembers := &packngo.ListOptions{Includes: []string{"members"}} + loMembersOut := &packngo.ListOptions{Excludes: []string{"members"}} + listProjectsAndUsers(loMembers) + listProjectsAndUsers(nil) + listProjectsAndUsers(loMembersOut) +} +``` + +
+ +### Deprecation and Sunset + +If the Equinix Metal API returns a [RFC-8594](https://tools.ietf.org/html/rfc8594) `Deprecation` or `Sunset` header, packngo will log this header to stderr with any accompanied `Link` headers. + +Example: + +```console +WARNING: "POST /deprecate-and-sunset" reported deprecation +WARNING: "POST /deprecate-and-sunset" reported sunsetting on Sat, 1 Aug 2020 23:59:59 GMT +WARNING: See for deprecation details +WARNING: See for sunset details +WARNING: See for sunset details +WARNING: See for deprecation details +``` + +## Contributing + +See [CONTIBUTING.md](CONTRIBUTING.md). diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/RELEASE.md b/provider/equinixmetal/vendor/github.com/packethost/packngo/RELEASE.md new file mode 100644 index 00000000..74a9ac2d --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/RELEASE.md @@ -0,0 +1,40 @@ +# Releases + +This file serves to provide guidance and act as a checklist for any maintainers +to this project, now or in the future. This file should be updated with any +changes to the process. Automated processes should be described well enough that +they can be run in the absence of that automation. + +* See [CHANGELOG.md](CHANGELOG.md) for notes on versioning. +* Fetch the latest origin branches: + + ```sh + git fetch origin + git checkout master + git pull + ``` + +* Verify that your branch matches the upstream branch: + + ```sh + git branch --points-at=master -r | grep origin/master >/dev/null || echo "master differs from origin/master" + ``` + +* Tag `master` with a semver tag that suits the level of changes + introduced: + + ```sh + git tag -m "v0.8.0" -a v0.8.0 master # use -s if gpg is available + ``` +* Push the tag: + + ```sh + git push --tags origin master v0.8.0 + ``` +* Create a release from the tag (include a keepachangelog.com formatted description): + + (use the correct + version) + +Releases can be followed through the GitHub Atom feed at +. diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/SUPPORT.md b/provider/equinixmetal/vendor/github.com/packethost/packngo/SUPPORT.md new file mode 100644 index 00000000..86f8401f --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/SUPPORT.md @@ -0,0 +1,17 @@ +# Support + +Please [open a GitHub Issue] stating any problems that you encounter using this +software. Be sure to leave a descriptive report of the problem, including the +environment you were running in and the expected behavior. Be careful not to +include any sensitive information such as API Keys, IP addresses or their +ranges, or any resource identifiers (UUID or ID) of your organizations, +projects, or devices. + +As an open-source project, the priority, timing, or eventual resolution is not +guaranteed. Issues will be addressed based on priorities that may or may not +be reflected in Github or issue comments. + +For other forms of support, including our Slack community, visit +. + +[open a GitHub Issue]: https://github.com/packethost/packngo/issues/new \ No newline at end of file diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/api_call_options.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/api_call_options.go new file mode 100644 index 00000000..381b5b84 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/api_call_options.go @@ -0,0 +1,194 @@ +package packngo + +import ( + "fmt" + "net/url" + "strconv" + "strings" +) + +type ListSortDirection string + +const ( + SortDirectionAsc ListSortDirection = "asc" + SortDirectionDesc ListSortDirection = "desc" +) + +// GetOptions are options common to Equinix Metal API GET requests +type GetOptions struct { + // Includes are a list of fields to expand in the request results. + // + // For resources that contain collections of other resources, the Equinix Metal API + // will only return the `Href` value of these resources by default. In + // nested API Go types, this will result in objects that have zero values in + // all fiends except their `Href` field. When an object's associated field + // name is "included", the returned fields will be Uumarshalled into the + // nested object. Field specifiers can use a dotted notation up to three + // references deep. (For example, "memberships.projects" can be used in + // ListUsers.) + Includes []string `url:"include,omitempty,comma"` + + // Excludes reduce the size of the API response by removing nested objects + // that may be returned. + // + // The default behavior of the Equinix Metal API is to "exclude" fields, but some + // API endpoints have an "include" behavior on certain fields. Nested Go + // types unmarshalled into an "excluded" field will only have a values in + // their `Href` field. + Excludes []string `url:"exclude,omitempty,comma"` + + // Page is the page of results to retrieve for paginated result sets + Page int `url:"page,omitempty"` + + // PerPage is the number of results to return per page for paginated result + // sets, + PerPage int `url:"per_page,omitempty"` + + // Search is a special API query parameter that, for resources that support + // it, will filter results to those with any one of various fields matching + // the supplied keyword. For example, a resource may have a defined search + // behavior matches either a name or a fingerprint field, while another + // resource may match entirely different fields. Search is currently + // implemented for SSHKeys and uses an exact match. + Search string `url:"search,omitempty"` + + SortBy string `url:"sort_by,omitempty"` + SortDirection ListSortDirection `url:"sort_direction,omitempty"` + + Meta meta `url:"-"` +} + +type ListOptions = GetOptions +type SearchOptions = GetOptions + +type QueryAppender interface { + WithQuery(path string) string // we use this in all List functions (urlQuery) + GetPage() int // we use this in List + Including(...string) // we use this in Device List to add facility + Excluding(...string) +} + +// GetOptions returns GetOptions from GetOptions (and is nil-receiver safe) +func (g *GetOptions) GetOptions() *GetOptions { + getOpts := GetOptions{} + if g != nil { + getOpts.Includes = g.Includes + getOpts.Excludes = g.Excludes + } + return &getOpts +} + +func (g *GetOptions) WithQuery(apiPath string) string { + params := g.Encode() + if params != "" { + // parse path, take existing vars + return fmt.Sprintf("%s?%s", apiPath, params) + } + return apiPath +} + +// OptionsGetter provides GetOptions +type OptionsGetter interface { + GetOptions() *GetOptions +} + +func (g *GetOptions) GetPage() int { // guaranteed int + if g == nil { + return 0 + } + return g.Page +} + +func (g *GetOptions) CopyOrNew() *GetOptions { + if g == nil { + return &GetOptions{} + } + ret := *g + return &ret +} + +// Including ensures that the variadic refs are included in a copy of the +// options, resulting in expansion of the the referred sub-resources. Unknown +// values within refs will be silently ignore by the API. +func (g *GetOptions) Including(refs ...string) *GetOptions { + ret := g.CopyOrNew() + for _, v := range refs { + if !contains(ret.Includes, v) { + ret.Includes = append(ret.Includes, v) + } + } + return ret +} + +// Excluding ensures that the variadic refs are included in the "Excluded" param +// in a copy of the options. +// Unknown values within refs will be silently ignore by the API. +func (g *GetOptions) Excluding(refs ...string) *GetOptions { + ret := g.CopyOrNew() + for _, v := range refs { + if !contains(ret.Excludes, v) { + ret.Excludes = append(ret.Excludes, v) + } + } + return ret +} + +func stripQuery(inURL string) string { + u, _ := url.Parse(inURL) + u.RawQuery = "" + return u.String() +} + +// nextPage is common and extracted from all List functions +func nextPage(meta meta, opts *GetOptions) (path string) { + if meta.Next != nil && (opts.GetPage() == 0) { + optsCopy := opts.CopyOrNew() + optsCopy.Page = meta.CurrentPageNum + 1 + return optsCopy.WithQuery(stripQuery(meta.Next.Href)) + } + if opts != nil { + opts.Meta = meta + } + return "" +} + +// Encode generates a URL query string ("?foo=bar") +func (g *GetOptions) Encode() string { + if g == nil { + return "" + } + v := url.Values{} + if g.Includes != nil && len(g.Includes) > 0 { + v.Add("include", strings.Join(g.Includes, ",")) + } + if g.Excludes != nil && len(g.Excludes) > 0 { + v.Add("exclude", strings.Join(g.Excludes, ",")) + } + if g.Page != 0 { + v.Add("page", strconv.Itoa(g.Page)) + } + if g.PerPage != 0 { + v.Add("per_page", strconv.Itoa(g.PerPage)) + } + if g.Search != "" { + v.Add("search", g.Search) + } + if g.SortBy != "" { + v.Add("sort_by", g.SortBy) + } + if g.SortDirection != "" { + v.Add("sort_direction", string(g.SortDirection)) + } + return v.Encode() +} + +/* +// Encode generates a URL query string ("?foo=bar") +func (g *GetOptions) Encode() string { + urlValues, _ := query.Values(g) + return urlValues.Encode() +} +*/ +func urlQuery(o *GetOptions) string { + return o.Encode() +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/apikeys.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/apikeys.go new file mode 100644 index 00000000..5241fa9c --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/apikeys.go @@ -0,0 +1,176 @@ +package packngo + +import ( + "fmt" + "path" +) + +const ( + apiKeyBasePath = "/api-keys" +) + +// APIKeyService interface defines available device methods +type APIKeyService interface { + UserList(*ListOptions) ([]APIKey, *Response, error) + ProjectList(string, *ListOptions) ([]APIKey, *Response, error) + UserGet(string, *GetOptions) (*APIKey, error) + ProjectGet(string, string, *GetOptions) (*APIKey, error) + Create(*APIKeyCreateRequest) (*APIKey, *Response, error) + Delete(string) (*Response, error) +} + +type apiKeyRoot struct { + APIKeys []APIKey `json:"api_keys"` +} + +type APIKey struct { + // ID is the UUIDv4 representing an API key in API requests and responses. + ID string `json:"id"` + + // Description is any text description of the key. This can be used to + // describe the purpose of the key. + Description string `json:"description"` + + // Token is a sensitive credential that can be used as a `Client.APIKey` to + // access Equinix Metal resources. + Token string `json:"token"` + + // ReadOnly keys can not create new resources. + ReadOnly bool `json:"read_only"` + + // Created is the creation date of the API key. + Created string `json:"created_at"` + + // Updated is the last-update date of the API key. + Updated string `json:"updated_at"` + + // User will be non-nil when getting or listing an User API key. + User *User `json:"user"` + + // Project will be non-nil when getting or listing a Project API key + Project *Project `json:"project"` +} + +// APIKeyCreateRequest type used to create an api key. +type APIKeyCreateRequest struct { + // Description is any text description of the key. This can be used to + // describe the purpose of the key. + Description string `json:"description"` + + // ReadOnly keys can not create new resources. + ReadOnly bool `json:"read_only"` + + // ProjectID when non-empty will result in the creation of a Project API + // key. + ProjectID string `json:"-"` +} + +func (s APIKeyCreateRequest) String() string { + return Stringify(s) +} + +// APIKeyServiceOp implements APIKeyService +type APIKeyServiceOp struct { + client *Client +} + +func (s *APIKeyServiceOp) list(url string, opts *ListOptions) ([]APIKey, *Response, error) { + root := new(apiKeyRoot) + apiPathQuery := opts.WithQuery(url) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, root) + if err != nil { + return nil, resp, err + } + + return root.APIKeys, resp, err +} + +// ProjectList lists the API keys associated with a project having `projectID` +// match `Project.ID`. +func (s *APIKeyServiceOp) ProjectList(projectID string, opts *ListOptions) ([]APIKey, *Response, error) { + endpointPath := path.Join(projectBasePath, projectID, apiKeyBasePath) + return s.list(endpointPath, opts) +} + +// UserList returns the API keys for the User associated with the +// `Client.APIKey`. +// +// When `Client.APIKey` is a Project API key, this method will return an access +// denied error. +func (s *APIKeyServiceOp) UserList(opts *ListOptions) ([]APIKey, *Response, error) { + endpointPath := path.Join(userBasePath, apiKeyBasePath) + return s.list(endpointPath, opts) +} + +// ProjectGet returns the Project API key with the given `APIKey.ID`. +// +// In other methods, it is typical for a Response to be returned, which could +// include a StatusCode of `http.StatusNotFound` (404 error) when the resource +// was not found. The Equinix Metal API does not expose a get by ID endpoint for +// APIKeys. That is why in this method, all API keys are listed and compared +// for a match. Therefor, the Response is not returned and a custom error will +// be returned when the key is not found. +func (s *APIKeyServiceOp) ProjectGet(projectID, apiKeyID string, opts *GetOptions) (*APIKey, error) { + pkeys, _, err := s.ProjectList(projectID, opts) + if err != nil { + return nil, err + } + for _, k := range pkeys { + if k.ID == apiKeyID { + return &k, nil + } + } + return nil, fmt.Errorf("Project (%s) API key %s not found", projectID, apiKeyID) +} + +// UserGet returns the User API key with the given `APIKey.ID`. +// +// In other methods, it is typical for a Response to be returned, which could +// include a StatusCode of `http.StatusNotFound` (404 error) when the resource +// was not found. The Equinix Metal API does not expose a get by ID endpoint for +// APIKeys. That is why in this method, all API keys are listed and compared +// for a match. Therefor, the Response is not returned and a custom error will +// be returned when the key is not found. +func (s *APIKeyServiceOp) UserGet(apiKeyID string, opts *GetOptions) (*APIKey, error) { + ukeys, _, err := s.UserList(opts) + if err != nil { + return nil, err + } + for _, k := range ukeys { + if k.ID == apiKeyID { + return &k, nil + } + } + return nil, fmt.Errorf("User API key %s not found", apiKeyID) +} + +// Create creates a new API key. +// +// The API key can be either an User API key or a Project API key, determined by +// the value (or emptiness) of `APIKeyCreateRequest.ProjectID`. Either `User` or +// `Project` will be non-nil in the `APIKey` depending on this factor. +func (s *APIKeyServiceOp) Create(createRequest *APIKeyCreateRequest) (*APIKey, *Response, error) { + apiPath := path.Join(userBasePath, apiKeyBasePath) + if createRequest.ProjectID != "" { + apiPath = path.Join(projectBasePath, createRequest.ProjectID, apiKeyBasePath) + } + apiKey := new(APIKey) + + resp, err := s.client.DoRequest("POST", apiPath, createRequest, apiKey) + if err != nil { + return nil, resp, err + } + + return apiKey, resp, err +} + +// Delete deletes an API key by `APIKey.ID` +// +// The API key can be either an User API key or a Project API key. +// +// Project API keys can not be used to delete themselves. +func (s *APIKeyServiceOp) Delete(apiKeyID string) (*Response, error) { + apiPath := path.Join(userBasePath, apiKeyBasePath, apiKeyID) + return s.client.DoRequest("DELETE", apiPath, nil, nil) +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/batches.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/batches.go new file mode 100644 index 00000000..280b1071 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/batches.go @@ -0,0 +1,106 @@ +package packngo + +import ( + "fmt" + "path" +) + +const batchBasePath = "/batches" + +// BatchService interface defines available batch methods +type BatchService interface { + Get(batchID string, getOpt *GetOptions) (*Batch, *Response, error) + List(ProjectID string, listOpt *ListOptions) ([]Batch, *Response, error) + Create(projectID string, batches *BatchCreateRequest) ([]Batch, *Response, error) + Delete(string, bool) (*Response, error) +} + +// Batch type +type Batch struct { + ID string `json:"id"` + ErrorMessages []string `json:"error_messages,omitempty"` + + // State may be 'failed' or 'completed' + State string `json:"state,omitempty"` + Quantity int32 `json:"quantity,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + Href string `json:"href,omitempty"` + Project Href `json:"project,omitempty"` + Devices []Device `json:"devices,omitempty"` +} + +//BatchesList represents collection of batches +type batchesList struct { + Batches []Batch `json:"batches,omitempty"` +} + +// BatchCreateRequest type used to create batch of device instances +type BatchCreateRequest struct { + Batches []BatchCreateDevice `json:"batches"` +} + +// BatchCreateDevice type used to describe batch instances +type BatchCreateDevice struct { + DeviceCreateRequest + Quantity int32 `json:"quantity"` + FacilityDiversityLevel int32 `json:"facility_diversity_level,omitempty"` + SpotInstance bool `json:"spot_instance,omitempty"` + SpotPriceMax float64 `json:"spot_price_max,omitempty"` +} + +// BatchServiceOp implements BatchService +type BatchServiceOp struct { + client *Client +} + +// Get returns batch details +func (s *BatchServiceOp) Get(batchID string, opts *GetOptions) (*Batch, *Response, error) { + endpointPath := path.Join(batchBasePath, batchID) + apiPathQuery := opts.WithQuery(endpointPath) + batch := new(Batch) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, batch) + if err != nil { + return nil, resp, err + } + + return batch, resp, err +} + +// List returns batches on a project +func (s *BatchServiceOp) List(projectID string, opts *ListOptions) (batches []Batch, resp *Response, err error) { + endpointPath := path.Join(projectBasePath, projectID, batchBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + subset := new(batchesList) + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + batches = append(batches, subset.Batches...) + return batches, resp, err +} + +// Create function to create batch of device instances +func (s *BatchServiceOp) Create(projectID string, request *BatchCreateRequest) ([]Batch, *Response, error) { + apiPath := path.Join(projectBasePath, projectID, "devices", "batch") + + batches := new(batchesList) + resp, err := s.client.DoRequest("POST", apiPath, request, batches) + + if err != nil { + return nil, resp, err + } + + return batches.Batches, resp, err +} + +// Delete function to remove an instance batch +func (s *BatchServiceOp) Delete(id string, removeDevices bool) (*Response, error) { + // API doc days the remove_associated_instances params shout be in the body + // https://metal.equinix.com/developers/api/batches/#delete-the-batch + // .. does this even work? + apiPath := fmt.Sprintf("%s/%s?remove_associated_instances=%t", batchBasePath, id, removeDevices) + + return s.client.DoRequest("DELETE", apiPath, nil, nil) +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/bgp_configs.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/bgp_configs.go new file mode 100644 index 00000000..95dc5b07 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/bgp_configs.go @@ -0,0 +1,85 @@ +package packngo + +import ( + "path" +) + +var ( + bgpConfigPostBasePath = "/bgp-configs" + bgpConfigGetBasePath = "/bgp-config" +) + +// BGPConfigService interface defines available BGP config methods +type BGPConfigService interface { + Get(projectID string, getOpt *GetOptions) (*BGPConfig, *Response, error) + Create(projectID string, request CreateBGPConfigRequest) (*Response, error) + // Delete(configID string) (resp *Response, err error) TODO: Not in Equinix Metal API +} + +// BGPConfigServiceOp implements BgpConfigService +type BGPConfigServiceOp struct { + client *Client +} + +// CreateBGPConfigRequest struct +type CreateBGPConfigRequest struct { + DeploymentType string `json:"deployment_type,omitempty"` + Asn int `json:"asn,omitempty"` + Md5 string `json:"md5,omitempty"` + UseCase string `json:"use_case,omitempty"` +} + +// BGPConfig represents an Equinix Metal BGP Config +type BGPConfig struct { + ID string `json:"id,omitempty"` + Status string `json:"status,omitempty"` + DeploymentType string `json:"deployment_type,omitempty"` + Asn int `json:"asn,omitempty"` + RouteObject string `json:"route_object,omitempty"` + Md5 string `json:"md5,omitempty"` + MaxPrefix int `json:"max_prefix,omitempty"` + Project Project `json:"project,omitempty"` + CreatedAt Timestamp `json:"created_at,omitempty"` + RequestedAt Timestamp `json:"requested_at,omitempty"` + Sessions []BGPSession `json:"sessions,omitempty"` + Href string `json:"href,omitempty"` +} + +// Create function +func (s *BGPConfigServiceOp) Create(projectID string, request CreateBGPConfigRequest) (*Response, error) { + apiPath := path.Join(projectBasePath, projectID, bgpConfigPostBasePath) + + resp, err := s.client.DoRequest("POST", apiPath, request, nil) + if err != nil { + return resp, err + } + + return resp, err +} + +// Get function +func (s *BGPConfigServiceOp) Get(projectID string, opts *GetOptions) (bgpConfig *BGPConfig, resp *Response, err error) { + endpointPath := path.Join(projectBasePath, projectID, bgpConfigGetBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + + subset := new(BGPConfig) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + return subset, resp, err +} + +// Delete function TODO: this is not implemented in the Equinix Metal API +// func (s *BGPConfigServiceOp) Delete(configID string) (resp *Response, err error) { +// apiPath := fmt.Sprintf("%ss/%s", bgpConfigBasePath, configID) + +// resp, err = s.client.DoRequest("DELETE", apiPath, nil, nil) +// if err != nil { +// return resp, err +// } + +// return resp, err +// } diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/bgp_sessions.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/bgp_sessions.go new file mode 100644 index 00000000..0054a51e --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/bgp_sessions.go @@ -0,0 +1,99 @@ +package packngo + +import ( + "path" +) + +var bgpSessionBasePath = "/bgp/sessions" +var bgpNeighborsBasePath = "/bgp/neighbors" + +// BGPSessionService interface defines available BGP session methods +type BGPSessionService interface { + Get(string, *GetOptions) (*BGPSession, *Response, error) + Create(string, CreateBGPSessionRequest) (*BGPSession, *Response, error) + Delete(string) (*Response, error) +} + +type bgpSessionsRoot struct { + Sessions []BGPSession `json:"bgp_sessions"` + Meta meta `json:"meta"` +} + +// BGPSessionServiceOp implements BgpSessionService +type BGPSessionServiceOp struct { + client *Client +} + +// BGPSession represents an Equinix Metal BGP Session +type BGPSession struct { + ID string `json:"id,omitempty"` + Status string `json:"status,omitempty"` + LearnedRoutes []string `json:"learned_routes,omitempty"` + AddressFamily string `json:"address_family,omitempty"` + Device Device `json:"device,omitempty"` + Href string `json:"href,omitempty"` + DefaultRoute *bool `json:"default_route,omitempty"` +} + +type bgpNeighborsRoot struct { + BGPNeighbors []BGPNeighbor `json:"bgp_neighbors"` +} + +// BGPNeighor is struct for listing BGP neighbors of a device +type BGPNeighbor struct { + AddressFamily int `json:"address_family"` + CustomerAs int `json:"customer_as"` + CustomerIP string `json:"customer_ip"` + Md5Enabled bool `json:"md5_enabled"` + Md5Password string `json:"md5_password"` + Multihop bool `json:"multihop"` + PeerAs int `json:"peer_as"` + PeerIps []string `json:"peer_ips"` + RoutesIn []BGPRoute `json:"routes_in"` + RoutesOut []BGPRoute `json:"routes_out"` +} + +// BGPRoute is a struct for Route in BGP neighbor listing +type BGPRoute struct { + Route string `json:"route"` + Exact bool `json:"exact"` +} + +// CreateBGPSessionRequest struct +type CreateBGPSessionRequest struct { + AddressFamily string `json:"address_family"` + DefaultRoute *bool `json:"default_route,omitempty"` +} + +// Create function +func (s *BGPSessionServiceOp) Create(deviceID string, request CreateBGPSessionRequest) (*BGPSession, *Response, error) { + apiPath := path.Join(deviceBasePath, deviceID, bgpSessionBasePath) + session := new(BGPSession) + + resp, err := s.client.DoRequest("POST", apiPath, request, session) + if err != nil { + return nil, resp, err + } + + return session, resp, err +} + +// Delete function +func (s *BGPSessionServiceOp) Delete(id string) (*Response, error) { + apiPath := path.Join(bgpSessionBasePath, id) + + return s.client.DoRequest("DELETE", apiPath, nil, nil) +} + +// Get function +func (s *BGPSessionServiceOp) Get(id string, opts *GetOptions) (session *BGPSession, response *Response, err error) { + endpointPath := path.Join(bgpSessionBasePath, id) + apiPathQuery := opts.WithQuery(endpointPath) + session = new(BGPSession) + response, err = s.client.DoRequest("GET", apiPathQuery, nil, session) + if err != nil { + return nil, response, err + } + + return session, response, err +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/billing_address.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/billing_address.go new file mode 100644 index 00000000..93255b32 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/billing_address.go @@ -0,0 +1,7 @@ +package packngo + +type BillingAddress struct { + StreetAddress string `json:"street_address,omitempty"` + PostalCode string `json:"postal_code,omitempty"` + CountryCode string `json:"country_code_alpha2,omitempty"` +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/capacities.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/capacities.go new file mode 100644 index 00000000..45ffac60 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/capacities.go @@ -0,0 +1,103 @@ +package packngo + +const ( + capacityBasePath = "/capacity" + capacityBasePathMetros = "/capacity/metros" +) + +// CapacityService interface defines available capacity methods +type CapacityService interface { + List() (*CapacityReport, *Response, error) + ListMetros() (*CapacityReport, *Response, error) + Check(*CapacityInput) (*CapacityInput, *Response, error) + CheckMetros(*CapacityInput) (*CapacityInput, *Response, error) +} + +// CapacityInput struct +type CapacityInput struct { + Servers []ServerInfo `json:"servers,omitempty"` +} + +// ServerInfo struct +type ServerInfo struct { + Facility string `json:"facility,omitempty"` + Metro string `json:"metro,omitempty"` + Plan string `json:"plan,omitempty"` + Quantity int `json:"quantity,omitempty"` + Available bool `json:"available,omitempty"` +} + +type capacityRoot struct { + Capacity CapacityReport `json:"capacity,omitempty"` +} + +// CapacityReport map +type CapacityReport map[string]map[string]CapacityPerBaremetal + +// // CapacityPerFacility struct +// type CapacityPerFacility struct { +// T1SmallX86 *CapacityPerBaremetal `json:"t1.small.x86,omitempty"` +// C1SmallX86 *CapacityPerBaremetal `json:"c1.small.x86,omitempty"` +// M1XlargeX86 *CapacityPerBaremetal `json:"m1.xlarge.x86,omitempty"` +// C1XlargeX86 *CapacityPerBaremetal `json:"c1.xlarge.x86,omitempty"` + +// Baremetal0 *CapacityPerBaremetal `json:"baremetal_0,omitempty"` +// Baremetal1 *CapacityPerBaremetal `json:"baremetal_1,omitempty"` +// Baremetal1e *CapacityPerBaremetal `json:"baremetal_1e,omitempty"` +// Baremetal2 *CapacityPerBaremetal `json:"baremetal_2,omitempty"` +// Baremetal2a *CapacityPerBaremetal `json:"baremetal_2a,omitempty"` +// Baremetal2a2 *CapacityPerBaremetal `json:"baremetal_2a2,omitempty"` +// Baremetal3 *CapacityPerBaremetal `json:"baremetal_3,omitempty"` +// } + +// CapacityPerBaremetal struct +type CapacityPerBaremetal struct { + Level string `json:"level,omitempty"` +} + +// CapacityList struct +type CapacityList struct { + Capacity CapacityReport `json:"capacity,omitempty"` +} + +// CapacityServiceOp implements CapacityService +type CapacityServiceOp struct { + client *Client +} + +func capacityList(client *Client, capUrl string) (*CapacityReport, *Response, error) { + root := new(capacityRoot) + + resp, err := client.DoRequest("GET", capUrl, nil, root) + if err != nil { + return nil, resp, err + } + + return &root.Capacity, nil, nil +} + +// List returns a list of facilities and plans with their current capacity. +func (s *CapacityServiceOp) List() (*CapacityReport, *Response, error) { + return capacityList(s.client, capacityBasePath) +} + +// ListMetros returns a list of metros and plans with their current capacity. +func (s *CapacityServiceOp) ListMetros() (*CapacityReport, *Response, error) { + return capacityList(s.client, capacityBasePathMetros) +} + +func checkCapacity(client *Client, input *CapacityInput, capUrl string) (capInput *CapacityInput, resp *Response, err error) { + capInput = new(CapacityInput) + resp, err = client.DoRequest("POST", capUrl, input, capInput) + return capInput, resp, err +} + +// Check validates if a deploy can be fulfilled in a capacity. +func (s *CapacityServiceOp) Check(input *CapacityInput) (capInput *CapacityInput, resp *Response, err error) { + return checkCapacity(s.client, input, capacityBasePath) +} + +// Check validates if a deploy can be fulfilled in a metro. +func (s *CapacityServiceOp) CheckMetros(input *CapacityInput) (capInput *CapacityInput, resp *Response, err error) { + return checkCapacity(s.client, input, capacityBasePathMetros) +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/code-of-conduct.md b/provider/equinixmetal/vendor/github.com/packethost/packngo/code-of-conduct.md new file mode 100644 index 00000000..3a14e528 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/code-of-conduct.md @@ -0,0 +1,3 @@ +# Code Of Conduct + +Please refer to the [Contributor Covenant](https://www.contributor-covenant.org/version/1/2/0/code-of-conduct/). diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/connections.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/connections.go new file mode 100644 index 00000000..e489d845 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/connections.go @@ -0,0 +1,218 @@ +package packngo + +import ( + "path" +) + +type ConnectionRedundancy string +type ConnectionType string +type ConnectionPortRole string + +const ( + connectionBasePath = "/connections" + ConnectionShared ConnectionType = "shared" + ConnectionDedicated ConnectionType = "dedicated" + ConnectionRedundant ConnectionRedundancy = "redundant" + ConnectionPrimary ConnectionRedundancy = "primary" + ConnectionPortPrimary ConnectionPortRole = "primary" + ConnectionPortSecondary ConnectionPortRole = "secondary" +) + +type ConnectionService interface { + OrganizationCreate(string, *ConnectionCreateRequest) (*Connection, *Response, error) + ProjectCreate(string, *ConnectionCreateRequest) (*Connection, *Response, error) + OrganizationList(string, *GetOptions) ([]Connection, *Response, error) + ProjectList(string, *GetOptions) ([]Connection, *Response, error) + Delete(string) (*Response, error) + Get(string, *GetOptions) (*Connection, *Response, error) + Events(string, *GetOptions) ([]Event, *Response, error) + PortEvents(string, string, *GetOptions) ([]Event, *Response, error) + Ports(string, *GetOptions) ([]ConnectionPort, *Response, error) + Port(string, string, *GetOptions) (*ConnectionPort, *Response, error) + VirtualCircuits(string, string, *GetOptions) ([]VirtualCircuit, *Response, error) +} + +type ConnectionServiceOp struct { + client *Client +} + +type connectionPortsRoot struct { + Ports []ConnectionPort `json:"ports"` +} + +type connectionsRoot struct { + Connections []Connection `json:"interconnections"` + Meta meta `json:"meta"` +} + +type ConnectionPort struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Status string `json:"status,omitempty"` + Role ConnectionPortRole `json:"role,omitempty"` + Speed int `json:"speed,omitempty"` + Organization *Organization `json:"organization,omitempty"` + VirtualCircuits []VirtualCircuit `json:"virtual_circuits,omitempty"` + LinkStatus string `json:"link_status,omitempty"` + Href string `json:"href,omitempty"` +} + +type Connection struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Status string `json:"status,omitempty"` + Redundancy ConnectionRedundancy `json:"redundancy,omitempty"` + Facility *Facility `json:"facility,omitempty"` + Metro *Metro `json:"metro,omitempty"` + Type ConnectionType `json:"type,omitempty"` + Description string `json:"description,omitempty"` + Project *Project `json:"project,omitempty"` + Organization *Organization `json:"organization,omitempty"` + Speed int `json:"speed,omitempty"` + Token string `json:"token,omitempty"` + Tags []string `json:"tags,omitempty"` + Ports []ConnectionPort `json:"ports,omitempty"` +} + +type ConnectionCreateRequest struct { + Name string `json:"name,omitempty"` + Redundancy ConnectionRedundancy `json:"redundancy,omitempty"` + Facility string `json:"facility,omitempty"` + Metro string `json:"metro,omitempty"` + Type ConnectionType `json:"type,omitempty"` + Description *string `json:"description,omitempty"` + Project string `json:"project,omitempty"` + Speed int `json:"speed,omitempty"` + Tags []string `json:"tags,omitempty"` +} + +func (c *Connection) PortByRole(r ConnectionPortRole) *ConnectionPort { + for _, p := range c.Ports { + if p.Role == r { + return &p + } + } + return nil +} + +func (s *ConnectionServiceOp) create(apiUrl string, createRequest *ConnectionCreateRequest) (*Connection, *Response, error) { + connection := new(Connection) + resp, err := s.client.DoRequest("POST", apiUrl, createRequest, connection) + if err != nil { + return nil, resp, err + } + + return connection, resp, err +} + +func (s *ConnectionServiceOp) OrganizationCreate(id string, createRequest *ConnectionCreateRequest) (*Connection, *Response, error) { + apiUrl := path.Join(organizationBasePath, id, connectionBasePath) + return s.create(apiUrl, createRequest) +} + +func (s *ConnectionServiceOp) ProjectCreate(id string, createRequest *ConnectionCreateRequest) (*Connection, *Response, error) { + apiUrl := path.Join(projectBasePath, id, connectionBasePath) + return s.create(apiUrl, createRequest) +} + +func (s *ConnectionServiceOp) list(url string, opts *GetOptions) (connections []Connection, resp *Response, err error) { + apiPathQuery := opts.WithQuery(url) + + for { + subset := new(connectionsRoot) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + connections = append(connections, subset.Connections...) + + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + + return + } + +} + +func (s *ConnectionServiceOp) OrganizationList(id string, opts *GetOptions) ([]Connection, *Response, error) { + apiUrl := path.Join(organizationBasePath, id, connectionBasePath) + return s.list(apiUrl, opts) +} + +func (s *ConnectionServiceOp) ProjectList(id string, opts *GetOptions) ([]Connection, *Response, error) { + apiUrl := path.Join(projectBasePath, id, connectionBasePath) + return s.list(apiUrl, opts) +} + +func (s *ConnectionServiceOp) Delete(id string) (*Response, error) { + apiPath := path.Join(connectionBasePath, id) + return s.client.DoRequest("DELETE", apiPath, nil, nil) +} + +func (s *ConnectionServiceOp) Port(connID, portID string, opts *GetOptions) (*ConnectionPort, *Response, error) { + endpointPath := path.Join(connectionBasePath, connID, portBasePath, portID) + apiPathQuery := opts.WithQuery(endpointPath) + port := new(ConnectionPort) + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, port) + if err != nil { + return nil, resp, err + } + return port, resp, err +} + +func (s *ConnectionServiceOp) Get(id string, opts *GetOptions) (*Connection, *Response, error) { + endpointPath := path.Join(connectionBasePath, id) + apiPathQuery := opts.WithQuery(endpointPath) + connection := new(Connection) + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, connection) + if err != nil { + return nil, resp, err + } + return connection, resp, err +} + +func (s *ConnectionServiceOp) Ports(connID string, opts *GetOptions) ([]ConnectionPort, *Response, error) { + endpointPath := path.Join(connectionBasePath, connID, portBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + ports := new(connectionPortsRoot) + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, ports) + if err != nil { + return nil, resp, err + } + return ports.Ports, resp, nil + +} + +func (s *ConnectionServiceOp) Events(id string, opts *GetOptions) ([]Event, *Response, error) { + apiPath := path.Join(connectionBasePath, id, eventBasePath) + return listEvents(s.client, apiPath, opts) +} + +func (s *ConnectionServiceOp) PortEvents(connID, portID string, opts *GetOptions) ([]Event, *Response, error) { + apiPath := path.Join(connectionBasePath, connID, portBasePath, portID, eventBasePath) + return listEvents(s.client, apiPath, opts) +} + +func (s *ConnectionServiceOp) VirtualCircuits(connID, portID string, opts *GetOptions) (vcs []VirtualCircuit, resp *Response, err error) { + endpointPath := path.Join(connectionBasePath, connID, portBasePath, portID, virtualCircuitBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + for { + subset := new(virtualCircuitsRoot) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + vcs = append(vcs, subset.VirtualCircuits...) + + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + + return + } +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/device_ports.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/device_ports.go new file mode 100644 index 00000000..0b02acfd --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/device_ports.go @@ -0,0 +1,323 @@ +package packngo + +import ( + "fmt" + "strings" +) + +const portBasePath = "/ports" + +// DevicePortService handles operations on a port which belongs to a particular device +// +// Deprecated: use PortService or Device methods +type DevicePortService interface { + Assign(*PortAssignRequest) (*Port, *Response, error) + Unassign(*PortAssignRequest) (*Port, *Response, error) + AssignNative(*PortAssignRequest) (*Port, *Response, error) + UnassignNative(string) (*Port, *Response, error) + Bond(*Port, bool) (*Port, *Response, error) + Disbond(*Port, bool) (*Port, *Response, error) + DeviceToNetworkType(string, string) (*Device, error) + DeviceNetworkType(string) (string, error) + PortToLayerTwo(string, string) (*Port, *Response, error) + PortToLayerThree(string, string) (*Port, *Response, error) + GetPortByName(string, string) (*Port, error) + GetOddEthPorts(*Device) (map[string]*Port, error) + GetAllEthPorts(*Device) (map[string]*Port, error) + ConvertDevice(*Device, string) error +} + +// DevicePortServiceOp implements DevicePortService on the Equinix Metal API +// +// Deprecated: use PortServiceOp or Device methods +type DevicePortServiceOp struct { + client *Client +} + +// GetPortByName returns the matching Port on the specified device +// +// Deprecated: use Device.GetPortByName +func (i *DevicePortServiceOp) GetPortByName(deviceID, name string) (*Port, error) { + device, _, err := i.client.Devices.Get(deviceID, nil) + if err != nil { + return nil, err + } + return device.GetPortByName(name) +} + +// Assign the specified VLAN to the specified Port +// +// Deprecated: use PortServiceOp.Assign +func (i *DevicePortServiceOp) Assign(par *PortAssignRequest) (*Port, *Response, error) { + return i.client.Ports.Assign(par.PortID, par.VirtualNetworkID) +} + +// AssignNative designates the specified VLAN as the native VLAN for the +// specified Port +// +// Deprecated: use PortServiceOp.AssignNative +func (i *DevicePortServiceOp) AssignNative(par *PortAssignRequest) (*Port, *Response, error) { + return i.client.Ports.AssignNative(par.PortID, par.VirtualNetworkID) +} + +// UnassignNative removes the native VLAN from the specified Port +// +// Deprecated: use PortServiceOp.UnassignNative +func (i *DevicePortServiceOp) UnassignNative(portID string) (*Port, *Response, error) { + return i.client.Ports.UnassignNative(portID) +} + +// Unassign removes the specified VLAN from the specified Port +// +// Deprecated: use PortServiceOp.Unassign +func (i *DevicePortServiceOp) Unassign(par *PortAssignRequest) (*Port, *Response, error) { + return i.client.Ports.Unassign(par.PortID, par.VirtualNetworkID) +} + +// Bond enabled bonding on the specified port +// +// Deprecated: use PortServiceOp.Bond +func (i *DevicePortServiceOp) Bond(p *Port, bulk_enable bool) (*Port, *Response, error) { + if p.Data.Bonded { + return p, nil, nil + } + + return i.client.Ports.Bond(p.ID, bulk_enable) +} + +// Disbond disables bonding on the specified port +// +// Deprecated: use PortServiceOp.Disbond +func (i *DevicePortServiceOp) Disbond(p *Port, bulk_disable bool) (*Port, *Response, error) { + if !p.Data.Bonded { + return p, nil, nil + } + return i.client.Ports.Disbond(p.ID, bulk_disable) +} + +// PortToLayerTwo fetches the specified device, finds the matching port by name, +// and converts it to layer2. A port may already be in a layer2 mode, in which +// case the port will be returned with a nil response and nil error with no +// additional action taking place. +// +// Deprecated: use PortServiceOp.ConvertToLayerTwo +func (i *DevicePortServiceOp) PortToLayerTwo(deviceID, portName string) (*Port, *Response, error) { + p, err := i.GetPortByName(deviceID, portName) + if err != nil { + return nil, nil, err + } + if strings.HasPrefix(p.NetworkType, "layer2") { + return p, nil, nil + } + + return i.client.Ports.ConvertToLayerTwo(p.ID) +} + +// PortToLayerThree fetches the specified device, finds the matching port by +// name, and converts it to layer3. A port may already be in a layer3 mode, in +// which case the port will be returned with a nil response and nil error with +// no additional action taking place. +// +// When switching to Layer3, a new set of IP addresses will be requested +// including Public IPv4, Public IPv6, and Private IPv6 addresses. +// +// Deprecated: use PortServiceOp.ConvertToLayerTwo +func (i *DevicePortServiceOp) PortToLayerThree(deviceID, portName string) (*Port, *Response, error) { + p, err := i.GetPortByName(deviceID, portName) + if err != nil { + return nil, nil, err + } + if (p.NetworkType == NetworkTypeL3) || (p.NetworkType == NetworkTypeHybrid) { + return p, nil, nil + } + + ips := []AddressRequest{ + {AddressFamily: 4, Public: true}, + {AddressFamily: 4, Public: false}, + {AddressFamily: 6, Public: true}, + } + + return i.client.Ports.ConvertToLayerThree(p.ID, ips) +} + +// DeviceNetworkType fetches the specified Device and returns a heuristic single +// word network type consistent with the Equinix Metal console experience. +// +// Deprecated: use Device.GetNetworkType +func (i *DevicePortServiceOp) DeviceNetworkType(deviceID string) (string, error) { + d, _, err := i.client.Devices.Get(deviceID, nil) + if err != nil { + return "", err + } + return d.GetNetworkType(), nil +} + +// GetAllEthPorts fetches the specified Device and returns a heuristic single +// word network type consistent with the Equinix Metal console experience. +// +// Deprecated: use Device.GetPhysicalPorts +func (i *DevicePortServiceOp) GetAllEthPorts(d *Device) (map[string]*Port, error) { + d, _, err := i.client.Devices.Get(d.ID, nil) + if err != nil { + return nil, err + } + return d.GetPhysicalPorts(), nil +} + +// GetOddEthPorts fetches the specified Device and returns physical +// ports eth1 and eth3. +// +// Deprecated: use Device.GetPhysicalPorts and filter the map to only the keys +// ending with odd digits +func (i *DevicePortServiceOp) GetOddEthPorts(d *Device) (map[string]*Port, error) { + d, _, err := i.client.Devices.Get(d.ID, nil) + if err != nil { + return nil, err + } + ret := map[string]*Port{} + eth1, err := d.GetPortByName("eth1") + if err != nil { + return nil, err + } + ret["eth1"] = eth1 + + eth3, err := d.GetPortByName("eth3") + if err != nil { + return ret, nil + } + ret["eth3"] = eth3 + return ret, nil + +} + +// ConvertDevice converts the specified device's network ports (including +// addresses and vlans) to the named network type, consistent with the Equinix +// Metal console experience. +// +// Deprecated: Equinix Metal devices may support more than two ports and the +// whole-device single word network type can no longer capture the capabilities +// and permutations of device port configurations. +func (i *DevicePortServiceOp) ConvertDevice(d *Device, targetType string) error { + bondPorts := d.GetBondPorts() + + if targetType == NetworkTypeL3 { + // TODO: remove vlans from all the ports + for _, p := range bondPorts { + _, _, err := i.Bond(p, false) + if err != nil { + return err + } + } + _, _, err := i.PortToLayerThree(d.ID, "bond0") + if err != nil { + return err + } + allEthPorts, err := i.GetAllEthPorts(d) + if err != nil { + return err + } + for _, p := range allEthPorts { + _, _, err := i.Bond(p, false) + if err != nil { + return err + } + } + } + if targetType == NetworkTypeHybrid { + for _, p := range bondPorts { + _, _, err := i.Bond(p, false) + if err != nil { + return err + } + } + + _, _, err := i.PortToLayerThree(d.ID, "bond0") + if err != nil { + return err + } + + // ports need to be refreshed before bonding/disbonding + oddEthPorts, err := i.GetOddEthPorts(d) + if err != nil { + return err + } + + for _, p := range oddEthPorts { + _, _, err := i.Disbond(p, false) + if err != nil { + return err + } + } + } + if targetType == NetworkTypeL2Individual { + _, _, err := i.PortToLayerTwo(d.ID, "bond0") + if err != nil { + return err + } + for _, p := range bondPorts { + _, _, err = i.Disbond(p, true) + if err != nil { + return err + } + } + } + if targetType == NetworkTypeL2Bonded { + + for _, p := range bondPorts { + _, _, err := i.PortToLayerTwo(d.ID, p.Name) + if err != nil { + return err + } + } + allEthPorts, err := i.GetAllEthPorts(d) + if err != nil { + return err + } + for _, p := range allEthPorts { + _, _, err := i.Bond(p, false) + if err != nil { + return err + } + } + } + return nil +} + +// DeviceToNetworkType fetches the specified device and converts its network +// ports (including addresses and vlans) to the named network type, consistent +// with the Equinix Metal console experience. +// +// Deprecated: use DevicePortServiceOp.ConvertDevice which this function thinly +// wraps. +func (i *DevicePortServiceOp) DeviceToNetworkType(deviceID string, targetType string) (*Device, error) { + d, _, err := i.client.Devices.Get(deviceID, nil) + if err != nil { + return nil, err + } + + curType := d.GetNetworkType() + + if curType == targetType { + return nil, fmt.Errorf("Device already is in state %s", targetType) + } + err = i.ConvertDevice(d, targetType) + if err != nil { + return nil, err + } + + d, _, err = i.client.Devices.Get(deviceID, nil) + + if err != nil { + return nil, err + } + + finalType := d.GetNetworkType() + + if finalType != targetType { + return nil, fmt.Errorf( + "Failed to convert device %s from %s to %s. New type was %s", + deviceID, curType, targetType, finalType) + + } + return d, err +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/devices.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/devices.go new file mode 100644 index 00000000..d73aee3d --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/devices.go @@ -0,0 +1,616 @@ +package packngo + +import ( + "encoding/json" + "fmt" + "net/url" + "path" + "strconv" +) + +const deviceBasePath = "/devices" + +const ( + NetworkTypeHybrid = "hybrid" + NetworkTypeL2Bonded = "layer2-bonded" + NetworkTypeL2Individual = "layer2-individual" + NetworkTypeL3 = "layer3" +) + +// DeviceService interface defines available device methods +type DeviceService interface { + List(ProjectID string, opts *ListOptions) ([]Device, *Response, error) + Get(DeviceID string, opts *GetOptions) (*Device, *Response, error) + Create(*DeviceCreateRequest) (*Device, *Response, error) + Update(string, *DeviceUpdateRequest) (*Device, *Response, error) + Delete(string, bool) (*Response, error) + Reboot(string) (*Response, error) + PowerOff(string) (*Response, error) + PowerOn(string) (*Response, error) + Lock(string) (*Response, error) + Unlock(string) (*Response, error) + ListBGPSessions(deviceID string, opts *ListOptions) ([]BGPSession, *Response, error) + ListBGPNeighbors(deviceID string, opts *ListOptions) ([]BGPNeighbor, *Response, error) + ListEvents(deviceID string, opts *ListOptions) ([]Event, *Response, error) + GetBandwidth(deviceID string, opts *BandwidthOpts) (*BandwidthIO, *Response, error) +} + +type devicesRoot struct { + Devices []Device `json:"devices"` + Meta meta `json:"meta"` +} + +// Device represents an Equinix Metal device from API +type Device struct { + ID string `json:"id"` + Href string `json:"href,omitempty"` + Hostname string `json:"hostname,omitempty"` + Description *string `json:"description,omitempty"` + State string `json:"state,omitempty"` + Created string `json:"created_at,omitempty"` + Updated string `json:"updated_at,omitempty"` + Locked bool `json:"locked,omitempty"` + BillingCycle string `json:"billing_cycle,omitempty"` + Storage *CPR `json:"storage,omitempty"` + Tags []string `json:"tags,omitempty"` + Network []*IPAddressAssignment `json:"ip_addresses"` + Volumes []*Volume `json:"volumes"` + OS *OS `json:"operating_system,omitempty"` + Plan *Plan `json:"plan,omitempty"` + Facility *Facility `json:"facility,omitempty"` + Metro *Metro `json:"metro,omitempty"` + Project *Project `json:"project,omitempty"` + ProvisionEvents []*Event `json:"provisioning_events,omitempty"` + ProvisionPer float32 `json:"provisioning_percentage,omitempty"` + UserData string `json:"userdata,omitempty"` + User string `json:"user,omitempty"` + RootPassword string `json:"root_password,omitempty"` + IPXEScriptURL string `json:"ipxe_script_url,omitempty"` + AlwaysPXE bool `json:"always_pxe,omitempty"` + HardwareReservation *HardwareReservation `json:"hardware_reservation,omitempty"` + SpotInstance bool `json:"spot_instance,omitempty"` + SpotPriceMax float64 `json:"spot_price_max,omitempty"` + TerminationTime *Timestamp `json:"termination_time,omitempty"` + NetworkPorts []Port `json:"network_ports,omitempty"` + CustomData map[string]interface{} `json:"customdata,omitempty"` + SSHKeys []SSHKey `json:"ssh_keys,omitempty"` + ShortID string `json:"short_id,omitempty"` + SwitchUUID string `json:"switch_uuid,omitempty"` +} + +type NetworkInfo struct { + PublicIPv4 string + PublicIPv6 string + PrivateIPv4 string +} + +type BandwidthIO struct { + Inbound BandwidthComponent `json:"inbound"` + Outbound BandwidthComponent `json:"outbound"` +} + +func (b *BandwidthIO) UnmarshalJSON(buf []byte) error { + tmp := []interface{}{&b.Inbound, &b.Outbound} + wantLen := len(tmp) + if err := json.Unmarshal(buf, &tmp); err != nil { + return err + } + if g, e := len(tmp), wantLen; g != e { + return fmt.Errorf("wrong number of fields in BandwidthIO: %d != %d", g, e) + } + if b.Inbound.Target == BandwidthOutbound { + b.Inbound, b.Outbound = b.Outbound, b.Inbound + } + return nil +} + +type bandwidthRoot struct { + Bandwidth BandwidthIO `json:"bandwidth"` +} + +type BandwidthTarget string + +// BandwidthTarget enums +const ( + BandwidthInbound BandwidthTarget = "inbound" + BandwidthOutbound BandwidthTarget = "outbound" +) + +// BandwidthTags +type BandwidthTags struct { + // AggregatedBy + AggregatedBy string `json:"aggregatedBy"` + + // Name + Name string `json:"name"` +} + +// BandwidthComponent +type BandwidthComponent struct { + // Datapoints + Datapoints []Datapoint `json:"datapoints"` + + // Tags + Tags BandwidthTags `json:"tags"` + + // Target + Target BandwidthTarget `json:"target"` +} +type Datapoint struct { + // Rate is the aggregated sum of Bytes/Second across all ports + Rate *float64 `json:"rate"` + + // When the rate was captured + When Timestamp `json:"when"` +} + +func (d *Datapoint) UnmarshalJSON(buf []byte) error { + tmp := []interface{}{&d.Rate, &d.When} + wantLen := len(tmp) + if err := json.Unmarshal(buf, &tmp); err != nil { + return err + } + if g, e := len(tmp), wantLen; g != e { + return fmt.Errorf("wrong number of fields in BandwidthComponent: %d != %d", g, e) + } + return nil +} + +type BandwidthOpts struct { + From *Timestamp `json:"from,omitempty"` + Until *Timestamp `json:"until,omitempty"` +} + +func (b *BandwidthOpts) Encode() string { + if b == nil { + return "" + } + v := url.Values{} + if b.From != nil { + v.Add("from", strconv.FormatInt(b.From.UTC().Unix(), 10)) + } + if b.Until != nil { + v.Add("until", strconv.FormatInt(b.Until.UTC().Unix(), 10)) + } + return v.Encode() +} + +func (b *BandwidthOpts) WithQuery(apiPath string) string { + params := b.Encode() + if params != "" { + // parse path, take existing vars + return fmt.Sprintf("%s?%s", apiPath, params) + } + return apiPath +} + +func (d *DeviceServiceOp) GetBandwidth(deviceID string, opts *BandwidthOpts) (*BandwidthIO, *Response, error) { + endpointPath := path.Join(deviceBasePath, deviceID, "bandwidth") + apiPathQuery := opts.WithQuery(endpointPath) + bw := new(bandwidthRoot) + resp, err := d.client.DoRequest("GET", apiPathQuery, nil, bw) + if err != nil { + return nil, resp, err + } + return &bw.Bandwidth, resp, nil +} + +func (d *Device) GetNetworkInfo() NetworkInfo { + ni := NetworkInfo{} + for _, ip := range d.Network { + // Initial device IPs are fixed and marked as "Management" + if ip.Management { + if ip.AddressFamily == 4 { + if ip.Public { + ni.PublicIPv4 = ip.Address + } else { + ni.PrivateIPv4 = ip.Address + } + } else { + ni.PublicIPv6 = ip.Address + } + } + } + return ni +} + +func (d Device) String() string { + return Stringify(d) +} + +func (d *Device) NumOfBonds() int { + numOfBonds := 0 + for _, p := range d.NetworkPorts { + if p.Type == "NetworkBondPort" { + numOfBonds++ + } + } + return numOfBonds +} + +func (d *Device) GetPortsInBond(name string) map[string]*Port { + ports := map[string]*Port{} + for _, port := range d.NetworkPorts { + if port.Bond != nil && port.Bond.Name == name { + p := port + ports[p.Name] = &p + } + } + return ports +} + +func (d *Device) GetBondPorts() map[string]*Port { + ports := map[string]*Port{} + for _, port := range d.NetworkPorts { + if port.Type == "NetworkBondPort" { + p := port + ports[p.Name] = &p + } + } + return ports +} + +func (d *Device) GetPhysicalPorts() map[string]*Port { + ports := map[string]*Port{} + for _, port := range d.NetworkPorts { + if port.Type == "NetworkPort" { + p := port + ports[p.Name] = &p + } + } + return ports +} + +func (d *Device) GetPortByName(name string) (*Port, error) { + for _, port := range d.NetworkPorts { + if port.Name == name { + return &port, nil + } + } + return nil, fmt.Errorf("Port %s not found in device %s", name, d.ID) +} + +type ports map[string]*Port + +func (ports ports) allBonded() bool { + if ports == nil { + return false + } + + if len(ports) == 0 { + return false + } + + for _, p := range ports { + if (p == nil) || (!p.Data.Bonded) { + return false + } + } + return true +} + +func (d *Device) HasManagementIPs() bool { + for _, ip := range d.Network { + if ip.Management { + return true + } + } + return false +} + +// GetNetworkType returns a composite network type identification for a device +// based on the plan, network_type, and IP management state of the device. +// GetNetworkType provides the same composite state rendered in the Packet +// Portal for a given device. +func (d *Device) GetNetworkType() string { + if d.Plan != nil { + if d.Plan.Slug == "baremetal_0" || d.Plan.Slug == "baremetal_1" { + return NetworkTypeL3 + } + if d.Plan.Slug == "baremetal_1e" { + return NetworkTypeHybrid + } + } + + bonds := ports(d.GetBondPorts()) + phys := ports(d.GetPhysicalPorts()) + + if bonds.allBonded() { + if phys.allBonded() { + if !d.HasManagementIPs() { + return NetworkTypeL2Bonded + } + return NetworkTypeL3 + } + return NetworkTypeHybrid + } + return NetworkTypeL2Individual +} + +type IPAddressCreateRequest struct { + // Address Family for IP Address + AddressFamily int `json:"address_family"` + + // Address Type for IP Address + Public bool `json:"public"` + + // CIDR Size for the IP Block created. Valid values depends on the operating system provisioned. + CIDR int `json:"cidr,omitempty"` + + // Reservations are UUIDs of any IP reservations to use when assigning IPs + Reservations []string `json:"ip_reservations,omitempty"` +} + +// CPR is a struct for custom partitioning and RAID +// If you don't want to bother writing the struct, just write the CPR conf to +// a string and then do +// +// var cpr CPR +// err := json.Unmarshal([]byte(cprString), &cpr) +// if err != nil { +// log.Fatal(err) +// } +type CPR struct { + Disks []struct { + Device string `json:"device"` + WipeTable bool `json:"wipeTable"` + Partitions []struct { + Label string `json:"label"` + Number int `json:"number"` + Size string `json:"size"` + } `json:"partitions"` + } `json:"disks"` + Raid []struct { + Devices []string `json:"devices"` + Level string `json:"level"` + Name string `json:"name"` + } `json:"raid,omitempty"` + Filesystems []struct { + Mount struct { + Device string `json:"device"` + Format string `json:"format"` + Point string `json:"point"` + Create struct { + Options []string `json:"options"` + } `json:"create"` + } `json:"mount"` + } `json:"filesystems"` +} + +// DeviceCreateRequest type used to create an Equinix Metal device +type DeviceCreateRequest struct { + Hostname string `json:"hostname"` + Plan string `json:"plan"` + Facility []string `json:"facility,omitempty"` + Metro string `json:"metro,omitempty"` + OS string `json:"operating_system"` + BillingCycle string `json:"billing_cycle"` + ProjectID string `json:"project_id"` + UserData string `json:"userdata"` + Storage *CPR `json:"storage,omitempty"` + Tags []string `json:"tags"` + Description string `json:"description,omitempty"` + IPXEScriptURL string `json:"ipxe_script_url,omitempty"` + PublicIPv4SubnetSize int `json:"public_ipv4_subnet_size,omitempty"` + AlwaysPXE bool `json:"always_pxe,omitempty"` + HardwareReservationID string `json:"hardware_reservation_id,omitempty"` + SpotInstance bool `json:"spot_instance,omitempty"` + SpotPriceMax float64 `json:"spot_price_max,omitempty,string"` + TerminationTime *Timestamp `json:"termination_time,omitempty"` + CustomData string `json:"customdata,omitempty"` + // UserSSHKeys is a list of user UUIDs - essentially a list of + // collaborators. The users must be a collaborator in the same project + // where the device is created. The user's SSH keys then go to the + // device + UserSSHKeys []string `json:"user_ssh_keys,omitempty"` + // Project SSHKeys is a list of SSHKeys resource UUIDs. If this param + // is supplied, only the listed SSHKeys will go to the device. + // Any other Project SSHKeys and any User SSHKeys will not be present + // in the device. + ProjectSSHKeys []string `json:"project_ssh_keys,omitempty"` + Features map[string]string `json:"features,omitempty"` + IPAddresses []IPAddressCreateRequest `json:"ip_addresses,omitempty"` +} + +// DeviceUpdateRequest type used to update an Equinix Metal device +type DeviceUpdateRequest struct { + Hostname *string `json:"hostname,omitempty"` + Description *string `json:"description,omitempty"` + UserData *string `json:"userdata,omitempty"` + Locked *bool `json:"locked,omitempty"` + Tags *[]string `json:"tags,omitempty"` + AlwaysPXE *bool `json:"always_pxe,omitempty"` + IPXEScriptURL *string `json:"ipxe_script_url,omitempty"` + CustomData *string `json:"customdata,omitempty"` +} + +func (d DeviceCreateRequest) String() string { + return Stringify(d) +} + +// DeviceActionRequest type used to execute actions on devices +type DeviceActionRequest struct { + Type string `json:"type"` +} + +type DeviceDeleteRequest struct { + Force bool `json:"force_delete"` +} + +func (d DeviceActionRequest) String() string { + return Stringify(d) +} + +// DeviceServiceOp implements DeviceService +type DeviceServiceOp struct { + client *Client +} + +// List returns devices on a project +// +// Regarding ListOptions.Search: The API documentation does not provide guidance +// on the fields that will be searched using this parameter, so this behavior is +// undefined and prone to change. +// +// As of 2020-10-20, ListOptions.Search will look for matches in the following +// Device properties: Hostname, Description, Tags, ID, ShortID, Network.Address, +// Plan.Name, Plan.Slug, Facility.Code, Facility.Name, OS.Name, OS.Slug, +// HardwareReservation.ID, HardwareReservation.ShortID +func (s *DeviceServiceOp) List(projectID string, opts *ListOptions) (devices []Device, resp *Response, err error) { + opts = opts.Including("facility") + endpointPath := path.Join(projectBasePath, projectID, deviceBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + + for { + subset := new(devicesRoot) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + devices = append(devices, subset.Devices...) + + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + + return + } +} + +// Get returns a device by id +func (s *DeviceServiceOp) Get(deviceID string, opts *GetOptions) (*Device, *Response, error) { + opts = opts.Including("facility") + endpointPath := path.Join(deviceBasePath, deviceID) + apiPathQuery := opts.WithQuery(endpointPath) + device := new(Device) + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, device) + if err != nil { + return nil, resp, err + } + return device, resp, err +} + +// Create creates a new device +func (s *DeviceServiceOp) Create(createRequest *DeviceCreateRequest) (*Device, *Response, error) { + apiPath := path.Join(projectBasePath, createRequest.ProjectID, deviceBasePath) + device := new(Device) + + resp, err := s.client.DoRequest("POST", apiPath, createRequest, device) + if err != nil { + return nil, resp, err + } + return device, resp, err +} + +// Update updates an existing device +func (s *DeviceServiceOp) Update(deviceID string, updateRequest *DeviceUpdateRequest) (*Device, *Response, error) { + opts := &GetOptions{} + opts = opts.Including("facility") + endpointPath := path.Join(deviceBasePath, deviceID) + apiPathQuery := opts.WithQuery(endpointPath) + device := new(Device) + + resp, err := s.client.DoRequest("PUT", apiPathQuery, updateRequest, device) + if err != nil { + return nil, resp, err + } + + return device, resp, err +} + +// Delete deletes a device +func (s *DeviceServiceOp) Delete(deviceID string, force bool) (*Response, error) { + apiPath := path.Join(deviceBasePath, deviceID) + req := &DeviceDeleteRequest{Force: force} + + return s.client.DoRequest("DELETE", apiPath, req, nil) +} + +// Reboot reboots on a device +func (s *DeviceServiceOp) Reboot(deviceID string) (*Response, error) { + apiPath := path.Join(deviceBasePath, deviceID, "actions") + action := &DeviceActionRequest{Type: "reboot"} + + return s.client.DoRequest("POST", apiPath, action, nil) +} + +// PowerOff powers on a device +func (s *DeviceServiceOp) PowerOff(deviceID string) (*Response, error) { + apiPath := path.Join(deviceBasePath, deviceID, "actions") + action := &DeviceActionRequest{Type: "power_off"} + + return s.client.DoRequest("POST", apiPath, action, nil) +} + +// PowerOn powers on a device +func (s *DeviceServiceOp) PowerOn(deviceID string) (*Response, error) { + apiPath := path.Join(deviceBasePath, deviceID, "actions") + action := &DeviceActionRequest{Type: "power_on"} + + return s.client.DoRequest("POST", apiPath, action, nil) +} + +type lockType struct { + Locked bool `json:"locked"` +} + +// Lock sets a device to "locked" +func (s *DeviceServiceOp) Lock(deviceID string) (*Response, error) { + apiPath := path.Join(deviceBasePath, deviceID) + action := lockType{Locked: true} + + return s.client.DoRequest("PATCH", apiPath, action, nil) +} + +// Unlock sets a device to "unlocked" +func (s *DeviceServiceOp) Unlock(deviceID string) (*Response, error) { + apiPath := path.Join(deviceBasePath, deviceID) + action := lockType{Locked: false} + + return s.client.DoRequest("PATCH", apiPath, action, nil) +} + +func (s *DeviceServiceOp) ListBGPNeighbors(deviceID string, opts *ListOptions) ([]BGPNeighbor, *Response, error) { + root := new(bgpNeighborsRoot) + endpointPath := path.Join(deviceBasePath, deviceID, bgpNeighborsBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, root) + if err != nil { + return nil, resp, err + } + + return root.BGPNeighbors, resp, err +} + +// ListBGPSessions returns all BGP Sessions associated with the device +func (s *DeviceServiceOp) ListBGPSessions(deviceID string, opts *ListOptions) (bgpSessions []BGPSession, resp *Response, err error) { + + endpointPath := path.Join(deviceBasePath, deviceID, bgpSessionBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + + for { + subset := new(bgpSessionsRoot) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + bgpSessions = append(bgpSessions, subset.Sessions...) + + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + return + } +} + +// ListEvents returns list of device events +func (s *DeviceServiceOp) ListEvents(deviceID string, opts *ListOptions) ([]Event, *Response, error) { + apiPath := path.Join(deviceBasePath, deviceID, eventBasePath) + + return listEvents(s.client, apiPath, opts) +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/doc.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/doc.go new file mode 100644 index 00000000..52a192a0 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/doc.go @@ -0,0 +1,3 @@ +// Package packngo implements the Equinix Metal API +// documented at https://metal.equinix.com/developers/api. +package packngo diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/email.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/email.go new file mode 100644 index 00000000..e82ed32d --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/email.go @@ -0,0 +1,89 @@ +package packngo + +import ( + "path" +) + +const emailBasePath = "/emails" + +// EmailRequest type used to add an email address to the current user +type EmailRequest struct { + Address string `json:"address,omitempty"` + Default *bool `json:"default,omitempty"` +} + +// EmailService interface defines available email methods +type EmailService interface { + Get(string, *GetOptions) (*Email, *Response, error) + Create(*EmailRequest) (*Email, *Response, error) + Update(string, *EmailRequest) (*Email, *Response, error) + Delete(string) (*Response, error) +} + +// Email represents a user's email address +type Email struct { + ID string `json:"id"` + Address string `json:"address"` + Default bool `json:"default,omitempty"` + URL string `json:"href,omitempty"` +} + +func (e Email) String() string { + return Stringify(e) +} + +// EmailServiceOp implements EmailService +type EmailServiceOp struct { + client *Client +} + +// Get retrieves an email by id +func (s *EmailServiceOp) Get(emailID string, opts *GetOptions) (*Email, *Response, error) { + endpointPath := path.Join(emailBasePath, emailID) + apiPathQuery := opts.WithQuery(endpointPath) + email := new(Email) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, email) + if err != nil { + return nil, resp, err + } + + return email, resp, err +} + +// Create adds a new email address to the current user. +func (s *EmailServiceOp) Create(request *EmailRequest) (*Email, *Response, error) { + email := new(Email) + + resp, err := s.client.DoRequest("POST", emailBasePath, request, email) + if err != nil { + return nil, resp, err + } + + return email, resp, err +} + +// Delete removes the email address from the current user account +func (s *EmailServiceOp) Delete(emailID string) (*Response, error) { + apiPath := path.Join(emailBasePath, emailID) + + resp, err := s.client.DoRequest("DELETE", apiPath, nil, nil) + if err != nil { + return resp, err + } + + return resp, err +} + +// Update email parameters +func (s *EmailServiceOp) Update(emailID string, request *EmailRequest) (*Email, *Response, error) { + email := new(Email) + apiPath := path.Join(emailBasePath, emailID) + + resp, err := s.client.DoRequest("PUT", apiPath, request, email) + if err != nil { + return nil, resp, err + } + + return email, resp, err +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/events.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/events.go new file mode 100644 index 00000000..cfc7bcc3 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/events.go @@ -0,0 +1,81 @@ +package packngo + +import ( + "path" +) + +const eventBasePath = "/events" + +// Event struct +type Event struct { + ID string `json:"id,omitempty"` + State string `json:"state,omitempty"` + Type string `json:"type,omitempty"` + Body string `json:"body,omitempty"` + Relationships []Href `json:"relationships,omitempty"` + Interpolated string `json:"interpolated,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + Href string `json:"href,omitempty"` +} + +type eventsRoot struct { + Events []Event `json:"events,omitempty"` + Meta meta `json:"meta,omitempty"` +} + +// EventService interface defines available event functions +type EventService interface { + List(*ListOptions) ([]Event, *Response, error) + Get(string, *GetOptions) (*Event, *Response, error) +} + +// EventServiceOp implements EventService +type EventServiceOp struct { + client *Client +} + +// List returns all events +func (s *EventServiceOp) List(listOpt *ListOptions) ([]Event, *Response, error) { + return listEvents(s.client, eventBasePath, listOpt) +} + +// Get returns an event by ID +func (s *EventServiceOp) Get(eventID string, getOpt *GetOptions) (*Event, *Response, error) { + apiPath := path.Join(eventBasePath, eventID) + return get(s.client, apiPath, getOpt) +} + +// list helper function for all event functions +func listEvents(client requestDoer, endpointPath string, opts *ListOptions) (events []Event, resp *Response, err error) { + apiPathQuery := opts.WithQuery(endpointPath) + + for { + subset := new(eventsRoot) + + resp, err = client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + events = append(events, subset.Events...) + + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + return + } + +} + +func get(client *Client, endpointPath string, opts *GetOptions) (*Event, *Response, error) { + event := new(Event) + + apiPathQuery := opts.WithQuery(endpointPath) + + resp, err := client.DoRequest("GET", apiPathQuery, nil, event) + if err != nil { + return nil, resp, err + } + + return event, resp, err +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/facilities.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/facilities.go new file mode 100644 index 00000000..91c21d79 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/facilities.go @@ -0,0 +1,54 @@ +package packngo + +const facilityBasePath = "/facilities" + +// FacilityService interface defines available facility methods +type FacilityService interface { + List(*ListOptions) ([]Facility, *Response, error) +} + +type facilityRoot struct { + Facilities []Facility `json:"facilities"` +} + +// Facility represents an Equinix Metal facility +type Facility struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Code string `json:"code,omitempty"` + Features []string `json:"features,omitempty"` + Address *Address `json:"address,omitempty"` + Metro *Metro `json:"metro,omitempty"` + URL string `json:"href,omitempty"` +} + +func (f Facility) String() string { + return Stringify(f) +} + +// Address - the physical address of the facility +type Address struct { + ID string `json:"id,omitempty"` +} + +func (a Address) String() string { + return Stringify(a) +} + +// FacilityServiceOp implements FacilityService +type FacilityServiceOp struct { + client *Client +} + +// List returns all facilities +func (s *FacilityServiceOp) List(opts *ListOptions) ([]Facility, *Response, error) { + root := new(facilityRoot) + apiPathQuery := opts.WithQuery(facilityBasePath) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, root) + if err != nil { + return nil, resp, err + } + + return root.Facilities, resp, err +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/go.mod b/provider/equinixmetal/vendor/github.com/packethost/packngo/go.mod new file mode 100644 index 00000000..dc09690a --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/go.mod @@ -0,0 +1,9 @@ +module github.com/packethost/packngo + +require ( + github.com/dnaeon/go-vcr v1.0.1 + github.com/stretchr/testify v1.5.1 + golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a +) + +go 1.15 diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/go.sum b/provider/equinixmetal/vendor/github.com/packethost/packngo/go.sum new file mode 100644 index 00000000..86d940d3 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/go.sum @@ -0,0 +1,21 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a h1:y6sBfNd1b9Wy08a6K1Z1DZc4aXABUN5TKjkYhz7UKmo= +golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/hardware_reservations.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/hardware_reservations.go new file mode 100644 index 00000000..c1b848dd --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/hardware_reservations.go @@ -0,0 +1,92 @@ +package packngo + +import ( + "path" +) + +const hardwareReservationBasePath = "/hardware-reservations" + +// HardwareReservationService interface defines available hardware reservation functions +type HardwareReservationService interface { + Get(hardwareReservationID string, getOpt *GetOptions) (*HardwareReservation, *Response, error) + List(projectID string, listOpt *ListOptions) ([]HardwareReservation, *Response, error) + Move(string, string) (*HardwareReservation, *Response, error) +} + +// HardwareReservationServiceOp implements HardwareReservationService +type HardwareReservationServiceOp struct { + client requestDoer +} + +// HardwareReservation struct +type HardwareReservation struct { + ID string `json:"id,omitempty"` + ShortID string `json:"short_id,omitempty"` + Facility Facility `json:"facility,omitempty"` + Plan Plan `json:"plan,omitempty"` + Provisionable bool `json:"provisionable,omitempty"` + Spare bool `json:"spare,omitempty"` + SwitchUUID string `json:"switch_uuid,omitempty"` + Intervals int `json:"intervals,omitempty"` + CurrentPeriod int `json:"current_period,omitempty"` + Href string `json:"href,omitempty"` + Project Project `json:"project,omitempty"` + Device *Device `json:"device,omitempty"` + CreatedAt Timestamp `json:"created_at,omitempty"` +} + +type hardwareReservationRoot struct { + HardwareReservations []HardwareReservation `json:"hardware_reservations"` + Meta meta `json:"meta"` +} + +// List returns all hardware reservations for a given project +func (s *HardwareReservationServiceOp) List(projectID string, opts *ListOptions) (reservations []HardwareReservation, resp *Response, err error) { + endpointPath := path.Join(projectBasePath, projectID, hardwareReservationBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + + for { + subset := new(hardwareReservationRoot) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + reservations = append(reservations, subset.HardwareReservations...) + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + return + } +} + +// Get returns a single hardware reservation +func (s *HardwareReservationServiceOp) Get(hardwareReservationdID string, opts *GetOptions) (*HardwareReservation, *Response, error) { + hardwareReservation := new(HardwareReservation) + + endpointPath := path.Join(hardwareReservationBasePath, hardwareReservationdID) + apiPathQuery := opts.WithQuery(endpointPath) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, hardwareReservation) + if err != nil { + return nil, resp, err + } + + return hardwareReservation, resp, err +} + +// Move a hardware reservation to another project +func (s *HardwareReservationServiceOp) Move(hardwareReservationdID, projectID string) (*HardwareReservation, *Response, error) { + hardwareReservation := new(HardwareReservation) + apiPath := path.Join(hardwareReservationBasePath, hardwareReservationdID, "move") + body := map[string]string{} + body["project_id"] = projectID + + resp, err := s.client.DoRequest("POST", apiPath, body, hardwareReservation) + if err != nil { + return nil, resp, err + } + + return hardwareReservation, resp, err +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/ip.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/ip.go new file mode 100644 index 00000000..e740cc4c --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/ip.go @@ -0,0 +1,245 @@ +package packngo + +import ( + "fmt" + "path" +) + +const ipBasePath = "/ips" + +const ( + // PublicIPv4 fixed string representation of public ipv4 + PublicIPv4 = "public_ipv4" + // PrivateIPv4 fixed string representation of private ipv4 + PrivateIPv4 = "private_ipv4" + // GlobalIPv4 fixed string representation of global ipv4 + GlobalIPv4 = "global_ipv4" + // PublicIPv6 fixed string representation of public ipv6 + PublicIPv6 = "public_ipv6" + // PrivateIPv6 fixed string representation of private ipv6 + PrivateIPv6 = "private_ipv6" + // GlobalIPv6 fixed string representation of global ipv6 + GlobalIPv6 = "global_ipv6" +) + +// DeviceIPService handles assignment of addresses from reserved blocks to instances in a project. +type DeviceIPService interface { + Assign(deviceID string, assignRequest *AddressStruct) (*IPAddressAssignment, *Response, error) + Unassign(assignmentID string) (*Response, error) + Get(assignmentID string, getOpt *GetOptions) (*IPAddressAssignment, *Response, error) + List(deviceID string, opts *ListOptions) ([]IPAddressAssignment, *Response, error) +} + +// ProjectIPService handles reservation of IP address blocks for a project. +type ProjectIPService interface { + Get(reservationID string, getOpt *GetOptions) (*IPAddressReservation, *Response, error) + List(projectID string, opts *ListOptions) ([]IPAddressReservation, *Response, error) + Request(projectID string, ipReservationReq *IPReservationRequest) (*IPAddressReservation, *Response, error) + Remove(ipReservationID string) (*Response, error) + AvailableAddresses(ipReservationID string, r *AvailableRequest) ([]string, *Response, error) +} + +type IpAddressCommon struct { //nolint:golint + ID string `json:"id"` + Address string `json:"address"` + Gateway string `json:"gateway"` + Network string `json:"network"` + AddressFamily int `json:"address_family"` + Netmask string `json:"netmask"` + Public bool `json:"public"` + CIDR int `json:"cidr"` + Created string `json:"created_at,omitempty"` + Updated string `json:"updated_at,omitempty"` + Href string `json:"href"` + Management bool `json:"management"` + Manageable bool `json:"manageable"` + Metro *Metro `json:"metro,omitempty"` + Project Href `json:"project"` + Global bool `json:"global_ip"` + Tags []string `json:"tags,omitempty"` + CustomData interface{} `json:"customdata,omitempty"` +} + +// IPAddressReservation is created when user sends IP reservation request for a project (considering it's within quota). +type IPAddressReservation struct { + IpAddressCommon + Assignments []*IPAddressAssignment `json:"assignments"` + Facility *Facility `json:"facility,omitempty"` + Available string `json:"available"` + Addon bool `json:"addon"` + Bill bool `json:"bill"` + Description *string `json:"details"` +} + +// AvailableResponse is a type for listing of available addresses from a reserved block. +type AvailableResponse struct { + Available []string `json:"available"` +} + +// AvailableRequest is a type for listing available addresses from a reserved block. +type AvailableRequest struct { + CIDR int `json:"cidr"` +} + +// IPAddressAssignment is created when an IP address from reservation block is assigned to a device. +type IPAddressAssignment struct { + IpAddressCommon + AssignedTo Href `json:"assigned_to"` +} + +// IPReservationRequest represents the body of a reservation request. +type IPReservationRequest struct { + Type string `json:"type"` + Quantity int `json:"quantity"` + Description string `json:"details,omitempty"` + Facility *string `json:"facility,omitempty"` + Metro *string `json:"metro,omitempty"` + Tags []string `json:"tags,omitempty"` + CustomData interface{} `json:"customdata,omitempty"` + // FailOnApprovalRequired if the IP request cannot be approved automatically, rather than sending to + // the longer Equinix Metal approval process, fail immediately with a 422 error + FailOnApprovalRequired bool `json:"fail_on_approval_required,omitempty"` +} + +// AddressStruct is a helper type for request/response with dict like {"address": ... } +type AddressStruct struct { + Address string `json:"address"` +} + +func deleteFromIP(client *Client, resourceID string) (*Response, error) { + apiPath := path.Join(ipBasePath, resourceID) + + return client.DoRequest("DELETE", apiPath, nil, nil) +} + +func (i IPAddressReservation) String() string { + return Stringify(i) +} + +func (i IPAddressAssignment) String() string { + return Stringify(i) +} + +// DeviceIPServiceOp is interface for IP-address assignment methods. +type DeviceIPServiceOp struct { + client *Client +} + +// Unassign unassigns an IP address from the device to which it is currently assigned. +// This will remove the relationship between an IP and the device and will make the IP +// address available to be assigned to another device. +func (i *DeviceIPServiceOp) Unassign(assignmentID string) (*Response, error) { + return deleteFromIP(i.client, assignmentID) +} + +// Assign assigns an IP address to a device. +// The IP address must be in one of the IP ranges assigned to the device’s project. +func (i *DeviceIPServiceOp) Assign(deviceID string, assignRequest *AddressStruct) (*IPAddressAssignment, *Response, error) { + apiPath := path.Join(deviceBasePath, deviceID, ipBasePath) + ipa := new(IPAddressAssignment) + + resp, err := i.client.DoRequest("POST", apiPath, assignRequest, ipa) + if err != nil { + return nil, resp, err + } + + return ipa, resp, err +} + +// Get returns assignment by ID. +func (i *DeviceIPServiceOp) Get(assignmentID string, opts *GetOptions) (*IPAddressAssignment, *Response, error) { + endpointPath := path.Join(ipBasePath, assignmentID) + apiPathQuery := opts.WithQuery(endpointPath) + ipa := new(IPAddressAssignment) + + resp, err := i.client.DoRequest("GET", apiPathQuery, nil, ipa) + if err != nil { + return nil, resp, err + } + + return ipa, resp, err +} + +// List list all of the IP address assignments on a device +func (i *DeviceIPServiceOp) List(deviceID string, opts *ListOptions) ([]IPAddressAssignment, *Response, error) { + endpointPath := path.Join(deviceBasePath, deviceID, ipBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + + //ipList represents collection of IP Address reservations + type ipList struct { + IPs []IPAddressAssignment `json:"ip_addresses,omitempty"` + } + + ips := new(ipList) + + resp, err := i.client.DoRequest("GET", apiPathQuery, nil, ips) + if err != nil { + return nil, resp, err + } + + return ips.IPs, resp, err +} + +// ProjectIPServiceOp is interface for IP assignment methods. +type ProjectIPServiceOp struct { + client *Client +} + +// Get returns reservation by ID. +func (i *ProjectIPServiceOp) Get(reservationID string, opts *GetOptions) (*IPAddressReservation, *Response, error) { + endpointPath := path.Join(ipBasePath, reservationID) + apiPathQuery := opts.WithQuery(endpointPath) + ipr := new(IPAddressReservation) + + resp, err := i.client.DoRequest("GET", apiPathQuery, nil, ipr) + if err != nil { + return nil, resp, err + } + + return ipr, resp, err +} + +// List provides a list of IP resevations for a single project. +func (i *ProjectIPServiceOp) List(projectID string, opts *ListOptions) ([]IPAddressReservation, *Response, error) { + endpointPath := path.Join(projectBasePath, projectID, ipBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + reservations := new(struct { + Reservations []IPAddressReservation `json:"ip_addresses"` + }) + + resp, err := i.client.DoRequest("GET", apiPathQuery, nil, reservations) + if err != nil { + return nil, resp, err + } + return reservations.Reservations, resp, nil +} + +// Request requests more IP space for a project in order to have additional IP addresses to assign to devices. +func (i *ProjectIPServiceOp) Request(projectID string, ipReservationReq *IPReservationRequest) (*IPAddressReservation, *Response, error) { + apiPath := path.Join(projectBasePath, projectID, ipBasePath) + ipr := new(IPAddressReservation) + + resp, err := i.client.DoRequest("POST", apiPath, ipReservationReq, ipr) + if err != nil { + return nil, resp, err + } + return ipr, resp, err +} + +// Remove removes an IP reservation from the project. +func (i *ProjectIPServiceOp) Remove(ipReservationID string) (*Response, error) { + return deleteFromIP(i.client, ipReservationID) +} + +// AvailableAddresses lists addresses available from a reserved block +func (i *ProjectIPServiceOp) AvailableAddresses(ipReservationID string, r *AvailableRequest) ([]string, *Response, error) { + apiPathQuery := fmt.Sprintf("%s/%s/available?cidr=%d", ipBasePath, ipReservationID, r.CIDR) + ar := new(AvailableResponse) + + resp, err := i.client.DoRequest("GET", apiPathQuery, r, ar) + if err != nil { + return nil, resp, err + } + return ar.Available, resp, nil + +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/metros.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/metros.go new file mode 100644 index 00000000..2a3924dc --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/metros.go @@ -0,0 +1,42 @@ +package packngo + +const metroBasePath = "/locations/metros" + +// MetroService interface defines available metro methods +type MetroService interface { + List(*ListOptions) ([]Metro, *Response, error) +} + +type metroRoot struct { + Metros []Metro `json:"metros"` +} + +// Metro represents an Equinix Metal metro +type Metro struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Code string `json:"code,omitempty"` + Country string `json:"country,omitempty"` +} + +func (f Metro) String() string { + return Stringify(f) +} + +// MetroServiceOp implements MetroService +type MetroServiceOp struct { + client *Client +} + +// List returns all metros +func (s *MetroServiceOp) List(opts *ListOptions) ([]Metro, *Response, error) { + root := new(metroRoot) + apiPathQuery := opts.WithQuery(metroBasePath) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, root) + if err != nil { + return nil, resp, err + } + + return root.Metros, resp, err +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/notifications.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/notifications.go new file mode 100644 index 00000000..073484cd --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/notifications.go @@ -0,0 +1,94 @@ +package packngo + +import ( + "path" +) + +const notificationBasePath = "/notifications" + +// Notification struct +type Notification struct { + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Body string `json:"body,omitempty"` + Severity string `json:"severity,omitempty"` + Read bool `json:"read,omitempty"` + Context string `json:"context,omitempty"` + CreatedAt Timestamp `json:"created_at,omitempty"` + UpdatedAt Timestamp `json:"updated_at,omitempty"` + User Href `json:"user,omitempty"` + Href string `json:"href,omitempty"` +} + +type notificationsRoot struct { + Notifications []Notification `json:"notifications,omitempty"` + Meta meta `json:"meta,omitempty"` +} + +// NotificationService interface defines available event functions +type NotificationService interface { + List(*ListOptions) ([]Notification, *Response, error) + Get(string, *GetOptions) (*Notification, *Response, error) + MarkAsRead(string) (*Notification, *Response, error) +} + +// NotificationServiceOp implements NotificationService +type NotificationServiceOp struct { + client *Client +} + +// List returns all notifications +func (s *NotificationServiceOp) List(listOpt *ListOptions) ([]Notification, *Response, error) { + return listNotifications(s.client, notificationBasePath, listOpt) +} + +// Get returns a notification by ID +func (s *NotificationServiceOp) Get(notificationID string, opts *GetOptions) (*Notification, *Response, error) { + endpointPath := path.Join(notificationBasePath, notificationID) + apiPathQuery := opts.WithQuery(endpointPath) + return getNotifications(s.client, apiPathQuery) +} + +// Marks notification as read by ID +func (s *NotificationServiceOp) MarkAsRead(notificationID string) (*Notification, *Response, error) { + apiPath := path.Join(notificationBasePath, notificationID) + return markAsRead(s.client, apiPath) +} + +// list helper function for all notification functions +func listNotifications(client *Client, endpointPath string, opts *ListOptions) ([]Notification, *Response, error) { + root := new(notificationsRoot) + + apiPathQuery := opts.WithQuery(endpointPath) + + resp, err := client.DoRequest("GET", apiPathQuery, nil, root) + if err != nil { + return nil, resp, err + } + + return root.Notifications, resp, err +} + +func getNotifications(client *Client, apiPath string) (*Notification, *Response, error) { + + notification := new(Notification) + + resp, err := client.DoRequest("GET", apiPath, nil, notification) + if err != nil { + return nil, resp, err + } + + return notification, resp, err +} + +func markAsRead(client *Client, apiPath string) (*Notification, *Response, error) { + + notification := new(Notification) + + resp, err := client.DoRequest("PUT", apiPath, nil, notification) + if err != nil { + return nil, resp, err + } + + return notification, resp, err +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/operatingsystems.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/operatingsystems.go new file mode 100644 index 00000000..087fba6c --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/operatingsystems.go @@ -0,0 +1,42 @@ +package packngo + +const osBasePath = "/operating-systems" + +// OSService interface defines available operating_systems methods +type OSService interface { + List() ([]OS, *Response, error) +} + +type osRoot struct { + OperatingSystems []OS `json:"operating_systems"` +} + +// OS represents an Equinix Metal operating system +type OS struct { + Name string `json:"name"` + Slug string `json:"slug"` + Distro string `json:"distro"` + Version string `json:"version"` + ProvisionableOn []string `json:"provisionable_on"` +} + +func (o OS) String() string { + return Stringify(o) +} + +// OSServiceOp implements OSService +type OSServiceOp struct { + client *Client +} + +// List returns all available operating systems +func (s *OSServiceOp) List() ([]OS, *Response, error) { + root := new(osRoot) + + resp, err := s.client.DoRequest("GET", osBasePath, nil, root) + if err != nil { + return nil, resp, err + } + + return root.OperatingSystems, resp, err +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/organizations.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/organizations.go new file mode 100644 index 00000000..07b0d6bc --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/organizations.go @@ -0,0 +1,168 @@ +package packngo + +import ( + "path" +) + +// API documentation https://metal.equinix.com/developers/api/organizations/ +const organizationBasePath = "/organizations" + +// OrganizationService interface defines available organization methods +type OrganizationService interface { + List(*ListOptions) ([]Organization, *Response, error) + Get(string, *GetOptions) (*Organization, *Response, error) + Create(*OrganizationCreateRequest) (*Organization, *Response, error) + Update(string, *OrganizationUpdateRequest) (*Organization, *Response, error) + Delete(string) (*Response, error) + ListPaymentMethods(string) ([]PaymentMethod, *Response, error) + ListEvents(string, *ListOptions) ([]Event, *Response, error) +} + +type organizationsRoot struct { + Organizations []Organization `json:"organizations"` + Meta meta `json:"meta"` +} + +// Organization represents an Equinix Metal organization +type Organization struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Website string `json:"website,omitempty"` + Twitter string `json:"twitter,omitempty"` + Created string `json:"created_at,omitempty"` + Updated string `json:"updated_at,omitempty"` + Address Address `json:"address,omitempty"` + TaxID string `json:"tax_id,omitempty"` + MainPhone string `json:"main_phone,omitempty"` + BillingPhone string `json:"billing_phone,omitempty"` + CreditAmount float64 `json:"credit_amount,omitempty"` + Logo string `json:"logo,omitempty"` + LogoThumb string `json:"logo_thumb,omitempty"` + Projects []Project `json:"projects,omitempty"` + URL string `json:"href,omitempty"` + Users []User `json:"members,omitempty"` + Owners []User `json:"owners,omitempty"` +} + +func (o Organization) String() string { + return Stringify(o) +} + +// OrganizationCreateRequest type used to create an Equinix Metal organization +type OrganizationCreateRequest struct { + Name string `json:"name"` + Description string `json:"description"` + Website string `json:"website"` + Twitter string `json:"twitter"` + Logo string `json:"logo"` +} + +func (o OrganizationCreateRequest) String() string { + return Stringify(o) +} + +// OrganizationUpdateRequest type used to update an Equinix Metal organization +type OrganizationUpdateRequest struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Website *string `json:"website,omitempty"` + Twitter *string `json:"twitter,omitempty"` + Logo *string `json:"logo,omitempty"` +} + +func (o OrganizationUpdateRequest) String() string { + return Stringify(o) +} + +// OrganizationServiceOp implements OrganizationService +type OrganizationServiceOp struct { + client *Client +} + +// List returns the user's organizations +func (s *OrganizationServiceOp) List(opts *ListOptions) (orgs []Organization, resp *Response, err error) { + subset := new(organizationsRoot) + + apiPathQuery := opts.WithQuery(organizationBasePath) + + for { + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + orgs = append(orgs, subset.Organizations...) + + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + return + } +} + +// Get returns a organization by id +func (s *OrganizationServiceOp) Get(organizationID string, opts *GetOptions) (*Organization, *Response, error) { + endpointPath := path.Join(organizationBasePath, organizationID) + apiPathQuery := opts.WithQuery(endpointPath) + organization := new(Organization) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, organization) + if err != nil { + return nil, resp, err + } + + return organization, resp, err +} + +// Create creates a new organization +func (s *OrganizationServiceOp) Create(createRequest *OrganizationCreateRequest) (*Organization, *Response, error) { + organization := new(Organization) + + resp, err := s.client.DoRequest("POST", organizationBasePath, createRequest, organization) + if err != nil { + return nil, resp, err + } + + return organization, resp, err +} + +// Update updates an organization +func (s *OrganizationServiceOp) Update(id string, updateRequest *OrganizationUpdateRequest) (*Organization, *Response, error) { + apiPath := path.Join(organizationBasePath, id) + organization := new(Organization) + + resp, err := s.client.DoRequest("PATCH", apiPath, updateRequest, organization) + if err != nil { + return nil, resp, err + } + + return organization, resp, err +} + +// Delete deletes an organizationID +func (s *OrganizationServiceOp) Delete(organizationID string) (*Response, error) { + apiPath := path.Join(organizationBasePath, organizationID) + + return s.client.DoRequest("DELETE", apiPath, nil, nil) +} + +// ListPaymentMethods returns PaymentMethods for an organization +func (s *OrganizationServiceOp) ListPaymentMethods(organizationID string) ([]PaymentMethod, *Response, error) { + apiPath := path.Join(organizationBasePath, organizationID, paymentMethodBasePath) + root := new(paymentMethodsRoot) + + resp, err := s.client.DoRequest("GET", apiPath, nil, root) + if err != nil { + return nil, resp, err + } + + return root.PaymentMethods, resp, err +} + +// ListEvents returns list of organization events +func (s *OrganizationServiceOp) ListEvents(organizationID string, listOpt *ListOptions) ([]Event, *Response, error) { + apiPath := path.Join(organizationBasePath, organizationID, eventBasePath) + + return listEvents(s.client, apiPath, listOpt) +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/packngo.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/packngo.go new file mode 100644 index 00000000..0e3f9f36 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/packngo.go @@ -0,0 +1,422 @@ +package packngo + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "net/http/httputil" + "net/url" + "os" + "regexp" + "strconv" + "strings" + "time" +) + +const ( + authTokenEnvVar = "PACKET_AUTH_TOKEN" + baseURL = "https://api.equinix.com/metal/v1/" + mediaType = "application/json" + debugEnvVar = "PACKNGO_DEBUG" + + headerRateLimit = "X-RateLimit-Limit" + headerRateRemaining = "X-RateLimit-Remaining" + headerRateReset = "X-RateLimit-Reset" + expectedAPIContentTypePrefix = "application/json" +) + +// meta contains pagination information +type meta struct { + Self *Href `json:"self"` + First *Href `json:"first"` + Last *Href `json:"last"` + Previous *Href `json:"previous,omitempty"` + Next *Href `json:"next,omitempty"` + Total int `json:"total"` + CurrentPageNum int `json:"current_page"` + LastPageNum int `json:"last_page"` +} + +// Response is the http response from api calls +type Response struct { + *http.Response + Rate +} + +// Href is an API link +type Href struct { + Href string `json:"href"` +} + +func (r *Response) populateRate() { + // parse the rate limit headers and populate Response.Rate + if limit := r.Header.Get(headerRateLimit); limit != "" { + r.Rate.RequestLimit, _ = strconv.Atoi(limit) + } + if remaining := r.Header.Get(headerRateRemaining); remaining != "" { + r.Rate.RequestsRemaining, _ = strconv.Atoi(remaining) + } + if reset := r.Header.Get(headerRateReset); reset != "" { + if v, _ := strconv.ParseInt(reset, 10, 64); v != 0 { + r.Rate.Reset = Timestamp{time.Unix(v, 0)} + } + } +} + +// ErrorResponse is the http response used on errors +type ErrorResponse struct { + Response *http.Response + Errors []string `json:"errors"` + SingleError string `json:"error"` +} + +func (r *ErrorResponse) Error() string { + return fmt.Sprintf("%v %v: %d %v %v", + r.Response.Request.Method, r.Response.Request.URL, r.Response.StatusCode, strings.Join(r.Errors, ", "), r.SingleError) +} + +// Client is the base API Client +type Client struct { + client *http.Client + debug bool + + BaseURL *url.URL + + UserAgent string + ConsumerToken string + APIKey string + + RateLimit Rate + + // Equinix Metal Api Objects + APIKeys APIKeyService + BGPConfig BGPConfigService + BGPSessions BGPSessionService + Batches BatchService + CapacityService CapacityService + Connections ConnectionService + DeviceIPs DeviceIPService + Devices DeviceService + Emails EmailService + Events EventService + Facilities FacilityService + HardwareReservations HardwareReservationService + Metros MetroService + Notifications NotificationService + OperatingSystems OSService + Organizations OrganizationService + Plans PlanService + Ports PortService + ProjectIPs ProjectIPService + ProjectVirtualNetworks ProjectVirtualNetworkService + Projects ProjectService + SSHKeys SSHKeyService + SpotMarket SpotMarketService + SpotMarketRequests SpotMarketRequestService + TwoFactorAuth TwoFactorAuthService + Users UserService + VirtualCircuits VirtualCircuitService + VolumeAttachments VolumeAttachmentService + Volumes VolumeService + + // DevicePorts + // + // Deprecated: Use Client.Ports or Device methods + DevicePorts DevicePortService + + // VPN + // + // Deprecated: As of March 31, 2021, Doorman service is no longer + // available. See https://metal.equinix.com/developers/docs/accounts/doorman/ + // for more details. + VPN VPNService +} + +// requestDoer provides methods for making HTTP requests and receiving the +// response, errors, and a structured result +// +// This interface is used in *ServiceOp as a mockable alternative to a full +// Client object. +type requestDoer interface { + NewRequest(method, path string, body interface{}) (*http.Request, error) + Do(req *http.Request, v interface{}) (*Response, error) + DoRequest(method, path string, body, v interface{}) (*Response, error) + DoRequestWithHeader(method string, headers map[string]string, path string, body, v interface{}) (*Response, error) +} + +// NewRequest inits a new http request with the proper headers +func (c *Client) NewRequest(method, path string, body interface{}) (*http.Request, error) { + // relative path to append to the endpoint url, no leading slash please + if path[0] == '/' { + path = path[1:] + } + rel, err := url.Parse(path) + if err != nil { + return nil, err + } + + u := c.BaseURL.ResolveReference(rel) + + // json encode the request body, if any + buf := new(bytes.Buffer) + if body != nil { + err := json.NewEncoder(buf).Encode(body) + if err != nil { + return nil, err + } + } + + req, err := http.NewRequest(method, u.String(), buf) + if err != nil { + return nil, err + } + + req.Close = true + + req.Header.Add("X-Auth-Token", c.APIKey) + req.Header.Add("X-Consumer-Token", c.ConsumerToken) + + req.Header.Add("Content-Type", mediaType) + req.Header.Add("Accept", mediaType) + req.Header.Add("User-Agent", c.UserAgent) + return req, nil +} + +// Do executes the http request +func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + response := Response{Response: resp} + response.populateRate() + if c.debug { + dumpResponse(response.Response) + } + dumpDeprecation(response.Response) + c.RateLimit = response.Rate + + err = checkResponse(resp) + // if the response is an error, return the ErrorResponse + if err != nil { + return &response, err + } + + if v != nil { + // if v implements the io.Writer interface, return the raw response + if w, ok := v.(io.Writer); ok { + _, err = io.Copy(w, resp.Body) + if err != nil { + return &response, err + } + } else { + err = json.NewDecoder(resp.Body).Decode(v) + if err != nil { + return &response, err + } + } + } + + return &response, err +} + +// dumpDeprecation logs headers defined by +// https://tools.ietf.org/html/rfc8594 +func dumpDeprecation(resp *http.Response) { + uri := "" + if resp.Request != nil { + uri = resp.Request.Method + " " + resp.Request.URL.Path + } + + deprecation := resp.Header.Get("Deprecation") + if deprecation != "" { + if deprecation == "true" { + deprecation = "" + } else { + deprecation = " on " + deprecation + } + log.Printf("WARNING: %q reported deprecation%s", uri, deprecation) + } + + sunset := resp.Header.Get("Sunset") + if sunset != "" { + log.Printf("WARNING: %q reported sunsetting on %s", uri, sunset) + } + + links := resp.Header.Values("Link") + + for _, s := range links { + for _, ss := range strings.Split(s, ",") { + if strings.Contains(ss, "rel=\"sunset\"") { + link := strings.Split(ss, ";")[0] + log.Printf("WARNING: See %s for sunset details", link) + } else if strings.Contains(ss, "rel=\"deprecation\"") { + link := strings.Split(ss, ";")[0] + log.Printf("WARNING: See %s for deprecation details", link) + } + } + } +} + +func dumpResponse(resp *http.Response) { + o, _ := httputil.DumpResponse(resp, true) + strResp := string(o) + reg, _ := regexp.Compile(`"token":(.+?),`) + reMatches := reg.FindStringSubmatch(strResp) + if len(reMatches) == 2 { + strResp = strings.Replace(strResp, reMatches[1], strings.Repeat("-", len(reMatches[1])), 1) + } + log.Printf("\n=======[RESPONSE]============\n%s\n\n", strResp) +} + +func dumpRequest(req *http.Request) { + r := req.Clone(context.TODO()) + r.Body, _ = req.GetBody() + h := r.Header + if len(h.Get("X-Auth-Token")) != 0 { + h.Set("X-Auth-Token", "**REDACTED**") + } + defer r.Body.Close() + + o, _ := httputil.DumpRequestOut(r, false) + bbs, _ := ioutil.ReadAll(r.Body) + + strReq := string(o) + log.Printf("\n=======[REQUEST]=============\n%s%s\n", string(strReq), string(bbs)) +} + +// DoRequest is a convenience method, it calls NewRequest followed by Do +// v is the interface to unmarshal the response JSON into +func (c *Client) DoRequest(method, path string, body, v interface{}) (*Response, error) { + req, err := c.NewRequest(method, path, body) + if c.debug { + dumpRequest(req) + } + if err != nil { + return nil, err + } + return c.Do(req, v) +} + +// DoRequestWithHeader same as DoRequest +func (c *Client) DoRequestWithHeader(method string, headers map[string]string, path string, body, v interface{}) (*Response, error) { + req, err := c.NewRequest(method, path, body) + for k, v := range headers { + req.Header.Add(k, v) + } + + if c.debug { + dumpRequest(req) + } + if err != nil { + return nil, err + } + return c.Do(req, v) +} + +// NewClient initializes and returns a Client +func NewClient() (*Client, error) { + apiToken := os.Getenv(authTokenEnvVar) + if apiToken == "" { + return nil, fmt.Errorf("you must export %s", authTokenEnvVar) + } + c := NewClientWithAuth("packngo lib", apiToken, nil) + return c, nil + +} + +// NewClientWithAuth initializes and returns a Client, use this to get an API Client to operate on +// N.B.: Equinix Metal's API certificate requires Go 1.5+ to successfully parse. If you are using +// an older version of Go, pass in a custom http.Client with a custom TLS configuration +// that sets "InsecureSkipVerify" to "true" +func NewClientWithAuth(consumerToken string, apiKey string, httpClient *http.Client) *Client { + client, _ := NewClientWithBaseURL(consumerToken, apiKey, httpClient, baseURL) + return client +} + +// NewClientWithBaseURL returns a Client pointing to nonstandard API URL, e.g. +// for mocking the remote API +func NewClientWithBaseURL(consumerToken string, apiKey string, httpClient *http.Client, apiBaseURL string) (*Client, error) { + if httpClient == nil { + httpClient = http.DefaultClient + } + + u, err := url.Parse(apiBaseURL) + if err != nil { + return nil, err + } + + c := &Client{client: httpClient, BaseURL: u, UserAgent: UserAgent, ConsumerToken: consumerToken, APIKey: apiKey} + c.APIKeys = &APIKeyServiceOp{client: c} + c.BGPConfig = &BGPConfigServiceOp{client: c} + c.BGPSessions = &BGPSessionServiceOp{client: c} + c.Batches = &BatchServiceOp{client: c} + c.CapacityService = &CapacityServiceOp{client: c} + c.Connections = &ConnectionServiceOp{client: c} + c.DeviceIPs = &DeviceIPServiceOp{client: c} + c.DevicePorts = &DevicePortServiceOp{client: c} + c.Devices = &DeviceServiceOp{client: c} + c.Emails = &EmailServiceOp{client: c} + c.Events = &EventServiceOp{client: c} + c.Facilities = &FacilityServiceOp{client: c} + c.HardwareReservations = &HardwareReservationServiceOp{client: c} + c.Metros = &MetroServiceOp{client: c} + c.Notifications = &NotificationServiceOp{client: c} + c.OperatingSystems = &OSServiceOp{client: c} + c.Organizations = &OrganizationServiceOp{client: c} + c.Plans = &PlanServiceOp{client: c} + c.Ports = &PortServiceOp{client: c} + c.ProjectIPs = &ProjectIPServiceOp{client: c} + c.ProjectVirtualNetworks = &ProjectVirtualNetworkServiceOp{client: c} + c.Projects = &ProjectServiceOp{client: c} + c.SSHKeys = &SSHKeyServiceOp{client: c} + c.SpotMarket = &SpotMarketServiceOp{client: c} + c.SpotMarketRequests = &SpotMarketRequestServiceOp{client: c} + c.TwoFactorAuth = &TwoFactorAuthServiceOp{client: c} + c.Users = &UserServiceOp{client: c} + c.VirtualCircuits = &VirtualCircuitServiceOp{client: c} + c.VPN = &VPNServiceOp{client: c} + c.VolumeAttachments = &VolumeAttachmentServiceOp{client: c} + c.Volumes = &VolumeServiceOp{client: c} + c.debug = os.Getenv(debugEnvVar) != "" + + return c, nil +} + +func checkResponse(r *http.Response) error { + + if s := r.StatusCode; s >= 200 && s <= 299 { + // response is good, return + return nil + } + + errorResponse := &ErrorResponse{Response: r} + data, err := ioutil.ReadAll(r.Body) + // if the response has a body, populate the message in errorResponse + if err != nil { + return err + } + + ct := r.Header.Get("Content-Type") + if !strings.HasPrefix(ct, expectedAPIContentTypePrefix) { + errorResponse.SingleError = fmt.Sprintf("Unexpected Content-Type %s with status %s", ct, r.Status) + return errorResponse + } + + if len(data) > 0 { + err = json.Unmarshal(data, errorResponse) + if err != nil { + return err + } + } + + return errorResponse +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/payment_methods.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/payment_methods.go new file mode 100644 index 00000000..8f9840f0 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/payment_methods.go @@ -0,0 +1,71 @@ +package packngo + +// API documentation https://metal.equinix.com/developers/api/paymentmethods/ +const paymentMethodBasePath = "/payment-methods" + +// ProjectService interface defines available project methods +type PaymentMethodService interface { + List() ([]PaymentMethod, *Response, error) + Get(string) (*PaymentMethod, *Response, error) + Create(*PaymentMethodCreateRequest) (*PaymentMethod, *Response, error) + Update(string, *PaymentMethodUpdateRequest) (*PaymentMethod, *Response, error) + Delete(string) (*Response, error) +} + +type paymentMethodsRoot struct { + PaymentMethods []PaymentMethod `json:"payment_methods"` +} + +// PaymentMethod represents an Equinix Metal payment method of an organization +type PaymentMethod struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Created string `json:"created_at,omitempty"` + Updated string `json:"updated_at,omitempty"` + Nonce string `json:"nonce,omitempty"` + Default bool `json:"default,omitempty"` + Organization Organization `json:"organization,omitempty"` + Projects []Project `json:"projects,omitempty"` + Type string `json:"type,omitempty"` + CardholderName string `json:"cardholder_name,omitempty"` + ExpMonth string `json:"expiration_month,omitempty"` + ExpYear string `json:"expiration_year,omitempty"` + Last4 string `json:"last_4,omitempty"` + BillingAddress BillingAddress `json:"billing_address,omitempty"` + URL string `json:"href,omitempty"` +} + +func (pm PaymentMethod) String() string { + return Stringify(pm) +} + +// PaymentMethodCreateRequest type used to create an Equinix Metal payment method of an organization +type PaymentMethodCreateRequest struct { + Name string `json:"name"` + Nonce string `json:"nonce"` + CardholderName string `json:"cardholder_name,omitempty"` + ExpMonth string `json:"expiration_month,omitempty"` + ExpYear string `json:"expiration_year,omitempty"` + BillingAddress string `json:"billing_address,omitempty"` +} + +func (pm PaymentMethodCreateRequest) String() string { + return Stringify(pm) +} + +// PaymentMethodUpdateRequest type used to update an Equinix Metal payment method of an organization +type PaymentMethodUpdateRequest struct { + Name *string `json:"name,omitempty"` + CardholderName *string `json:"cardholder_name,omitempty"` + ExpMonth *string `json:"expiration_month,omitempty"` + ExpYear *string `json:"expiration_year,omitempty"` + BillingAddress *string `json:"billing_address,omitempty"` +} + +func (pm PaymentMethodUpdateRequest) String() string { + return Stringify(pm) +} + +// PaymentMethodServiceOp implements PaymentMethodService +type PaymentMethodServiceOp struct { +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/plans.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/plans.go new file mode 100644 index 00000000..3bd5cd38 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/plans.go @@ -0,0 +1,145 @@ +package packngo + +import "path" + +const planBasePath = "/plans" + +// PlanService interface defines available plan methods +type PlanService interface { + List(*ListOptions) ([]Plan, *Response, error) + ProjectList(string, *ListOptions) ([]Plan, *Response, error) + OrganizationList(string, *ListOptions) ([]Plan, *Response, error) +} + +type planRoot struct { + Plans []Plan `json:"plans"` +} + +// Plan represents an Equinix Metal service plan +type Plan struct { + ID string `json:"id"` + Slug string `json:"slug,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Line string `json:"line,omitempty"` + Legacy bool `json:"legacy,omitempty"` + Specs *Specs `json:"specs,omitempty"` + Pricing *Pricing `json:"pricing,omitempty"` + DeploymentTypes []string `json:"deployment_types"` + Class string `json:"class"` + AvailableIn []Facility `json:"available_in"` + AvailableInMetros []Metro `json:"available_in_metros"` + + Href string `json:"href,omitempty"` +} + +func (p Plan) String() string { + return Stringify(p) +} + +// Specs - the server specs for a plan +type Specs struct { + Cpus []*Cpus `json:"cpus,omitempty"` + Memory *Memory `json:"memory,omitempty"` + Drives []*Drives `json:"drives,omitempty"` + Nics []*Nics `json:"nics,omitempty"` + Features *Features `json:"features,omitempty"` +} + +func (s Specs) String() string { + return Stringify(s) +} + +// Cpus - the CPU config details for specs on a plan +type Cpus struct { + Count int `json:"count,omitempty"` + Type string `json:"type,omitempty"` +} + +func (c Cpus) String() string { + return Stringify(c) +} + +// Memory - the RAM config details for specs on a plan +type Memory struct { + Total string `json:"total,omitempty"` +} + +func (m Memory) String() string { + return Stringify(m) +} + +// Drives - the storage config details for specs on a plan +type Drives struct { + Count int `json:"count,omitempty"` + Size string `json:"size,omitempty"` + Type string `json:"type,omitempty"` +} + +func (d Drives) String() string { + return Stringify(d) +} + +// Nics - the network hardware details for specs on a plan +type Nics struct { + Count int `json:"count,omitempty"` + Type string `json:"type,omitempty"` +} + +func (n Nics) String() string { + return Stringify(n) +} + +// Features - other features in the specs for a plan +type Features struct { + Raid bool `json:"raid,omitempty"` + Txt bool `json:"txt,omitempty"` +} + +func (f Features) String() string { + return Stringify(f) +} + +// Pricing - the pricing options on a plan +type Pricing struct { + Hour float32 `json:"hour,omitempty"` + Month float32 `json:"month,omitempty"` +} + +func (p Pricing) String() string { + return Stringify(p) +} + +// PlanServiceOp implements PlanService +type PlanServiceOp struct { + client *Client +} + +func planList(c *Client, apiPath string, opts *ListOptions) ([]Plan, *Response, error) { + root := new(planRoot) + apiPathQuery := opts.WithQuery(apiPath) + + resp, err := c.DoRequest("GET", apiPathQuery, nil, root) + if err != nil { + return nil, resp, err + } + + return root.Plans, resp, err + +} + +// List method returns all available plans +func (s *PlanServiceOp) List(opts *ListOptions) ([]Plan, *Response, error) { + return planList(s.client, planBasePath, opts) + +} + +// ProjectList method returns plans available in a project +func (s *PlanServiceOp) ProjectList(projectID string, opts *ListOptions) ([]Plan, *Response, error) { + return planList(s.client, path.Join(projectBasePath, projectID, planBasePath), opts) +} + +// OrganizationList method returns plans available in an organization +func (s *PlanServiceOp) OrganizationList(organizationID string, opts *ListOptions) ([]Plan, *Response, error) { + return planList(s.client, path.Join(organizationBasePath, organizationID, planBasePath), opts) +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/ports.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/ports.go new file mode 100644 index 00000000..d820e722 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/ports.go @@ -0,0 +1,208 @@ +package packngo + +import ( + "path" +) + +// PortService handles operations on a port +type PortService interface { + Assign(string, string) (*Port, *Response, error) + Unassign(string, string) (*Port, *Response, error) + AssignNative(string, string) (*Port, *Response, error) + UnassignNative(string) (*Port, *Response, error) + Bond(string, bool) (*Port, *Response, error) + Disbond(string, bool) (*Port, *Response, error) + ConvertToLayerTwo(string) (*Port, *Response, error) + ConvertToLayerThree(string, []AddressRequest) (*Port, *Response, error) + Get(string, *GetOptions) (*Port, *Response, error) +} + +type PortServiceOp struct { + client requestDoer +} + +var _ PortService = (*PortServiceOp)(nil) + +type PortData struct { + // MAC address is set for NetworkPort ports + MAC string `json:"mac,omitempty"` + + // Bonded is true for NetworkPort ports in a bond and NetworkBondPort ports + // that are active + Bonded bool `json:"bonded"` +} + +type BondData struct { + // ID is the Port.ID of the bonding port + ID string `json:"id"` + + // Name of the port interface for the bond ("bond0") + Name string `json:"name"` +} + +// Port is a hardware port associated with a reserved or instantiated hardware +// device. +type Port struct { + // ID of the Port + ID string `json:"id"` + + // Type is either "NetworkBondPort" for bond ports or "NetworkPort" for + // bondable ethernet ports + Type string `json:"type"` + + // Name of the interface for this port (such as "bond0" or "eth0") + Name string `json:"name"` + + // Data about the port + Data PortData `json:"data,omitempty"` + + // Indicates whether or not the bond can be broken on the port (when applicable). + DisbondOperationSupported bool `json:"disbond_operation_supported,omitempty"` + + // NetworkType is either of layer2-bonded, layer2-individual, layer3, + // hybrid, hybrid-bonded + NetworkType string `json:"network_type,omitempty"` + + // NativeVirtualNetwork is the Native VLAN attached to the port + // + NativeVirtualNetwork *VirtualNetwork `json:"native_virtual_network,omitempty"` + + // VLANs attached to the port + AttachedVirtualNetworks []VirtualNetwork `json:"virtual_networks,omitempty"` + + // Bond details for ports with a NetworkPort type + Bond *BondData `json:"bond,omitempty"` +} + +type AddressRequest struct { + AddressFamily int `json:"address_family"` + Public bool `json:"public"` +} + +type BackToL3Request struct { + RequestIPs []AddressRequest `json:"request_ips"` +} + +type PortAssignRequest struct { + // PortID of the Port + // + // Deprecated: this is redundant to the portID parameter in request + // functions. This is kept for use by deprecated DevicePortServiceOps + // methods. + PortID string `json:"id,omitempty"` + + VirtualNetworkID string `json:"vnid"` +} + +type BondRequest struct { + BulkEnable bool `json:"bulk_enable"` +} + +type DisbondRequest struct { + BulkDisable bool `json:"bulk_disable"` +} + +// Assign adds a VLAN to a port +func (i *PortServiceOp) Assign(portID, vlanID string) (*Port, *Response, error) { + apiPath := path.Join(portBasePath, portID, "assign") + par := &PortAssignRequest{VirtualNetworkID: vlanID} + + return i.portAction(apiPath, par) +} + +// AssignNative assigns a virtual network to the port as a "native VLAN" +func (i *PortServiceOp) AssignNative(portID, vlanID string) (*Port, *Response, error) { + apiPath := path.Join(portBasePath, portID, "native-vlan") + par := &PortAssignRequest{VirtualNetworkID: vlanID} + return i.portAction(apiPath, par) +} + +// UnassignNative removes native VLAN from the supplied port +func (i *PortServiceOp) UnassignNative(portID string) (*Port, *Response, error) { + apiPath := path.Join(portBasePath, portID, "native-vlan") + port := new(Port) + + resp, err := i.client.DoRequest("DELETE", apiPath, nil, port) + if err != nil { + return nil, resp, err + } + + return port, resp, err +} + +// Unassign removes a VLAN from the port +func (i *PortServiceOp) Unassign(portID, vlanID string) (*Port, *Response, error) { + apiPath := path.Join(portBasePath, portID, "unassign") + par := &PortAssignRequest{VirtualNetworkID: vlanID} + + return i.portAction(apiPath, par) +} + +// Bond enables bonding for one or all ports +func (i *PortServiceOp) Bond(portID string, bulkEnable bool) (*Port, *Response, error) { + br := &BondRequest{BulkEnable: bulkEnable} + apiPath := path.Join(portBasePath, portID, "bond") + return i.portAction(apiPath, br) +} + +// Disbond disables bonding for one or all ports +func (i *PortServiceOp) Disbond(portID string, bulkEnable bool) (*Port, *Response, error) { + dr := &DisbondRequest{BulkDisable: bulkEnable} + apiPath := path.Join(portBasePath, portID, "disbond") + return i.portAction(apiPath, dr) +} + +func (i *PortServiceOp) portAction(apiPath string, req interface{}) (*Port, *Response, error) { + port := new(Port) + + resp, err := i.client.DoRequest("POST", apiPath, req, port) + if err != nil { + return nil, resp, err + } + + return port, resp, err +} + +// ConvertToLayerTwo converts a bond port to Layer 2. IP assignments of the port will be removed. +// +// portID is the UUID of a Bonding Port +func (i *PortServiceOp) ConvertToLayerTwo(portID string) (*Port, *Response, error) { + apiPath := path.Join(portBasePath, portID, "convert", "layer-2") + port := new(Port) + + resp, err := i.client.DoRequest("POST", apiPath, nil, port) + if err != nil { + return nil, resp, err + } + + return port, resp, err +} + +// ConvertToLayerThree converts a bond port to Layer 3. VLANs must first be unassigned. +func (i *PortServiceOp) ConvertToLayerThree(portID string, ips []AddressRequest) (*Port, *Response, error) { + apiPath := path.Join(portBasePath, portID, "convert", "layer-3") + port := new(Port) + + req := BackToL3Request{ + RequestIPs: ips, + } + + resp, err := i.client.DoRequest("POST", apiPath, &req, port) + if err != nil { + return nil, resp, err + } + + return port, resp, err +} + +// Get returns a port by id +func (s *PortServiceOp) Get(portID string, opts *GetOptions) (*Port, *Response, error) { + endpointPath := path.Join(portBasePath, portID) + apiPathQuery := opts.WithQuery(endpointPath) + port := new(Port) + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, port) + if err != nil { + return nil, resp, err + } + return port, resp, err +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/projects.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/projects.go new file mode 100644 index 00000000..24f56c6e --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/projects.go @@ -0,0 +1,182 @@ +package packngo + +import ( + "path" +) + +const projectBasePath = "/projects" + +// ProjectService interface defines available project methods +type ProjectService interface { + List(listOpt *ListOptions) ([]Project, *Response, error) + Get(string, *GetOptions) (*Project, *Response, error) + Create(*ProjectCreateRequest) (*Project, *Response, error) + Update(string, *ProjectUpdateRequest) (*Project, *Response, error) + Delete(string) (*Response, error) + ListBGPSessions(projectID string, listOpt *ListOptions) ([]BGPSession, *Response, error) + ListEvents(string, *ListOptions) ([]Event, *Response, error) + ListSSHKeys(projectID string, searchOpt *SearchOptions) ([]SSHKey, *Response, error) +} + +type projectsRoot struct { + Projects []Project `json:"projects"` + Meta meta `json:"meta"` +} + +// Project represents an Equinix Metal project +type Project struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Organization Organization `json:"organization,omitempty"` + Created string `json:"created_at,omitempty"` + Updated string `json:"updated_at,omitempty"` + Users []User `json:"members,omitempty"` + Devices []Device `json:"devices,omitempty"` + SSHKeys []SSHKey `json:"ssh_keys,omitempty"` + URL string `json:"href,omitempty"` + PaymentMethod PaymentMethod `json:"payment_method,omitempty"` + BackendTransfer bool `json:"backend_transfer_enabled"` +} + +func (p Project) String() string { + return Stringify(p) +} + +// ProjectCreateRequest type used to create an Equinix Metal project +type ProjectCreateRequest struct { + Name string `json:"name"` + PaymentMethodID string `json:"payment_method_id,omitempty"` + OrganizationID string `json:"organization_id,omitempty"` +} + +func (p ProjectCreateRequest) String() string { + return Stringify(p) +} + +// ProjectUpdateRequest type used to update an Equinix Metal project +type ProjectUpdateRequest struct { + Name *string `json:"name,omitempty"` + PaymentMethodID *string `json:"payment_method_id,omitempty"` + BackendTransfer *bool `json:"backend_transfer_enabled,omitempty"` +} + +func (p ProjectUpdateRequest) String() string { + return Stringify(p) +} + +// ProjectServiceOp implements ProjectService +type ProjectServiceOp struct { + client requestDoer +} + +// List returns the user's projects +func (s *ProjectServiceOp) List(opts *ListOptions) (projects []Project, resp *Response, err error) { + apiPathQuery := opts.WithQuery(projectBasePath) + + for { + subset := new(projectsRoot) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + projects = append(projects, subset.Projects...) + + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + + return + } +} + +// Get returns a project by id +func (s *ProjectServiceOp) Get(projectID string, opts *GetOptions) (*Project, *Response, error) { + endpointPath := path.Join(projectBasePath, projectID) + apiPathQuery := opts.WithQuery(endpointPath) + project := new(Project) + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, project) + if err != nil { + return nil, resp, err + } + return project, resp, err +} + +// Create creates a new project +func (s *ProjectServiceOp) Create(createRequest *ProjectCreateRequest) (*Project, *Response, error) { + project := new(Project) + + resp, err := s.client.DoRequest("POST", projectBasePath, createRequest, project) + if err != nil { + return nil, resp, err + } + + return project, resp, err +} + +// Update updates a project +func (s *ProjectServiceOp) Update(id string, updateRequest *ProjectUpdateRequest) (*Project, *Response, error) { + apiPath := path.Join(projectBasePath, id) + project := new(Project) + + resp, err := s.client.DoRequest("PATCH", apiPath, updateRequest, project) + if err != nil { + return nil, resp, err + } + + return project, resp, err +} + +// Delete deletes a project +func (s *ProjectServiceOp) Delete(projectID string) (*Response, error) { + apiPath := path.Join(projectBasePath, projectID) + + return s.client.DoRequest("DELETE", apiPath, nil, nil) +} + +// ListBGPSessions returns all BGP Sessions associated with the project +func (s *ProjectServiceOp) ListBGPSessions(projectID string, opts *ListOptions) (bgpSessions []BGPSession, resp *Response, err error) { + endpointPath := path.Join(projectBasePath, projectID, bgpSessionBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + + for { + subset := new(bgpSessionsRoot) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + bgpSessions = append(bgpSessions, subset.Sessions...) + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + return + } +} + +// ListSSHKeys returns all SSH Keys associated with the project +func (s *ProjectServiceOp) ListSSHKeys(projectID string, opts *SearchOptions) (sshKeys []SSHKey, resp *Response, err error) { + + endpointPath := path.Join(projectBasePath, projectID, sshKeyBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + + subset := new(sshKeyRoot) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + sshKeys = append(sshKeys, subset.SSHKeys...) + + return +} + +// ListEvents returns list of project events +func (s *ProjectServiceOp) ListEvents(projectID string, listOpt *ListOptions) ([]Event, *Response, error) { + apiPath := path.Join(projectBasePath, projectID, eventBasePath) + + return listEvents(s.client, apiPath, listOpt) +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/rate.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/rate.go new file mode 100644 index 00000000..965967d4 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/rate.go @@ -0,0 +1,12 @@ +package packngo + +// Rate provides the API request rate limit details +type Rate struct { + RequestLimit int `json:"request_limit"` + RequestsRemaining int `json:"requests_remaining"` + Reset Timestamp `json:"rate_reset"` +} + +func (r Rate) String() string { + return Stringify(r) +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/spotmarket.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/spotmarket.go new file mode 100644 index 00000000..3c1ee943 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/spotmarket.go @@ -0,0 +1,92 @@ +package packngo + +import "path" + +const ( + spotMarketBasePath = "/market/spot/prices" + + spotMarketMetrosPath = "metros" +) + +// SpotMarketService expooses Spot Market methods +type SpotMarketService interface { + // Prices gets current spot market prices by facility. + // + // Deprecated: Use PricesByFacility + Prices() (PriceMap, *Response, error) + + // PricesByFacility gets current spot market prices by facility. The map is + // indexed by facility code and then plan name. + PricesByFacility() (PriceMap, *Response, error) + + // PricesByMetro gets current spot market prices by metro. The map is + // indexed by metro code and then plan name. + PricesByMetro() (PriceMap, *Response, error) +} + +// SpotMarketServiceOp implements SpotMarketService +type SpotMarketServiceOp struct { + client *Client +} + +// PriceMap is a map of [location][plan]-> float Price +type PriceMap map[string]map[string]float64 + +// Prices gets current spot market prices by facility. +// +// Deprecated: Use PricesByFacility which this function thinly wraps. +func (s *SpotMarketServiceOp) Prices() (PriceMap, *Response, error) { + return s.PricesByFacility() +} + +// PricesByFacility gets current spot market prices by facility. The map is +// indexed by facility code and then plan name. +// +// price := client.SpotMarket.PricesByFacility()["ny5"]["c3.medium.x86"] +func (s *SpotMarketServiceOp) PricesByFacility() (PriceMap, *Response, error) { + root := new(struct { + SMPs map[string]map[string]struct { + Price float64 `json:"price"` + } `json:"spot_market_prices"` + }) + + resp, err := s.client.DoRequest("GET", spotMarketBasePath, nil, root) + if err != nil { + return nil, resp, err + } + + prices := make(PriceMap) + for facility, planMap := range root.SMPs { + prices[facility] = map[string]float64{} + for plan, v := range planMap { + prices[facility][plan] = v.Price + } + } + return prices, resp, err +} + +// PricesByMetro gets current spot market prices by metro. The map is +// indexed by metro code and then plan name. +// +// price := client.SpotMarket.PricesByMetro()["sv"]["c3.medium.x86"] +func (s *SpotMarketServiceOp) PricesByMetro() (PriceMap, *Response, error) { + root := new(struct { + SMPs map[string]map[string]struct { + Price float64 `json:"price"` + } `json:"spot_market_prices"` + }) + + resp, err := s.client.DoRequest("GET", path.Join(spotMarketBasePath, spotMarketMetrosPath), nil, root) + if err != nil { + return nil, resp, err + } + + prices := make(PriceMap) + for metro, planMap := range root.SMPs { + prices[metro] = map[string]float64{} + for plan, v := range planMap { + prices[metro][plan] = v.Price + } + } + return prices, resp, err +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/spotmarketrequest.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/spotmarketrequest.go new file mode 100644 index 00000000..1ef1f352 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/spotmarketrequest.go @@ -0,0 +1,120 @@ +package packngo + +import ( + "math" + "path" +) + +const spotMarketRequestBasePath = "/spot-market-requests" + +type SpotMarketRequestService interface { + List(string, *ListOptions) ([]SpotMarketRequest, *Response, error) + Create(*SpotMarketRequestCreateRequest, string) (*SpotMarketRequest, *Response, error) + Delete(string, bool) (*Response, error) + Get(string, *GetOptions) (*SpotMarketRequest, *Response, error) +} + +type SpotMarketRequestCreateRequest struct { + DevicesMax int `json:"devices_max"` + DevicesMin int `json:"devices_min"` + EndAt *Timestamp `json:"end_at,omitempty"` + FacilityIDs []string `json:"facilities,omitempty"` + Metro string `json:"metro,omitempty"` + MaxBidPrice float64 `json:"max_bid_price"` + + Parameters SpotMarketRequestInstanceParameters `json:"instance_parameters"` +} + +type SpotMarketRequest struct { + SpotMarketRequestCreateRequest + ID string `json:"id"` + Devices []Device `json:"devices"` + Facilities []Facility `json:"facilities,omitempty"` + Metro *Metro `json:"metro,omitempty"` + Project Project `json:"project"` + Href string `json:"href"` + Plan Plan `json:"plan"` +} + +type SpotMarketRequestInstanceParameters struct { + AlwaysPXE bool `json:"always_pxe,omitempty"` + IPXEScriptURL string `json:"ipxe_script_url,omitempty"` + BillingCycle string `json:"billing_cycle"` + CustomData string `json:"customdata,omitempty"` + Description string `json:"description,omitempty"` + Features []string `json:"features,omitempty"` + Hostname string `json:"hostname,omitempty"` + Hostnames []string `json:"hostnames,omitempty"` + Locked bool `json:"locked,omitempty"` + OperatingSystem string `json:"operating_system"` + Plan string `json:"plan"` + ProjectSSHKeys []string `json:"project_ssh_keys,omitempty"` + Tags []string `json:"tags"` + TerminationTime *Timestamp `json:"termination_time,omitempty"` + UserSSHKeys []string `json:"user_ssh_keys,omitempty"` + UserData string `json:"userdata"` +} + +type SpotMarketRequestServiceOp struct { + client *Client +} + +func roundPlus(f float64, places int) float64 { + shift := math.Pow(10, float64(places)) + return math.Floor(f*shift+.5) / shift +} + +func (s *SpotMarketRequestServiceOp) Create(cr *SpotMarketRequestCreateRequest, pID string) (*SpotMarketRequest, *Response, error) { + opts := (&GetOptions{}).Including("devices", "project", "plan") + endpointPath := path.Join(projectBasePath, pID, spotMarketRequestBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + + cr.MaxBidPrice = roundPlus(cr.MaxBidPrice, 2) + smr := new(SpotMarketRequest) + + resp, err := s.client.DoRequest("POST", apiPathQuery, cr, smr) + if err != nil { + return nil, resp, err + } + + return smr, resp, err +} + +func (s *SpotMarketRequestServiceOp) List(pID string, opts *ListOptions) ([]SpotMarketRequest, *Response, error) { + type smrRoot struct { + SMRs []SpotMarketRequest `json:"spot_market_requests"` + } + + endpointPath := path.Join(projectBasePath, pID, spotMarketRequestBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + output := new(smrRoot) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, output) + if err != nil { + return nil, nil, err + } + + return output.SMRs, resp, nil +} + +func (s *SpotMarketRequestServiceOp) Get(id string, opts *GetOptions) (*SpotMarketRequest, *Response, error) { + endpointPath := path.Join(spotMarketRequestBasePath, id) + apiPathQuery := opts.WithQuery(endpointPath) + smr := new(SpotMarketRequest) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, &smr) + if err != nil { + return nil, resp, err + } + + return smr, resp, err +} + +func (s *SpotMarketRequestServiceOp) Delete(id string, forceDelete bool) (*Response, error) { + apiPath := path.Join(spotMarketRequestBasePath, id) + var params *map[string]bool + if forceDelete { + params = &map[string]bool{"force_termination": true} + } + return s.client.DoRequest("DELETE", apiPath, params, nil) +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/sshkeys.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/sshkeys.go new file mode 100644 index 00000000..3f4e3b74 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/sshkeys.go @@ -0,0 +1,143 @@ +package packngo + +import ( + "fmt" + "path" +) + +const ( + sshKeyBasePath = "/ssh-keys" +) + +// SSHKeyService interface defines available device methods +type SSHKeyService interface { + List() ([]SSHKey, *Response, error) + ProjectList(string) ([]SSHKey, *Response, error) + Get(string, *GetOptions) (*SSHKey, *Response, error) + Create(*SSHKeyCreateRequest) (*SSHKey, *Response, error) + Update(string, *SSHKeyUpdateRequest) (*SSHKey, *Response, error) + Delete(string) (*Response, error) +} + +type sshKeyRoot struct { + SSHKeys []SSHKey `json:"ssh_keys"` +} + +// SSHKey represents a user's ssh key +type SSHKey struct { + ID string `json:"id"` + Label string `json:"label"` + Key string `json:"key"` + FingerPrint string `json:"fingerprint"` + Created string `json:"created_at"` + Updated string `json:"updated_at"` + Owner Href + URL string `json:"href,omitempty"` +} + +func (s SSHKey) String() string { + return Stringify(s) +} + +// SSHKeyCreateRequest type used to create an ssh key +type SSHKeyCreateRequest struct { + Label string `json:"label"` + Key string `json:"key"` + ProjectID string `json:"-"` +} + +func (s SSHKeyCreateRequest) String() string { + return Stringify(s) +} + +// SSHKeyUpdateRequest type used to update an ssh key +type SSHKeyUpdateRequest struct { + Label *string `json:"label,omitempty"` + Key *string `json:"key,omitempty"` +} + +func (s SSHKeyUpdateRequest) String() string { + return Stringify(s) +} + +// SSHKeyServiceOp implements SSHKeyService +type SSHKeyServiceOp struct { + client *Client +} + +func (s *SSHKeyServiceOp) list(url string) ([]SSHKey, *Response, error) { + root := new(sshKeyRoot) + + resp, err := s.client.DoRequest("GET", url, nil, root) + if err != nil { + return nil, resp, err + } + + return root.SSHKeys, resp, err +} + +// ProjectList lists ssh keys of a project +// Deprecated: Use ProjectServiceOp.ListSSHKeys +func (s *SSHKeyServiceOp) ProjectList(projectID string) ([]SSHKey, *Response, error) { + return s.list(path.Join(projectBasePath, projectID, sshKeyBasePath)) + +} + +// List returns a user's ssh keys +func (s *SSHKeyServiceOp) List() ([]SSHKey, *Response, error) { + return s.list(sshKeyBasePath) +} + +// Get returns an ssh key by id +func (s *SSHKeyServiceOp) Get(sshKeyID string, opts *GetOptions) (*SSHKey, *Response, error) { + endpointPath := path.Join(sshKeyBasePath, sshKeyID) + apiPathQuery := opts.WithQuery(endpointPath) + sshKey := new(SSHKey) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, sshKey) + if err != nil { + return nil, resp, err + } + + return sshKey, resp, err +} + +// Create creates a new ssh key +func (s *SSHKeyServiceOp) Create(createRequest *SSHKeyCreateRequest) (*SSHKey, *Response, error) { + urlPath := sshKeyBasePath + if createRequest.ProjectID != "" { + urlPath = path.Join(projectBasePath, createRequest.ProjectID, sshKeyBasePath) + } + sshKey := new(SSHKey) + + resp, err := s.client.DoRequest("POST", urlPath, createRequest, sshKey) + if err != nil { + return nil, resp, err + } + + return sshKey, resp, err +} + +// Update updates an ssh key +func (s *SSHKeyServiceOp) Update(id string, updateRequest *SSHKeyUpdateRequest) (*SSHKey, *Response, error) { + if updateRequest.Label == nil && updateRequest.Key == nil { + return nil, nil, fmt.Errorf("You must set either Label or Key string for SSH Key update") + } + apiPath := path.Join(sshKeyBasePath, id) + + sshKey := new(SSHKey) + + resp, err := s.client.DoRequest("PATCH", apiPath, updateRequest, sshKey) + if err != nil { + return nil, resp, err + } + + return sshKey, resp, err +} + +// Delete deletes an ssh key +func (s *SSHKeyServiceOp) Delete(sshKeyID string) (*Response, error) { + apiPath := path.Join(sshKeyBasePath, sshKeyID) + + return s.client.DoRequest("DELETE", apiPath, nil, nil) +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/timestamp.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/timestamp.go new file mode 100644 index 00000000..ee3d780d --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/timestamp.go @@ -0,0 +1,38 @@ +package packngo + +import ( + "strconv" + "strings" + "time" +) + +// Timestamp represents a time that can be unmarshalled from a JSON string +// formatted as either an RFC3339 or Unix timestamp. All +// exported methods of time.Time can be called on Timestamp. +type Timestamp struct { + time.Time +} + +func (t Timestamp) String() string { + return t.Time.String() +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +// Time is expected in RFC3339 or Unix format. +func (t *Timestamp) UnmarshalJSON(data []byte) (err error) { + str := string(data) + i, err := strconv.ParseInt(str, 10, 64) + if err == nil { + t.Time = time.Unix(i, 0).UTC() + } else { + if t.Time, err = time.ParseInLocation(time.RFC3339, strings.Trim(str, `"`), time.UTC); err != nil { + return err + } + } + return +} + +// Equal reports whether t and u are equal based on time.Equal +func (t Timestamp) Equal(u Timestamp) bool { + return t.Time.Equal(u.Time) +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/two_factor_auth.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/two_factor_auth.go new file mode 100644 index 00000000..5064b09f --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/two_factor_auth.go @@ -0,0 +1,56 @@ +package packngo + +const twoFactorAuthAppPath = "/user/otp/app" +const twoFactorAuthSmsPath = "/user/otp/sms" + +// TwoFactorAuthService interface defines available two factor authentication functions +type TwoFactorAuthService interface { + EnableApp(string) (*Response, error) + DisableApp(string) (*Response, error) + EnableSms(string) (*Response, error) + DisableSms(string) (*Response, error) + ReceiveSms() (*Response, error) + SeedApp() (string, *Response, error) +} + +// TwoFactorAuthServiceOp implements TwoFactorAuthService +type TwoFactorAuthServiceOp struct { + client *Client +} + +// EnableApp function enables two factor auth using authenticatior app +func (s *TwoFactorAuthServiceOp) EnableApp(token string) (resp *Response, err error) { + headers := map[string]string{"x-otp-token": token} + return s.client.DoRequestWithHeader("POST", headers, twoFactorAuthAppPath, nil, nil) +} + +// EnableSms function enables two factor auth using sms +func (s *TwoFactorAuthServiceOp) EnableSms(token string) (resp *Response, err error) { + headers := map[string]string{"x-otp-token": token} + return s.client.DoRequestWithHeader("POST", headers, twoFactorAuthSmsPath, nil, nil) +} + +// ReceiveSms orders the auth service to issue an SMS token +func (s *TwoFactorAuthServiceOp) ReceiveSms() (resp *Response, err error) { + return s.client.DoRequest("POST", twoFactorAuthSmsPath+"/receive", nil, nil) +} + +// DisableApp function disables two factor auth using +func (s *TwoFactorAuthServiceOp) DisableApp(token string) (resp *Response, err error) { + headers := map[string]string{"x-otp-token": token} + return s.client.DoRequestWithHeader("DELETE", headers, twoFactorAuthAppPath, nil, nil) +} + +// DisableSms function disables two factor auth using +func (s *TwoFactorAuthServiceOp) DisableSms(token string) (resp *Response, err error) { + headers := map[string]string{"x-otp-token": token} + return s.client.DoRequestWithHeader("DELETE", headers, twoFactorAuthSmsPath, nil, nil) +} + +// SeedApp orders the auth service to issue a token via google authenticator +func (s *TwoFactorAuthServiceOp) SeedApp() (otpURI string, resp *Response, err error) { + ret := &map[string]string{} + resp, err = s.client.DoRequest("POST", twoFactorAuthAppPath+"/receive", nil, ret) + + return (*ret)["otp_uri"], resp, err +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/user.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/user.go new file mode 100644 index 00000000..6132e23c --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/user.go @@ -0,0 +1,101 @@ +package packngo + +import "path" + +const usersBasePath = "/users" +const userBasePath = "/user" + +// UserService interface defines available user methods +type UserService interface { + List(*ListOptions) ([]User, *Response, error) + Get(string, *GetOptions) (*User, *Response, error) + Current() (*User, *Response, error) +} + +type usersRoot struct { + Users []User `json:"users"` + Meta meta `json:"meta"` +} + +// User represents an Equinix Metal user +type User struct { + ID string `json:"id"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + FullName string `json:"full_name,omitempty"` + Email string `json:"email,omitempty"` + TwoFactor string `json:"two_factor_auth,omitempty"` + DefaultOrganizationID string `json:"default_organization_id,omitempty"` + AvatarURL string `json:"avatar_url,omitempty"` + Facebook string `json:"twitter,omitempty"` + Twitter string `json:"facebook,omitempty"` + LinkedIn string `json:"linkedin,omitempty"` + Created string `json:"created_at,omitempty"` + Updated string `json:"updated_at,omitempty"` + TimeZone string `json:"timezone,omitempty"` + Emails []Email `json:"emails,omitempty"` + PhoneNumber string `json:"phone_number,omitempty"` + URL string `json:"href,omitempty"` + + // VPN indicates if Doorman VPN service is enabled for the user. + // + // Deprecated: As of March 31, 2021, Doorman service is no longer + // available. See https://metal.equinix.com/developers/docs/accounts/doorman/ + // for more details. + VPN bool `json:"vpn"` +} + +func (u User) String() string { + return Stringify(u) +} + +// UserServiceOp implements UserService +type UserServiceOp struct { + client *Client +} + +// Get method gets a user by userID +func (s *UserServiceOp) List(opts *ListOptions) (users []User, resp *Response, err error) { + apiPathQuery := opts.WithQuery(usersBasePath) + + for { + subset := new(usersRoot) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + users = append(users, subset.Users...) + + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + return + } +} + +// Returns the user object for the currently logged-in user. +func (s *UserServiceOp) Current() (*User, *Response, error) { + user := new(User) + + resp, err := s.client.DoRequest("GET", userBasePath, nil, user) + if err != nil { + return nil, resp, err + } + + return user, resp, err +} + +func (s *UserServiceOp) Get(userID string, opts *GetOptions) (*User, *Response, error) { + endpointPath := path.Join(usersBasePath, userID) + apiPathQuery := opts.WithQuery(endpointPath) + user := new(User) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, user) + if err != nil { + return nil, resp, err + } + + return user, resp, err +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/utils.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/utils.go new file mode 100644 index 00000000..42124ba4 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/utils.go @@ -0,0 +1,156 @@ +package packngo + +import ( + "bytes" + "fmt" + "io" + "reflect" +) + +var ( + timestampType = reflect.TypeOf(Timestamp{}) + + // Facilities DEPRECATED Use Facilities.List + Facilities = []string{ + "yyz1", "nrt1", "atl1", "mrs1", "hkg1", "ams1", + "ewr1", "sin1", "dfw1", "lax1", "syd1", "sjc1", + "ord1", "iad1", "fra1", "sea1", "dfw2"} + + // FacilityFeatures DEPRECATED Use Facilities.List + FacilityFeatures = []string{ + "baremetal", "layer_2", "backend_transfer", "storage", "global_ipv4"} + + // UtilizationLevels DEPRECATED + UtilizationLevels = []string{"unavailable", "critical", "limited", "normal"} + + // DevicePlans DEPRECATED Use Plans.List + DevicePlans = []string{"c2.medium.x86", "g2.large.x86", + "m2.xlarge.x86", "x2.xlarge.x86", "baremetal_2a", "baremetal_2a2", + "baremetal_1", "baremetal_3", "baremetal_2", "baremetal_s", + "baremetal_0", "baremetal_1e", + } +) + +// Stringify creates a string representation of the provided message +// DEPRECATED This is used internally and should not be exported by packngo +func Stringify(message interface{}) string { + var buf bytes.Buffer + v := reflect.ValueOf(message) + // TODO(displague) errors here are not reported + _ = stringifyValue(&buf, v) + return buf.String() +} + +// StreamToString converts a reader to a string +// DEPRECATED This is unused and should not be exported by packngo +func StreamToString(stream io.Reader) (string, error) { + buf := new(bytes.Buffer) + if _, err := buf.ReadFrom(stream); err != nil { + return "", err + } + return buf.String(), nil +} + +// contains tells whether a contains x. +func contains(a []string, x string) bool { + for _, n := range a { + if x == n { + return true + } + } + return false +} + +// stringifyValue was graciously cargoculted from the goprotubuf library +func stringifyValue(w io.Writer, val reflect.Value) error { + if val.Kind() == reflect.Ptr && val.IsNil() { + _, err := w.Write([]byte("")) + return err + } + + v := reflect.Indirect(val) + + switch v.Kind() { + case reflect.String: + if _, err := fmt.Fprintf(w, `"%s"`, v); err != nil { + return err + } + case reflect.Slice: + if _, err := w.Write([]byte{'['}); err != nil { + return err + } + for i := 0; i < v.Len(); i++ { + if i > 0 { + if _, err := w.Write([]byte{' '}); err != nil { + return err + } + } + + if err := stringifyValue(w, v.Index(i)); err != nil { + return err + } + } + + if _, err := w.Write([]byte{']'}); err != nil { + return err + } + return nil + case reflect.Struct: + if v.Type().Name() != "" { + if _, err := w.Write([]byte(v.Type().String())); err != nil { + return err + } + } + + // special handling of Timestamp values + if v.Type() == timestampType { + _, err := fmt.Fprintf(w, "{%s}", v.Interface()) + return err + } + + if _, err := w.Write([]byte{'{'}); err != nil { + return err + } + + var sep bool + for i := 0; i < v.NumField(); i++ { + fv := v.Field(i) + if fv.Kind() == reflect.Ptr && fv.IsNil() { + continue + } + if fv.Kind() == reflect.Slice && fv.IsNil() { + continue + } + + if sep { + if _, err := w.Write([]byte(", ")); err != nil { + return err + } + } else { + sep = true + } + + if _, err := w.Write([]byte(v.Type().Field(i).Name)); err != nil { + return err + } + if _, err := w.Write([]byte{':'}); err != nil { + return err + } + + if err := stringifyValue(w, fv); err != nil { + return err + } + } + + if _, err := w.Write([]byte{'}'}); err != nil { + return err + } + default: + if v.CanInterface() { + if _, err := fmt.Fprint(w, v.Interface()); err != nil { + return err + } + } + } + return nil +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/version.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/version.go new file mode 100644 index 00000000..384479c7 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/version.go @@ -0,0 +1,36 @@ +package packngo + +import "runtime/debug" + +var ( + // Version of the packngo package. Version will be updated at runtime. + Version = "(devel)" + + // UserAgent is the default HTTP User-Agent Header value that will be used by NewClient. + // init() will update the version to match the built version of packngo. + UserAgent = "packngo/(devel)" +) + +const packagePath = "github.com/packethost/packngo" + +// init finds packngo in the dependency so the package Version can be properly +// reflected in API UserAgent headers and client introspection +func init() { + bi, ok := debug.ReadBuildInfo() + if !ok { + return + } + for _, d := range bi.Deps { + if d.Path == packagePath { + Version = d.Version + if d.Replace != nil { + v := d.Replace.Version + if v != "" { + Version = v + } + } + UserAgent = "packngo/" + Version + break + } + } +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/virtualcircuits.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/virtualcircuits.go new file mode 100644 index 00000000..4a08ece7 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/virtualcircuits.go @@ -0,0 +1,102 @@ +package packngo + +import "path" + +const ( + virtualCircuitBasePath = "/virtual-circuits" + + // VC is being create but not ready yet + VCStatusPending = "pending" + + // VC is ready with a VLAN + VCStatusActive = "active" + + // VC is ready without a VLAN + VCStatusWaiting = "waiting_on_customer_vlan" + + // VC is being deleted + VCStatusDeleting = "deleting" + + // not sure what the following states mean, or whether they exist + // someone from the API side could check + VCStatusActivating = "activating" + VCStatusDeactivating = "deactivating" + VCStatusActivationFailed = "activation_failed" + VCStatusDeactivationFailed = "dactivation_failed" +) + +type VirtualCircuitService interface { + Create(string, string, string, *VCCreateRequest, *GetOptions) (*VirtualCircuit, *Response, error) + Get(string, *GetOptions) (*VirtualCircuit, *Response, error) + Events(string, *GetOptions) ([]Event, *Response, error) + Delete(string) (*Response, error) + Update(string, *VCUpdateRequest, *GetOptions) (*VirtualCircuit, *Response, error) +} + +type VCUpdateRequest struct { + VirtualNetworkID *string `json:"vnid"` +} + +type VCCreateRequest struct { + VirtualNetworkID string `json:"vnid"` + NniVLAN int `json:"nni_vlan,omitempty"` + Name string `json:"name,omitempty"` +} + +type VirtualCircuitServiceOp struct { + client *Client +} + +type virtualCircuitsRoot struct { + VirtualCircuits []VirtualCircuit `json:"virtual_circuits"` + Meta meta `json:"meta"` +} + +type VirtualCircuit struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Status string `json:"status,omitempty"` + VNID int `json:"vnid,omitempty"` + NniVNID int `json:"nni_vnid,omitempty"` + NniVLAN int `json:"nni_vlan,omitempty"` + Project *Project `json:"project,omitempty"` + Port *ConnectionPort `json:"port,omitempty"` + VirtualNetwork *VirtualNetwork `json:"virtual_network,omitempty"` +} + +func (s *VirtualCircuitServiceOp) do(method, apiPathQuery string, req interface{}) (*VirtualCircuit, *Response, error) { + vc := new(VirtualCircuit) + resp, err := s.client.DoRequest(method, apiPathQuery, req, vc) + if err != nil { + return nil, resp, err + } + return vc, resp, err +} + +func (s *VirtualCircuitServiceOp) Update(vcID string, req *VCUpdateRequest, opts *GetOptions) (*VirtualCircuit, *Response, error) { + endpointPath := path.Join(virtualCircuitBasePath, vcID) + apiPathQuery := opts.WithQuery(endpointPath) + return s.do("PUT", apiPathQuery, req) +} + +func (s *VirtualCircuitServiceOp) Events(id string, opts *GetOptions) ([]Event, *Response, error) { + apiPath := path.Join(virtualCircuitBasePath, id, eventBasePath) + return listEvents(s.client, apiPath, opts) +} + +func (s *VirtualCircuitServiceOp) Get(id string, opts *GetOptions) (*VirtualCircuit, *Response, error) { + endpointPath := path.Join(virtualCircuitBasePath, id) + apiPathQuery := opts.WithQuery(endpointPath) + return s.do("GET", apiPathQuery, nil) +} + +func (s *VirtualCircuitServiceOp) Delete(id string) (*Response, error) { + apiPath := path.Join(virtualCircuitBasePath, id) + return s.client.DoRequest("DELETE", apiPath, nil, nil) +} + +func (s *VirtualCircuitServiceOp) Create(projectID, connID, portID string, request *VCCreateRequest, opts *GetOptions) (*VirtualCircuit, *Response, error) { + endpointPath := path.Join(projectBasePath, projectID, connectionBasePath, connID, portBasePath, portID, virtualCircuitBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + return s.do("POST", apiPathQuery, request) +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/virtualnetworks.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/virtualnetworks.go new file mode 100644 index 00000000..64299b8b --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/virtualnetworks.go @@ -0,0 +1,108 @@ +package packngo + +import ( + "path" +) + +const virtualNetworkBasePath = "/virtual-networks" + +// DevicePortService handles operations on a port which belongs to a particular device +type ProjectVirtualNetworkService interface { + List(projectID string, opts *ListOptions) (*VirtualNetworkListResponse, *Response, error) + Create(*VirtualNetworkCreateRequest) (*VirtualNetwork, *Response, error) + Get(string, *GetOptions) (*VirtualNetwork, *Response, error) + Delete(virtualNetworkID string) (*Response, error) +} + +type VirtualNetwork struct { + ID string `json:"id"` + Description string `json:"description,omitempty"` // TODO: field can be null + VXLAN int `json:"vxlan,omitempty"` + FacilityCode string `json:"facility_code,omitempty"` + MetroCode string `json:"metro_code,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + Href string `json:"href"` + Project *Project `json:"assigned_to,omitempty"` + Facility *Facility `json:"facility,omitempty"` + Metro *Metro `json:"metro,omitempty"` + Instances []*Device `json:"instances,omitempty"` +} + +type ProjectVirtualNetworkServiceOp struct { + client *Client +} + +type VirtualNetworkListResponse struct { + VirtualNetworks []VirtualNetwork `json:"virtual_networks"` +} + +func (i *ProjectVirtualNetworkServiceOp) List(projectID string, opts *ListOptions) (*VirtualNetworkListResponse, *Response, error) { + endpointPath := path.Join(projectBasePath, projectID, virtualNetworkBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + output := new(VirtualNetworkListResponse) + + resp, err := i.client.DoRequest("GET", apiPathQuery, nil, output) + if err != nil { + return nil, nil, err + } + + return output, resp, nil +} + +type VirtualNetworkCreateRequest struct { + // ProjectID of the project where the VLAN will be made available. + ProjectID string `json:"project_id"` + + // Description is a user supplied description of the VLAN. + Description string `json:"description"` + + // TODO: default Description is null when not specified. Permitting *string here would require changing VirtualNetwork.Description to *string too. + + // Facility in which to create the VLAN. Mutually exclusive with Metro. + Facility string `json:"facility,omitempty"` + + // Metro in which to create the VLAN. Mutually exclusive with Facility. + Metro string `json:"metro,omitempty"` + + // VXLAN is the VLAN ID. VXLAN may be specified when Metro is defined. It is remotely incremented otherwise. Must be unique per Metro. + VXLAN int `json:"vxlan,omitempty"` +} + +func (i *ProjectVirtualNetworkServiceOp) Get(vlanID string, opts *GetOptions) (*VirtualNetwork, *Response, error) { + endpointPath := path.Join(virtualNetworkBasePath, vlanID) + apiPathQuery := opts.WithQuery(endpointPath) + vlan := new(VirtualNetwork) + + resp, err := i.client.DoRequest("GET", apiPathQuery, nil, vlan) + if err != nil { + return nil, resp, err + } + + return vlan, resp, err +} + +func (i *ProjectVirtualNetworkServiceOp) Create(input *VirtualNetworkCreateRequest) (*VirtualNetwork, *Response, error) { + // TODO: May need to add timestamp to output from 'post' request + // for the 'created_at' attribute of VirtualNetwork struct since + // API response doesn't include it + apiPath := path.Join(projectBasePath, input.ProjectID, virtualNetworkBasePath) + output := new(VirtualNetwork) + + resp, err := i.client.DoRequest("POST", apiPath, input, output) + if err != nil { + return nil, nil, err + } + + return output, resp, nil +} + +func (i *ProjectVirtualNetworkServiceOp) Delete(virtualNetworkID string) (*Response, error) { + apiPath := path.Join(virtualNetworkBasePath, virtualNetworkID) + + resp, err := i.client.DoRequest("DELETE", apiPath, nil, nil) + if err != nil { + return nil, err + } + + return resp, nil +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/volumes.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/volumes.go new file mode 100644 index 00000000..b6166787 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/volumes.go @@ -0,0 +1,231 @@ +package packngo + +import ( + "path" +) + +const ( + volumeBasePath = "/storage" + attachmentsBasePath = "/attachments" +) + +// VolumeService interface defines available Volume methods +type VolumeService interface { + List(string, *ListOptions) ([]Volume, *Response, error) + Get(string, *GetOptions) (*Volume, *Response, error) + Update(string, *VolumeUpdateRequest) (*Volume, *Response, error) + Delete(string) (*Response, error) + Create(*VolumeCreateRequest, string) (*Volume, *Response, error) + Lock(string) (*Response, error) + Unlock(string) (*Response, error) +} + +// VolumeAttachmentService defines attachment methdods +type VolumeAttachmentService interface { + Get(string, *GetOptions) (*VolumeAttachment, *Response, error) + Create(string, string) (*VolumeAttachment, *Response, error) + Delete(string) (*Response, error) +} + +type volumesRoot struct { + Volumes []Volume `json:"volumes"` + Meta meta `json:"meta"` +} + +// Volume represents a volume +type Volume struct { + Attachments []*VolumeAttachment `json:"attachments,omitempty"` + BillingCycle string `json:"billing_cycle,omitempty"` + Created string `json:"created_at,omitempty"` + Description string `json:"description,omitempty"` + Facility *Facility `json:"facility,omitempty"` + Href string `json:"href,omitempty"` + ID string `json:"id"` + Locked bool `json:"locked,omitempty"` + Name string `json:"name,omitempty"` + Plan *Plan `json:"plan,omitempty"` + Project *Project `json:"project,omitempty"` + Size int `json:"size,omitempty"` + SnapshotPolicies []*SnapshotPolicy `json:"snapshot_policies,omitempty"` + State string `json:"state,omitempty"` + Updated string `json:"updated_at,omitempty"` +} + +// SnapshotPolicy used to execute actions on volume +type SnapshotPolicy struct { + ID string `json:"id"` + Href string `json:"href"` + SnapshotFrequency string `json:"snapshot_frequency,omitempty"` + SnapshotCount int `json:"snapshot_count,omitempty"` +} + +func (v Volume) String() string { + return Stringify(v) +} + +// VolumeCreateRequest type used to create an Equinix Metal volume +type VolumeCreateRequest struct { + BillingCycle string `json:"billing_cycle"` + Description string `json:"description,omitempty"` + Locked bool `json:"locked,omitempty"` + Size int `json:"size"` + PlanID string `json:"plan_id"` + FacilityID string `json:"facility_id"` + SnapshotPolicies []*SnapshotPolicy `json:"snapshot_policies,omitempty"` +} + +func (v VolumeCreateRequest) String() string { + return Stringify(v) +} + +// VolumeUpdateRequest type used to update an Equinix Metal volume +type VolumeUpdateRequest struct { + Description *string `json:"description,omitempty"` + PlanID *string `json:"plan_id,omitempty"` + Size *int `json:"size,omitempty"` + BillingCycle *string `json:"billing_cycle,omitempty"` +} + +// VolumeAttachment is a type from Equinix Metal API +type VolumeAttachment struct { + Href string `json:"href"` + ID string `json:"id"` + Volume Volume `json:"volume"` + Device Device `json:"device"` +} + +func (v VolumeUpdateRequest) String() string { + return Stringify(v) +} + +// VolumeAttachmentServiceOp implements VolumeService +type VolumeAttachmentServiceOp struct { + client *Client +} + +// VolumeServiceOp implements VolumeService +type VolumeServiceOp struct { + client *Client +} + +// List returns the volumes for a project +func (v *VolumeServiceOp) List(projectID string, opts *ListOptions) (volumes []Volume, resp *Response, err error) { + endpointPath := path.Join(projectBasePath, projectID, volumeBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + for { + subset := new(volumesRoot) + + resp, err = v.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + volumes = append(volumes, subset.Volumes...) + + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + return + } +} + +// Get returns a volume by id +func (v *VolumeServiceOp) Get(volumeID string, opts *GetOptions) (*Volume, *Response, error) { + endpointPath := path.Join(volumeBasePath, volumeID) + apiPathQuery := opts.WithQuery(endpointPath) + volume := new(Volume) + + resp, err := v.client.DoRequest("GET", apiPathQuery, nil, volume) + if err != nil { + return nil, resp, err + } + + return volume, resp, err +} + +// Update updates a volume +func (v *VolumeServiceOp) Update(id string, updateRequest *VolumeUpdateRequest) (*Volume, *Response, error) { + apiPath := path.Join(volumeBasePath, id) + volume := new(Volume) + + resp, err := v.client.DoRequest("PATCH", apiPath, updateRequest, volume) + if err != nil { + return nil, resp, err + } + + return volume, resp, err +} + +// Delete deletes a volume +func (v *VolumeServiceOp) Delete(volumeID string) (*Response, error) { + apiPath := path.Join(volumeBasePath, volumeID) + + return v.client.DoRequest("DELETE", apiPath, nil, nil) +} + +// Create creates a new volume for a project +func (v *VolumeServiceOp) Create(createRequest *VolumeCreateRequest, projectID string) (*Volume, *Response, error) { + url := path.Join(projectBasePath, projectID, volumeBasePath) + volume := new(Volume) + + resp, err := v.client.DoRequest("POST", url, createRequest, volume) + if err != nil { + return nil, resp, err + } + + return volume, resp, err +} + +// Attachments + +// Create Attachment, i.e. attach volume to a device +func (v *VolumeAttachmentServiceOp) Create(volumeID, deviceID string) (*VolumeAttachment, *Response, error) { + url := path.Join(volumeBasePath, volumeID, attachmentsBasePath) + volAttachParam := map[string]string{ + "device_id": deviceID, + } + volumeAttachment := new(VolumeAttachment) + + resp, err := v.client.DoRequest("POST", url, volAttachParam, volumeAttachment) + if err != nil { + return nil, resp, err + } + return volumeAttachment, resp, nil +} + +// Get gets attachment by id +func (v *VolumeAttachmentServiceOp) Get(attachmentID string, opts *GetOptions) (*VolumeAttachment, *Response, error) { + endpointPath := path.Join(volumeBasePath, attachmentsBasePath, attachmentID) + apiPathQuery := opts.WithQuery(endpointPath) + volumeAttachment := new(VolumeAttachment) + + resp, err := v.client.DoRequest("GET", apiPathQuery, nil, volumeAttachment) + if err != nil { + return nil, resp, err + } + + return volumeAttachment, resp, nil +} + +// Delete deletes attachment by id +func (v *VolumeAttachmentServiceOp) Delete(attachmentID string) (*Response, error) { + apiPath := path.Join(volumeBasePath, attachmentsBasePath, attachmentID) + + return v.client.DoRequest("DELETE", apiPath, nil, nil) +} + +// Lock sets a volume to "locked" +func (v *VolumeServiceOp) Lock(id string) (*Response, error) { + apiPath := path.Join(volumeBasePath, id) + action := lockType{Locked: true} + + return v.client.DoRequest("PATCH", apiPath, action, nil) +} + +// Unlock sets a volume to "unlocked" +func (v *VolumeServiceOp) Unlock(id string) (*Response, error) { + apiPath := path.Join(volumeBasePath, id) + action := lockType{Locked: false} + + return v.client.DoRequest("PATCH", apiPath, action, nil) +} diff --git a/provider/equinixmetal/vendor/github.com/packethost/packngo/vpn.go b/provider/equinixmetal/vendor/github.com/packethost/packngo/vpn.go new file mode 100644 index 00000000..6ae09169 --- /dev/null +++ b/provider/equinixmetal/vendor/github.com/packethost/packngo/vpn.go @@ -0,0 +1,74 @@ +package packngo + +import "fmt" + +const vpnBasePath = "/user/vpn" + +// VPNConfig struct +// +// Deprecated: As of March 31, 2021, Doorman service is no longer +// available. See https://metal.equinix.com/developers/docs/accounts/doorman/ +// for more details. +type VPNConfig struct { + Config string `json:"config,omitempty"` +} + +// VPNService interface defines available VPN functions +// +// Deprecated: As of March 31, 2021, Doorman service is no longer +// available. See https://metal.equinix.com/developers/docs/accounts/doorman/ +// for more details. +type VPNService interface { + Enable() (*Response, error) + Disable() (*Response, error) + Get(code string, getOpt *GetOptions) (*VPNConfig, *Response, error) +} + +// VPNServiceOp implements VPNService +// +// Deprecated: As of March 31, 2021, Doorman service is no longer +// available. See https://metal.equinix.com/developers/docs/accounts/doorman/ +// for more details. +type VPNServiceOp struct { + client *Client +} + +// Enable VPN for current user +// +// Deprecated: As of March 31, 2021, Doorman service is no longer +// available. See https://metal.equinix.com/developers/docs/accounts/doorman/ +// for more details. +func (s *VPNServiceOp) Enable() (resp *Response, err error) { + return s.client.DoRequest("POST", vpnBasePath, nil, nil) +} + +// Disable VPN for current user +// +// Deprecated: As of March 31, 2021, Doorman service is no longer +// available. See https://metal.equinix.com/developers/docs/accounts/doorman/ +// for more details. +func (s *VPNServiceOp) Disable() (resp *Response, err error) { + return s.client.DoRequest("DELETE", vpnBasePath, nil, nil) + +} + +// Get returns the client vpn config for the currently logged-in user. +// +// Deprecated: As of March 31, 2021, Doorman service is no longer +// available. See https://metal.equinix.com/developers/docs/accounts/doorman/ +// for more details. +func (s *VPNServiceOp) Get(code string, opts *GetOptions) (config *VPNConfig, resp *Response, err error) { + params := urlQuery(opts) + config = &VPNConfig{} + apiPath := fmt.Sprintf("%s?code=%s", vpnBasePath, code) + if params != "" { + apiPath += params + } + + resp, err = s.client.DoRequest("GET", apiPath, nil, config) + if err != nil { + return nil, resp, err + } + + return config, resp, err +} diff --git a/provider/equinixmetal/vendor/vendor.json b/provider/equinixmetal/vendor/vendor.json new file mode 100644 index 00000000..5f21283e --- /dev/null +++ b/provider/equinixmetal/vendor/vendor.json @@ -0,0 +1,13 @@ +{ + "comment": "", + "ignore": "test", + "package": [ + { + "checksumSHA1": "kItpnJu5G9MVC56waIyHLoQZt/s=", + "path": "github.com/packethost/packngo", + "revision": "2e0421a3ecec0ce96df230128ee44669e8cd6e3a", + "revisionTime": "2021-05-20T00:32:48Z" + } + ], + "rootPath": "github.com/hashicorp/go-discover/provider/equinixmetal" +} From c3812be065f43c46dfef1198cbf2fa03ae0f7186 Mon Sep 17 00:00:00 2001 From: Marques Johansson Date: Fri, 18 Jun 2021 18:35:13 -0400 Subject: [PATCH 3/5] update packngo vendor for packet provider Signed-off-by: Marques Johansson --- .../github.com/packethost/packngo/.gitignore | 30 ++ .../packethost/packngo/CHANGELOG.md | 58 +- .../packethost/packngo/CONTRIBUTING.md | 119 +++++ .../vendor/github.com/packethost/packngo/DCO | 37 ++ .../github.com/packethost/packngo/Makefile | 24 + .../github.com/packethost/packngo/OWNERS.md | 26 + .../github.com/packethost/packngo/README.md | 125 ++++- .../github.com/packethost/packngo/RELEASE.md | 40 ++ .../github.com/packethost/packngo/SUPPORT.md | 17 + .../packethost/packngo/api_call_options.go | 194 +++++++ .../github.com/packethost/packngo/apikeys.go | 176 +++++++ .../github.com/packethost/packngo/batches.go | 106 ++++ .../packethost/packngo/bgp_configs.go | 85 +++ .../packethost/packngo/bgp_sessions.go | 99 ++++ .../packethost/packngo/capacities.go | 103 ++++ .../packethost/packngo/code-of-conduct.md | 3 + .../packethost/packngo/connections.go | 218 ++++++++ .../packethost/packngo/device_ports.go | 323 ++++++++++++ .../github.com/packethost/packngo/devices.go | 495 +++++++++++++++--- .../github.com/packethost/packngo/doc.go | 3 + .../github.com/packethost/packngo/email.go | 58 +- .../github.com/packethost/packngo/events.go | 81 +++ .../packethost/packngo/facilities.go | 12 +- .../github.com/packethost/packngo/go.mod | 9 + .../github.com/packethost/packngo/go.sum | 21 + .../packngo/hardware_reservations.go | 92 ++++ .../github.com/packethost/packngo/ip.go | 143 +++-- .../github.com/packethost/packngo/metros.go | 42 ++ .../packethost/packngo/notifications.go | 94 ++++ .../packethost/packngo/operatingsystems.go | 11 +- .../packethost/packngo/organizations.go | 67 ++- .../github.com/packethost/packngo/packngo.go | 286 +++++++--- .../packethost/packngo/payment_methods.go | 11 +- .../github.com/packethost/packngo/plans.go | 56 +- .../github.com/packethost/packngo/ports.go | 269 +++++----- .../github.com/packethost/packngo/projects.go | 124 +++-- .../packethost/packngo/spotmarket.go | 59 ++- .../packethost/packngo/spotmarketrequest.go | 120 +++++ .../github.com/packethost/packngo/sshkeys.go | 33 +- .../packethost/packngo/timestamp.go | 7 +- .../packethost/packngo/two_factor_auth.go | 56 ++ .../github.com/packethost/packngo/user.go | 53 +- .../github.com/packethost/packngo/utils.go | 113 +++- .../github.com/packethost/packngo/version.go | 36 ++ .../packethost/packngo/virtualcircuits.go | 102 ++++ .../packethost/packngo/virtualnetworks.go | 69 ++- .../github.com/packethost/packngo/volumes.go | 78 ++- .../github.com/packethost/packngo/vpn.go | 74 +++ provider/packet/vendor/vendor.json | 6 +- 49 files changed, 3834 insertions(+), 629 deletions(-) create mode 100644 provider/packet/vendor/github.com/packethost/packngo/.gitignore create mode 100644 provider/packet/vendor/github.com/packethost/packngo/CONTRIBUTING.md create mode 100644 provider/packet/vendor/github.com/packethost/packngo/DCO create mode 100644 provider/packet/vendor/github.com/packethost/packngo/Makefile create mode 100644 provider/packet/vendor/github.com/packethost/packngo/OWNERS.md create mode 100644 provider/packet/vendor/github.com/packethost/packngo/RELEASE.md create mode 100644 provider/packet/vendor/github.com/packethost/packngo/SUPPORT.md create mode 100644 provider/packet/vendor/github.com/packethost/packngo/api_call_options.go create mode 100644 provider/packet/vendor/github.com/packethost/packngo/apikeys.go create mode 100644 provider/packet/vendor/github.com/packethost/packngo/batches.go create mode 100644 provider/packet/vendor/github.com/packethost/packngo/bgp_configs.go create mode 100644 provider/packet/vendor/github.com/packethost/packngo/bgp_sessions.go create mode 100644 provider/packet/vendor/github.com/packethost/packngo/capacities.go create mode 100644 provider/packet/vendor/github.com/packethost/packngo/code-of-conduct.md create mode 100644 provider/packet/vendor/github.com/packethost/packngo/connections.go create mode 100644 provider/packet/vendor/github.com/packethost/packngo/device_ports.go create mode 100644 provider/packet/vendor/github.com/packethost/packngo/doc.go create mode 100644 provider/packet/vendor/github.com/packethost/packngo/events.go create mode 100644 provider/packet/vendor/github.com/packethost/packngo/go.mod create mode 100644 provider/packet/vendor/github.com/packethost/packngo/go.sum create mode 100644 provider/packet/vendor/github.com/packethost/packngo/hardware_reservations.go create mode 100644 provider/packet/vendor/github.com/packethost/packngo/metros.go create mode 100644 provider/packet/vendor/github.com/packethost/packngo/notifications.go create mode 100644 provider/packet/vendor/github.com/packethost/packngo/spotmarketrequest.go create mode 100644 provider/packet/vendor/github.com/packethost/packngo/two_factor_auth.go create mode 100644 provider/packet/vendor/github.com/packethost/packngo/version.go create mode 100644 provider/packet/vendor/github.com/packethost/packngo/virtualcircuits.go create mode 100644 provider/packet/vendor/github.com/packethost/packngo/vpn.go diff --git a/provider/packet/vendor/github.com/packethost/packngo/.gitignore b/provider/packet/vendor/github.com/packethost/packngo/.gitignore new file mode 100644 index 00000000..7cc2daad --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/.gitignore @@ -0,0 +1,30 @@ +### Go template +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +*.swp + +# IDEs +.idea**/** +.vscode diff --git a/provider/packet/vendor/github.com/packethost/packngo/CHANGELOG.md b/provider/packet/vendor/github.com/packethost/packngo/CHANGELOG.md index 34e5d311..53b6de27 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/CHANGELOG.md +++ b/provider/packet/vendor/github.com/packethost/packngo/CHANGELOG.md @@ -1,54 +1,12 @@ # Changelog -All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). -This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +This project adheres to [Semantic +Versioning](http://semver.org/spec/v2.0.0.html). -## [Unreleased] +All notable changes to this project will be documented at +. Drafts release notes may be +used to track features that will be available in future releases. -This release contains a bunch of fixes to the package api after some more real -world use. There a few breaks in backwards compatibility, but we are tying to -minimize them and move towards a 1.0 release. - -### Added -- "acceptance" tests which run against production api (will incur charges) -- HardwareReservation to Device -- RootPassword to Device -- Spot market support -- Management and Manageable fields to discern between Elastic IPs and device unique IP -- Support for Volume attachments to Device and Volume -- Support for ProvisionEvents -- DoRequest sugar to Client -- Add ListProject function to the SSHKeys interface -- Operations for switching between Network Modes, aka "L2 support" - Support for Organization, Payment Method and Billing address resources - -### Fixed -- User.Emails json tag is fixed to match api response -- Single error object api response is now handled correctly - -### Changed -- IPService was split to DeviceIPService and ProjectIPService -- Renamed Device.IPXEScriptUrl -> Device.IPXEScriptURL -- Renamed DeviceCreateRequest.HostName -> DeviceCreateRequest.Hostname -- Renamed DeviceCreateRequest.IPXEScriptUrl -> DeviceCreateRequest.IPXEScriptURL -- Renamed DeviceUpdateRequest.HostName -> DeviceUpdateRequest.Hostname -- Renamed DeviceUpdateRequest.IPXEScriptUrl -> DeviceUpdateRequest.IPXEScriptURL -- Sync with packet.net api change to /projects/{id}/ips which no longer returns - the address in CIDR form -- Removed package level exported functions that should have never existed - -## [0.1.0] - 2017-08-17 - -Initial release, supports most of the api for interacting with: - -- Plans -- Users -- Emails -- SSH Keys -- Devices -- Projects -- Facilities -- Operating Systems -- IP Reservations -- Volumes +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/), +breaking changes, additions, removals, and fixes should be pointed out in the +release notes. diff --git a/provider/packet/vendor/github.com/packethost/packngo/CONTRIBUTING.md b/provider/packet/vendor/github.com/packethost/packngo/CONTRIBUTING.md new file mode 100644 index 00000000..368e3a4a --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/CONTRIBUTING.md @@ -0,0 +1,119 @@ +# Contributing + +Thanks for your interest in improving this project! Before we get technical, +make sure you have reviewed the [code of conduct](code-of-conduct.md), +[Developer Certificate of Origin](DCO), and [OWNERS](OWNERS.md) files. Code will +be licensed according to [LICENSE.txt](LICENSE.txt). + +## Pull Requests + +When creating a pull request, please refer to an open issue. If there is no +issue open for the pull request you are creating, please create one. Frequently, +pull requests may be merged or closed while the underlying issue being addressed +is not fully addressed. Issues are a place to discuss the problem in need of a +solution. Pull requests are a place to discuss an implementation of one +particular answer to that problem. A pull request may not address all (or any) +of the problems expressed in the issue, so it is important to track these +separately. + +## Code Quality + +### Documentation + +All public functions and variables should include at least a short description +of the functionality they provide. Comments should be formatted according to +. + +Documentation at will be +generated from these comments. + +The documentation provided for packngo fields and functions should be at or +better than the quality provided at . +When the API documentation provides a lengthy description, a linking to the +related API documentation will benefit users. + +### Linters + +`golangci-lint` is used to verify that the style of the code remains consistent. + +Before committing, it's a good idea to run `goimports -w .`. +([goimports](https://pkg.go.dev/golang.org/x/tools/cmd/goimports?tab=doc)) and +`gofmt -w *.go`. ([gofmt](https://golang.org/cmd/gofmt/)) + +`make lint` can be used to verify style before creating a pull request. + +## Building and Testing + +The [Makefile](./Makefile) contains the targets to build, lint and test: + +```sh +make build +make lint +make test +``` + +These normally will be run in a docker image of golang. To run locally, just run +with `BUILD=local`: + +```sh +make build BUILD=local +make lint BUILD=local +make test BUILD=local +``` + +### Acceptance Tests + +If you want to run tests against the actual Equinix Metal API, you must set the +environment variable `PACKET_TEST_ACTUAL_API` to a non-empty string and set +`PACKNGO_TEST_RECORDER` to `disabled`. The device tests wait for the device +creation, so it's best to run a few in parallel. + +To run a particular test, you can do + +```sh +PACKNGO_TEST_ACTUAL_API=1 go test -v -run=TestAccDeviceBasic --timeout=2h +``` + +If you want to see HTTP requests, set the `PACKNGO_DEBUG` env var to non-empty +string, for example: + +```sh +PACKNGO_DEBUG=1 PACKNGO_TEST_ACTUAL_API=1 go test -v -run=TestAccVolumeUpdate +``` + +### Test Fixtures + +By default, `go test ./...` will skip most of the tests unless +`PACKNGO_TEST_ACTUAL_API` is non-empty. + +With the `PACKNGO_TEST_ACTUAL_API` environment variable set, tests will be run +against the Equinix Metal API, creating real infrastructure and incurring costs. + +The `PACKNGO_TEST_RECORDER` variable can be used to record and playback API +responses to test code changes without the delay and costs of making actual API +calls. When unset, `PACKNGO_TEST_RECORDER` acts as though it was set to +`disabled`. This is the default behavior. This default behavior may change in +the future once fixtures are available for all tests. + +When `PACKNGO_TEST_RECORDER` is set to `play`, tests will playback API responses +from recorded HTTP response fixtures. This is idea for refactoring and making +changes to request and response handling without introducing changes to the data +sent or received by the Equinix Metal API. + +When adding support for new end-points, recorded test sessions should be added. +Record the HTTP interactions to fixtures by setting the environment variable +`PACKNGO_TEST_RECORDER` to `record`. + +The fixtures are automatically named according to the test they were run from. +They are placed in `fixtures/`. The API token used during authentication is +automatically removed from these fixtures. Nonetheless, caution should be +exercised before committing any fixtures into the project. Account details +includes API tokens, contact, and payment details could easily be leaked by +committing fixtures that haven't been thoroughly reviewed. + +### Automation (CI/CD) + +Today, Drone tests pull requests using tests defined in +[.drone.yml](.drone.yml). + +See [RELEASE.md](RELEASE.md) for details on the release process. diff --git a/provider/packet/vendor/github.com/packethost/packngo/DCO b/provider/packet/vendor/github.com/packethost/packngo/DCO new file mode 100644 index 00000000..068953d4 --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/DCO @@ -0,0 +1,37 @@ +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. + diff --git a/provider/packet/vendor/github.com/packethost/packngo/Makefile b/provider/packet/vendor/github.com/packethost/packngo/Makefile new file mode 100644 index 00000000..90ceffa3 --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/Makefile @@ -0,0 +1,24 @@ +IMG ?= golang:1.15 + +# enable go modules, disabled CGO + +GOENV ?= GO111MODULE=on CGO_ENABLED=0 +export GO111MODULE=on +export CGO_ENABLED=0 + +# we build in a docker image, unless we are set to BUILD=local +GO ?= docker run --rm -v $(PWD):/app -w /app $(IMG) env $(GOENV) +ifeq ($(BUILD),local) +GO = +endif + + +build: + $(GO) go build -i -v ./... + +lint: + @docker run --rm -v $(CURDIR):/app -w /app golangci/golangci-lint:v1.34.1 golangci-lint run -v + +test: + $(GO) test ./... + diff --git a/provider/packet/vendor/github.com/packethost/packngo/OWNERS.md b/provider/packet/vendor/github.com/packethost/packngo/OWNERS.md new file mode 100644 index 00000000..8b002e98 --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/OWNERS.md @@ -0,0 +1,26 @@ +# Owners + +This project is governed by [Equinix Metal] and benefits from a community of users that +collaborate and contribute to its use in Go powered projects, such as the [Equinix Metal +Terraform provider], [Docker machine driver], Kubernetes drivers for [CSI] and [CCM], +the [Equinix Metal CLI], and others. + +Members of the Equinix Metal Github organization will strive to triage issues in a +timely manner, see [SUPPORT.md] for details. + +See the [packethost/standards glossary] for more details about this file. + +## Maintainers + +Maintainers of this repository are defined within the [CODEOWNERS] file. + +[Equinix Metal]: https://metal.equinix.com +[Equinix Metal Terraform provider]: https://github.com/packethost/terraform-provider-packet +[Docker machine driver]: https://github.com/packethost/docker-machine-driver-packet +[CSI]: https://github.com/packethost/csi-packet +[CCM]: https://github.com/packethost/packet-ccm +[Equinix Metal CLI]: https://github.com/packethost/packet-cli +[SUPPORT.md]: SUPPORT.md +[packethost/standards +glossary]: https://github.com/packethost/standards/blob/master/glossary.md#ownersmd +[CODEOWNERS]: CODEOWNERS diff --git a/provider/packet/vendor/github.com/packethost/packngo/README.md b/provider/packet/vendor/github.com/packethost/packngo/README.md index a63faf5a..0678b780 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/README.md +++ b/provider/packet/vendor/github.com/packethost/packngo/README.md @@ -1,20 +1,43 @@ # packngo -Packet Go Api Client -![](https://www.packet.net/media/images/xeiw-packettwitterprofilew.png) +[![](https://img.shields.io/badge/stability-maintained-green.svg)](https://github.com/packethost/standards/blob/master/maintained-statement.md) +[![Release](https://img.shields.io/github/v/release/packethost/packngo)](https://github.com/packethost/packngo/releases/latest) +[![GoDoc](https://godoc.org/github.com/packethost/packngo?status.svg)](https://godoc.org/github.com/packethost/packngo) +[![Go Report Card](https://goreportcard.com/badge/github.com/packethost/packngo)](https://goreportcard.com/report/github.com/packethost/packngo) +[![Slack](https://slack.equinixmetal.com/badge.svg)](https://slack.equinixmetal.com/) +[![Twitter Follow](https://img.shields.io/twitter/follow/equinixmetal.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=equinixmetal) +A Golang client for the Equinix Metal API. ([Packet is now Equinix Metal](https://blog.equinix.com/blog/2020/10/06/equinix-metal-metal-and-more/)) -Installation ------------- +## Installation -`go get github.com/packethost/packngo` +To import this library into your Go project: -Usage ------ +```go +import "github.com/packethost/packngo" +``` + +Reference a particular version with: + +```sh +go get github.com/packethost/packngo@v0.2.0 +``` + +## Stability and Compatibility + +This repository is [Maintained](https://github.com/packethost/standards/blob/master/maintained-statement.md) meaning that this software is supported by Equinix Metal and its community - available to use in production environments. + +Packngo is currently provided with a major version of [v0](https://blog.golang.org/v2-go-modules). We'll try to avoid breaking changes to this library, but they will certainly happen as we work towards a stable v1 library. See [CHANGELOG.md](CHANGELOG.md) for details on the latest additions, removals, fixes, and breaking changes. + +While packngo provides an interface to most of the [Equinix Metal API](https://metal.equinix.com/developers/api/), the API is regularly adding new features. To request or contribute support for more API end-points or added fields, [create an issue](https://github.com/packethost/packngo/issues/new). + +See [SUPPORT.md](SUPPORT.md) for any other issues. + +## Usage -To authenticate to the Packet API, you must have your API token exported in env var `PACKET_API_TOKEN`. +To authenticate to the Equinix Metal API, you must have your API token exported in env var `PACKET_AUTH_TOKEN`. -This code snippet initializes Packet API client, and lists your Projects: +This code snippet initializes Equinix Metal API client, and lists your Projects: ```go package main @@ -42,31 +65,87 @@ func main() { ``` -This lib is used by the official [terraform-provider-packet](https://github.com/terraform-providers/terraform-provider-packet). +This library is used by the official [terraform-provider-packet](https://github.com/packethost/terraform-provider-packet). -You can also learn a lot from the `*_test.go` sources. Almost all out tests touch the Packet API, so you can see how auth, querying and POSTing works. For example [devices_test.go](devices_test.go). +You can also learn a lot from the `*_test.go` sources. Almost all out tests touch the Equinix Metal API, so you can see how auth, querying and POSTing works. For example [devices_test.go](devices_test.go). +
+Linked Resources +### Linked resources in Get\* and List\* functions -Acceptance Tests ----------------- +The Equinix Metal API includes references to related entities for a wide selection of resource types, indicated by `href` fields. The Equinix Metal API allows for these entities to be included in the API response, saving the user from making more round-trip API requests. This is useful for linked resources, e.g members of a project, devices in a project. Similarly, by excluding entities that are included by default, you can reduce the API response time and payload size. -If you want to run tests against the actual Packet API, you must set envvar `PACKET_TEST_ACTUAL_API` to non-empty string for the `go test`. The device tests wait for the device creation, so it's best to run a few in parallel. +Control of this behavior is provided through [common attributes](https://metal.equinix.com/developers/api/common-parameters/) that can be used to toggle, by field name, which referenced resources will be included as values in API responses. The API exposes this feature through `?include=` and `?exclude=` query parameters which accept a comma-separated list of field names. These field names can be dotted to reference nested entities. -To run a particular test, you can do +Most of the packngo `Get` functions take references to `GetOptions` parameters (or `ListOptions` for `List` functions). These types include an `Include` and `Exclude` slice that will be converted to query parameters upon request. -``` -$ PACKNGO_TEST_ACTUAL_API=1 go test -v -run=TestAccDeviceBasic -``` +For example, if you want to list users in a project, you can fetch the project via `Projects.Get(pid, nil)` call. The result of this call will be a `Project` which has a `Users []User` attribute. The items in the `[]User` slice only have a non-zero URL attribute, the rest of the fields will be type defaults. You can then parse the ID of the User resources and fetch them consequently. -If you want to see HTTP requests, set the `PACKNGO_DEBUG` env var to non-empty string, for example: +Optionally, you can use the ListOptions struct in the project fetch call to include the Users (`members` JSON tag). Then, every item in the `[]User` slice will have all (not only the `Href`) attributes populated. +```go +Projects.Get(pid, &packngo.ListOptions{Includes: []{'members'}}) ``` -$ PACKNGO_DEBUG=1 PACKNGO_TEST_ACTUAL_API=1 go test -v -run=TestAccVolumeUpdate + +The following is a more comprehensive illustration of Includes and Excludes. + +```go +import ( + "log" + + "github.com/packethost/packngo" +) + +func listProjectsAndUsers(lo *packngo.ListOptions) { + c, err := packngo.NewClient() + if err != nil { + log.Fatal(err) + } + + ps, _, err := c.Projects.List(lo) + if err != nil { + log.Fatal(err) + } + log.Printf("Listing for listOptions %+v\n", lo) + for _, p := range ps { + log.Printf("project resource %s has %d users", p.Name, len(p.Users)) + for _, u := range p.Users { + if u.Email != "" && u.FullName != "" { + log.Printf(" user %s has email %s\n", u.FullName, u.Email) + } else { + log.Printf(" only got user link %s\n", u.URL) + } + } + } +} + +func main() { + loMembers := &packngo.ListOptions{Includes: []string{"members"}} + loMembersOut := &packngo.ListOptions{Excludes: []string{"members"}} + listProjectsAndUsers(loMembers) + listProjectsAndUsers(nil) + listProjectsAndUsers(loMembersOut) +} ``` +
+ +### Deprecation and Sunset + +If the Equinix Metal API returns a [RFC-8594](https://tools.ietf.org/html/rfc8594) `Deprecation` or `Sunset` header, packngo will log this header to stderr with any accompanied `Link` headers. + +Example: + +```console +WARNING: "POST /deprecate-and-sunset" reported deprecation +WARNING: "POST /deprecate-and-sunset" reported sunsetting on Sat, 1 Aug 2020 23:59:59 GMT +WARNING: See for deprecation details +WARNING: See for sunset details +WARNING: See for sunset details +WARNING: See for deprecation details +``` -Committing ----------- +## Contributing -Before committing, it's a good idea to run `gofmt -w *.go`. ([gofmt](https://golang.org/cmd/gofmt/)) +See [CONTIBUTING.md](CONTRIBUTING.md). diff --git a/provider/packet/vendor/github.com/packethost/packngo/RELEASE.md b/provider/packet/vendor/github.com/packethost/packngo/RELEASE.md new file mode 100644 index 00000000..74a9ac2d --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/RELEASE.md @@ -0,0 +1,40 @@ +# Releases + +This file serves to provide guidance and act as a checklist for any maintainers +to this project, now or in the future. This file should be updated with any +changes to the process. Automated processes should be described well enough that +they can be run in the absence of that automation. + +* See [CHANGELOG.md](CHANGELOG.md) for notes on versioning. +* Fetch the latest origin branches: + + ```sh + git fetch origin + git checkout master + git pull + ``` + +* Verify that your branch matches the upstream branch: + + ```sh + git branch --points-at=master -r | grep origin/master >/dev/null || echo "master differs from origin/master" + ``` + +* Tag `master` with a semver tag that suits the level of changes + introduced: + + ```sh + git tag -m "v0.8.0" -a v0.8.0 master # use -s if gpg is available + ``` +* Push the tag: + + ```sh + git push --tags origin master v0.8.0 + ``` +* Create a release from the tag (include a keepachangelog.com formatted description): + + (use the correct + version) + +Releases can be followed through the GitHub Atom feed at +. diff --git a/provider/packet/vendor/github.com/packethost/packngo/SUPPORT.md b/provider/packet/vendor/github.com/packethost/packngo/SUPPORT.md new file mode 100644 index 00000000..86f8401f --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/SUPPORT.md @@ -0,0 +1,17 @@ +# Support + +Please [open a GitHub Issue] stating any problems that you encounter using this +software. Be sure to leave a descriptive report of the problem, including the +environment you were running in and the expected behavior. Be careful not to +include any sensitive information such as API Keys, IP addresses or their +ranges, or any resource identifiers (UUID or ID) of your organizations, +projects, or devices. + +As an open-source project, the priority, timing, or eventual resolution is not +guaranteed. Issues will be addressed based on priorities that may or may not +be reflected in Github or issue comments. + +For other forms of support, including our Slack community, visit +. + +[open a GitHub Issue]: https://github.com/packethost/packngo/issues/new \ No newline at end of file diff --git a/provider/packet/vendor/github.com/packethost/packngo/api_call_options.go b/provider/packet/vendor/github.com/packethost/packngo/api_call_options.go new file mode 100644 index 00000000..381b5b84 --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/api_call_options.go @@ -0,0 +1,194 @@ +package packngo + +import ( + "fmt" + "net/url" + "strconv" + "strings" +) + +type ListSortDirection string + +const ( + SortDirectionAsc ListSortDirection = "asc" + SortDirectionDesc ListSortDirection = "desc" +) + +// GetOptions are options common to Equinix Metal API GET requests +type GetOptions struct { + // Includes are a list of fields to expand in the request results. + // + // For resources that contain collections of other resources, the Equinix Metal API + // will only return the `Href` value of these resources by default. In + // nested API Go types, this will result in objects that have zero values in + // all fiends except their `Href` field. When an object's associated field + // name is "included", the returned fields will be Uumarshalled into the + // nested object. Field specifiers can use a dotted notation up to three + // references deep. (For example, "memberships.projects" can be used in + // ListUsers.) + Includes []string `url:"include,omitempty,comma"` + + // Excludes reduce the size of the API response by removing nested objects + // that may be returned. + // + // The default behavior of the Equinix Metal API is to "exclude" fields, but some + // API endpoints have an "include" behavior on certain fields. Nested Go + // types unmarshalled into an "excluded" field will only have a values in + // their `Href` field. + Excludes []string `url:"exclude,omitempty,comma"` + + // Page is the page of results to retrieve for paginated result sets + Page int `url:"page,omitempty"` + + // PerPage is the number of results to return per page for paginated result + // sets, + PerPage int `url:"per_page,omitempty"` + + // Search is a special API query parameter that, for resources that support + // it, will filter results to those with any one of various fields matching + // the supplied keyword. For example, a resource may have a defined search + // behavior matches either a name or a fingerprint field, while another + // resource may match entirely different fields. Search is currently + // implemented for SSHKeys and uses an exact match. + Search string `url:"search,omitempty"` + + SortBy string `url:"sort_by,omitempty"` + SortDirection ListSortDirection `url:"sort_direction,omitempty"` + + Meta meta `url:"-"` +} + +type ListOptions = GetOptions +type SearchOptions = GetOptions + +type QueryAppender interface { + WithQuery(path string) string // we use this in all List functions (urlQuery) + GetPage() int // we use this in List + Including(...string) // we use this in Device List to add facility + Excluding(...string) +} + +// GetOptions returns GetOptions from GetOptions (and is nil-receiver safe) +func (g *GetOptions) GetOptions() *GetOptions { + getOpts := GetOptions{} + if g != nil { + getOpts.Includes = g.Includes + getOpts.Excludes = g.Excludes + } + return &getOpts +} + +func (g *GetOptions) WithQuery(apiPath string) string { + params := g.Encode() + if params != "" { + // parse path, take existing vars + return fmt.Sprintf("%s?%s", apiPath, params) + } + return apiPath +} + +// OptionsGetter provides GetOptions +type OptionsGetter interface { + GetOptions() *GetOptions +} + +func (g *GetOptions) GetPage() int { // guaranteed int + if g == nil { + return 0 + } + return g.Page +} + +func (g *GetOptions) CopyOrNew() *GetOptions { + if g == nil { + return &GetOptions{} + } + ret := *g + return &ret +} + +// Including ensures that the variadic refs are included in a copy of the +// options, resulting in expansion of the the referred sub-resources. Unknown +// values within refs will be silently ignore by the API. +func (g *GetOptions) Including(refs ...string) *GetOptions { + ret := g.CopyOrNew() + for _, v := range refs { + if !contains(ret.Includes, v) { + ret.Includes = append(ret.Includes, v) + } + } + return ret +} + +// Excluding ensures that the variadic refs are included in the "Excluded" param +// in a copy of the options. +// Unknown values within refs will be silently ignore by the API. +func (g *GetOptions) Excluding(refs ...string) *GetOptions { + ret := g.CopyOrNew() + for _, v := range refs { + if !contains(ret.Excludes, v) { + ret.Excludes = append(ret.Excludes, v) + } + } + return ret +} + +func stripQuery(inURL string) string { + u, _ := url.Parse(inURL) + u.RawQuery = "" + return u.String() +} + +// nextPage is common and extracted from all List functions +func nextPage(meta meta, opts *GetOptions) (path string) { + if meta.Next != nil && (opts.GetPage() == 0) { + optsCopy := opts.CopyOrNew() + optsCopy.Page = meta.CurrentPageNum + 1 + return optsCopy.WithQuery(stripQuery(meta.Next.Href)) + } + if opts != nil { + opts.Meta = meta + } + return "" +} + +// Encode generates a URL query string ("?foo=bar") +func (g *GetOptions) Encode() string { + if g == nil { + return "" + } + v := url.Values{} + if g.Includes != nil && len(g.Includes) > 0 { + v.Add("include", strings.Join(g.Includes, ",")) + } + if g.Excludes != nil && len(g.Excludes) > 0 { + v.Add("exclude", strings.Join(g.Excludes, ",")) + } + if g.Page != 0 { + v.Add("page", strconv.Itoa(g.Page)) + } + if g.PerPage != 0 { + v.Add("per_page", strconv.Itoa(g.PerPage)) + } + if g.Search != "" { + v.Add("search", g.Search) + } + if g.SortBy != "" { + v.Add("sort_by", g.SortBy) + } + if g.SortDirection != "" { + v.Add("sort_direction", string(g.SortDirection)) + } + return v.Encode() +} + +/* +// Encode generates a URL query string ("?foo=bar") +func (g *GetOptions) Encode() string { + urlValues, _ := query.Values(g) + return urlValues.Encode() +} +*/ +func urlQuery(o *GetOptions) string { + return o.Encode() +} diff --git a/provider/packet/vendor/github.com/packethost/packngo/apikeys.go b/provider/packet/vendor/github.com/packethost/packngo/apikeys.go new file mode 100644 index 00000000..5241fa9c --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/apikeys.go @@ -0,0 +1,176 @@ +package packngo + +import ( + "fmt" + "path" +) + +const ( + apiKeyBasePath = "/api-keys" +) + +// APIKeyService interface defines available device methods +type APIKeyService interface { + UserList(*ListOptions) ([]APIKey, *Response, error) + ProjectList(string, *ListOptions) ([]APIKey, *Response, error) + UserGet(string, *GetOptions) (*APIKey, error) + ProjectGet(string, string, *GetOptions) (*APIKey, error) + Create(*APIKeyCreateRequest) (*APIKey, *Response, error) + Delete(string) (*Response, error) +} + +type apiKeyRoot struct { + APIKeys []APIKey `json:"api_keys"` +} + +type APIKey struct { + // ID is the UUIDv4 representing an API key in API requests and responses. + ID string `json:"id"` + + // Description is any text description of the key. This can be used to + // describe the purpose of the key. + Description string `json:"description"` + + // Token is a sensitive credential that can be used as a `Client.APIKey` to + // access Equinix Metal resources. + Token string `json:"token"` + + // ReadOnly keys can not create new resources. + ReadOnly bool `json:"read_only"` + + // Created is the creation date of the API key. + Created string `json:"created_at"` + + // Updated is the last-update date of the API key. + Updated string `json:"updated_at"` + + // User will be non-nil when getting or listing an User API key. + User *User `json:"user"` + + // Project will be non-nil when getting or listing a Project API key + Project *Project `json:"project"` +} + +// APIKeyCreateRequest type used to create an api key. +type APIKeyCreateRequest struct { + // Description is any text description of the key. This can be used to + // describe the purpose of the key. + Description string `json:"description"` + + // ReadOnly keys can not create new resources. + ReadOnly bool `json:"read_only"` + + // ProjectID when non-empty will result in the creation of a Project API + // key. + ProjectID string `json:"-"` +} + +func (s APIKeyCreateRequest) String() string { + return Stringify(s) +} + +// APIKeyServiceOp implements APIKeyService +type APIKeyServiceOp struct { + client *Client +} + +func (s *APIKeyServiceOp) list(url string, opts *ListOptions) ([]APIKey, *Response, error) { + root := new(apiKeyRoot) + apiPathQuery := opts.WithQuery(url) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, root) + if err != nil { + return nil, resp, err + } + + return root.APIKeys, resp, err +} + +// ProjectList lists the API keys associated with a project having `projectID` +// match `Project.ID`. +func (s *APIKeyServiceOp) ProjectList(projectID string, opts *ListOptions) ([]APIKey, *Response, error) { + endpointPath := path.Join(projectBasePath, projectID, apiKeyBasePath) + return s.list(endpointPath, opts) +} + +// UserList returns the API keys for the User associated with the +// `Client.APIKey`. +// +// When `Client.APIKey` is a Project API key, this method will return an access +// denied error. +func (s *APIKeyServiceOp) UserList(opts *ListOptions) ([]APIKey, *Response, error) { + endpointPath := path.Join(userBasePath, apiKeyBasePath) + return s.list(endpointPath, opts) +} + +// ProjectGet returns the Project API key with the given `APIKey.ID`. +// +// In other methods, it is typical for a Response to be returned, which could +// include a StatusCode of `http.StatusNotFound` (404 error) when the resource +// was not found. The Equinix Metal API does not expose a get by ID endpoint for +// APIKeys. That is why in this method, all API keys are listed and compared +// for a match. Therefor, the Response is not returned and a custom error will +// be returned when the key is not found. +func (s *APIKeyServiceOp) ProjectGet(projectID, apiKeyID string, opts *GetOptions) (*APIKey, error) { + pkeys, _, err := s.ProjectList(projectID, opts) + if err != nil { + return nil, err + } + for _, k := range pkeys { + if k.ID == apiKeyID { + return &k, nil + } + } + return nil, fmt.Errorf("Project (%s) API key %s not found", projectID, apiKeyID) +} + +// UserGet returns the User API key with the given `APIKey.ID`. +// +// In other methods, it is typical for a Response to be returned, which could +// include a StatusCode of `http.StatusNotFound` (404 error) when the resource +// was not found. The Equinix Metal API does not expose a get by ID endpoint for +// APIKeys. That is why in this method, all API keys are listed and compared +// for a match. Therefor, the Response is not returned and a custom error will +// be returned when the key is not found. +func (s *APIKeyServiceOp) UserGet(apiKeyID string, opts *GetOptions) (*APIKey, error) { + ukeys, _, err := s.UserList(opts) + if err != nil { + return nil, err + } + for _, k := range ukeys { + if k.ID == apiKeyID { + return &k, nil + } + } + return nil, fmt.Errorf("User API key %s not found", apiKeyID) +} + +// Create creates a new API key. +// +// The API key can be either an User API key or a Project API key, determined by +// the value (or emptiness) of `APIKeyCreateRequest.ProjectID`. Either `User` or +// `Project` will be non-nil in the `APIKey` depending on this factor. +func (s *APIKeyServiceOp) Create(createRequest *APIKeyCreateRequest) (*APIKey, *Response, error) { + apiPath := path.Join(userBasePath, apiKeyBasePath) + if createRequest.ProjectID != "" { + apiPath = path.Join(projectBasePath, createRequest.ProjectID, apiKeyBasePath) + } + apiKey := new(APIKey) + + resp, err := s.client.DoRequest("POST", apiPath, createRequest, apiKey) + if err != nil { + return nil, resp, err + } + + return apiKey, resp, err +} + +// Delete deletes an API key by `APIKey.ID` +// +// The API key can be either an User API key or a Project API key. +// +// Project API keys can not be used to delete themselves. +func (s *APIKeyServiceOp) Delete(apiKeyID string) (*Response, error) { + apiPath := path.Join(userBasePath, apiKeyBasePath, apiKeyID) + return s.client.DoRequest("DELETE", apiPath, nil, nil) +} diff --git a/provider/packet/vendor/github.com/packethost/packngo/batches.go b/provider/packet/vendor/github.com/packethost/packngo/batches.go new file mode 100644 index 00000000..280b1071 --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/batches.go @@ -0,0 +1,106 @@ +package packngo + +import ( + "fmt" + "path" +) + +const batchBasePath = "/batches" + +// BatchService interface defines available batch methods +type BatchService interface { + Get(batchID string, getOpt *GetOptions) (*Batch, *Response, error) + List(ProjectID string, listOpt *ListOptions) ([]Batch, *Response, error) + Create(projectID string, batches *BatchCreateRequest) ([]Batch, *Response, error) + Delete(string, bool) (*Response, error) +} + +// Batch type +type Batch struct { + ID string `json:"id"` + ErrorMessages []string `json:"error_messages,omitempty"` + + // State may be 'failed' or 'completed' + State string `json:"state,omitempty"` + Quantity int32 `json:"quantity,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + Href string `json:"href,omitempty"` + Project Href `json:"project,omitempty"` + Devices []Device `json:"devices,omitempty"` +} + +//BatchesList represents collection of batches +type batchesList struct { + Batches []Batch `json:"batches,omitempty"` +} + +// BatchCreateRequest type used to create batch of device instances +type BatchCreateRequest struct { + Batches []BatchCreateDevice `json:"batches"` +} + +// BatchCreateDevice type used to describe batch instances +type BatchCreateDevice struct { + DeviceCreateRequest + Quantity int32 `json:"quantity"` + FacilityDiversityLevel int32 `json:"facility_diversity_level,omitempty"` + SpotInstance bool `json:"spot_instance,omitempty"` + SpotPriceMax float64 `json:"spot_price_max,omitempty"` +} + +// BatchServiceOp implements BatchService +type BatchServiceOp struct { + client *Client +} + +// Get returns batch details +func (s *BatchServiceOp) Get(batchID string, opts *GetOptions) (*Batch, *Response, error) { + endpointPath := path.Join(batchBasePath, batchID) + apiPathQuery := opts.WithQuery(endpointPath) + batch := new(Batch) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, batch) + if err != nil { + return nil, resp, err + } + + return batch, resp, err +} + +// List returns batches on a project +func (s *BatchServiceOp) List(projectID string, opts *ListOptions) (batches []Batch, resp *Response, err error) { + endpointPath := path.Join(projectBasePath, projectID, batchBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + subset := new(batchesList) + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + batches = append(batches, subset.Batches...) + return batches, resp, err +} + +// Create function to create batch of device instances +func (s *BatchServiceOp) Create(projectID string, request *BatchCreateRequest) ([]Batch, *Response, error) { + apiPath := path.Join(projectBasePath, projectID, "devices", "batch") + + batches := new(batchesList) + resp, err := s.client.DoRequest("POST", apiPath, request, batches) + + if err != nil { + return nil, resp, err + } + + return batches.Batches, resp, err +} + +// Delete function to remove an instance batch +func (s *BatchServiceOp) Delete(id string, removeDevices bool) (*Response, error) { + // API doc days the remove_associated_instances params shout be in the body + // https://metal.equinix.com/developers/api/batches/#delete-the-batch + // .. does this even work? + apiPath := fmt.Sprintf("%s/%s?remove_associated_instances=%t", batchBasePath, id, removeDevices) + + return s.client.DoRequest("DELETE", apiPath, nil, nil) +} diff --git a/provider/packet/vendor/github.com/packethost/packngo/bgp_configs.go b/provider/packet/vendor/github.com/packethost/packngo/bgp_configs.go new file mode 100644 index 00000000..95dc5b07 --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/bgp_configs.go @@ -0,0 +1,85 @@ +package packngo + +import ( + "path" +) + +var ( + bgpConfigPostBasePath = "/bgp-configs" + bgpConfigGetBasePath = "/bgp-config" +) + +// BGPConfigService interface defines available BGP config methods +type BGPConfigService interface { + Get(projectID string, getOpt *GetOptions) (*BGPConfig, *Response, error) + Create(projectID string, request CreateBGPConfigRequest) (*Response, error) + // Delete(configID string) (resp *Response, err error) TODO: Not in Equinix Metal API +} + +// BGPConfigServiceOp implements BgpConfigService +type BGPConfigServiceOp struct { + client *Client +} + +// CreateBGPConfigRequest struct +type CreateBGPConfigRequest struct { + DeploymentType string `json:"deployment_type,omitempty"` + Asn int `json:"asn,omitempty"` + Md5 string `json:"md5,omitempty"` + UseCase string `json:"use_case,omitempty"` +} + +// BGPConfig represents an Equinix Metal BGP Config +type BGPConfig struct { + ID string `json:"id,omitempty"` + Status string `json:"status,omitempty"` + DeploymentType string `json:"deployment_type,omitempty"` + Asn int `json:"asn,omitempty"` + RouteObject string `json:"route_object,omitempty"` + Md5 string `json:"md5,omitempty"` + MaxPrefix int `json:"max_prefix,omitempty"` + Project Project `json:"project,omitempty"` + CreatedAt Timestamp `json:"created_at,omitempty"` + RequestedAt Timestamp `json:"requested_at,omitempty"` + Sessions []BGPSession `json:"sessions,omitempty"` + Href string `json:"href,omitempty"` +} + +// Create function +func (s *BGPConfigServiceOp) Create(projectID string, request CreateBGPConfigRequest) (*Response, error) { + apiPath := path.Join(projectBasePath, projectID, bgpConfigPostBasePath) + + resp, err := s.client.DoRequest("POST", apiPath, request, nil) + if err != nil { + return resp, err + } + + return resp, err +} + +// Get function +func (s *BGPConfigServiceOp) Get(projectID string, opts *GetOptions) (bgpConfig *BGPConfig, resp *Response, err error) { + endpointPath := path.Join(projectBasePath, projectID, bgpConfigGetBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + + subset := new(BGPConfig) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + return subset, resp, err +} + +// Delete function TODO: this is not implemented in the Equinix Metal API +// func (s *BGPConfigServiceOp) Delete(configID string) (resp *Response, err error) { +// apiPath := fmt.Sprintf("%ss/%s", bgpConfigBasePath, configID) + +// resp, err = s.client.DoRequest("DELETE", apiPath, nil, nil) +// if err != nil { +// return resp, err +// } + +// return resp, err +// } diff --git a/provider/packet/vendor/github.com/packethost/packngo/bgp_sessions.go b/provider/packet/vendor/github.com/packethost/packngo/bgp_sessions.go new file mode 100644 index 00000000..0054a51e --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/bgp_sessions.go @@ -0,0 +1,99 @@ +package packngo + +import ( + "path" +) + +var bgpSessionBasePath = "/bgp/sessions" +var bgpNeighborsBasePath = "/bgp/neighbors" + +// BGPSessionService interface defines available BGP session methods +type BGPSessionService interface { + Get(string, *GetOptions) (*BGPSession, *Response, error) + Create(string, CreateBGPSessionRequest) (*BGPSession, *Response, error) + Delete(string) (*Response, error) +} + +type bgpSessionsRoot struct { + Sessions []BGPSession `json:"bgp_sessions"` + Meta meta `json:"meta"` +} + +// BGPSessionServiceOp implements BgpSessionService +type BGPSessionServiceOp struct { + client *Client +} + +// BGPSession represents an Equinix Metal BGP Session +type BGPSession struct { + ID string `json:"id,omitempty"` + Status string `json:"status,omitempty"` + LearnedRoutes []string `json:"learned_routes,omitempty"` + AddressFamily string `json:"address_family,omitempty"` + Device Device `json:"device,omitempty"` + Href string `json:"href,omitempty"` + DefaultRoute *bool `json:"default_route,omitempty"` +} + +type bgpNeighborsRoot struct { + BGPNeighbors []BGPNeighbor `json:"bgp_neighbors"` +} + +// BGPNeighor is struct for listing BGP neighbors of a device +type BGPNeighbor struct { + AddressFamily int `json:"address_family"` + CustomerAs int `json:"customer_as"` + CustomerIP string `json:"customer_ip"` + Md5Enabled bool `json:"md5_enabled"` + Md5Password string `json:"md5_password"` + Multihop bool `json:"multihop"` + PeerAs int `json:"peer_as"` + PeerIps []string `json:"peer_ips"` + RoutesIn []BGPRoute `json:"routes_in"` + RoutesOut []BGPRoute `json:"routes_out"` +} + +// BGPRoute is a struct for Route in BGP neighbor listing +type BGPRoute struct { + Route string `json:"route"` + Exact bool `json:"exact"` +} + +// CreateBGPSessionRequest struct +type CreateBGPSessionRequest struct { + AddressFamily string `json:"address_family"` + DefaultRoute *bool `json:"default_route,omitempty"` +} + +// Create function +func (s *BGPSessionServiceOp) Create(deviceID string, request CreateBGPSessionRequest) (*BGPSession, *Response, error) { + apiPath := path.Join(deviceBasePath, deviceID, bgpSessionBasePath) + session := new(BGPSession) + + resp, err := s.client.DoRequest("POST", apiPath, request, session) + if err != nil { + return nil, resp, err + } + + return session, resp, err +} + +// Delete function +func (s *BGPSessionServiceOp) Delete(id string) (*Response, error) { + apiPath := path.Join(bgpSessionBasePath, id) + + return s.client.DoRequest("DELETE", apiPath, nil, nil) +} + +// Get function +func (s *BGPSessionServiceOp) Get(id string, opts *GetOptions) (session *BGPSession, response *Response, err error) { + endpointPath := path.Join(bgpSessionBasePath, id) + apiPathQuery := opts.WithQuery(endpointPath) + session = new(BGPSession) + response, err = s.client.DoRequest("GET", apiPathQuery, nil, session) + if err != nil { + return nil, response, err + } + + return session, response, err +} diff --git a/provider/packet/vendor/github.com/packethost/packngo/capacities.go b/provider/packet/vendor/github.com/packethost/packngo/capacities.go new file mode 100644 index 00000000..45ffac60 --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/capacities.go @@ -0,0 +1,103 @@ +package packngo + +const ( + capacityBasePath = "/capacity" + capacityBasePathMetros = "/capacity/metros" +) + +// CapacityService interface defines available capacity methods +type CapacityService interface { + List() (*CapacityReport, *Response, error) + ListMetros() (*CapacityReport, *Response, error) + Check(*CapacityInput) (*CapacityInput, *Response, error) + CheckMetros(*CapacityInput) (*CapacityInput, *Response, error) +} + +// CapacityInput struct +type CapacityInput struct { + Servers []ServerInfo `json:"servers,omitempty"` +} + +// ServerInfo struct +type ServerInfo struct { + Facility string `json:"facility,omitempty"` + Metro string `json:"metro,omitempty"` + Plan string `json:"plan,omitempty"` + Quantity int `json:"quantity,omitempty"` + Available bool `json:"available,omitempty"` +} + +type capacityRoot struct { + Capacity CapacityReport `json:"capacity,omitempty"` +} + +// CapacityReport map +type CapacityReport map[string]map[string]CapacityPerBaremetal + +// // CapacityPerFacility struct +// type CapacityPerFacility struct { +// T1SmallX86 *CapacityPerBaremetal `json:"t1.small.x86,omitempty"` +// C1SmallX86 *CapacityPerBaremetal `json:"c1.small.x86,omitempty"` +// M1XlargeX86 *CapacityPerBaremetal `json:"m1.xlarge.x86,omitempty"` +// C1XlargeX86 *CapacityPerBaremetal `json:"c1.xlarge.x86,omitempty"` + +// Baremetal0 *CapacityPerBaremetal `json:"baremetal_0,omitempty"` +// Baremetal1 *CapacityPerBaremetal `json:"baremetal_1,omitempty"` +// Baremetal1e *CapacityPerBaremetal `json:"baremetal_1e,omitempty"` +// Baremetal2 *CapacityPerBaremetal `json:"baremetal_2,omitempty"` +// Baremetal2a *CapacityPerBaremetal `json:"baremetal_2a,omitempty"` +// Baremetal2a2 *CapacityPerBaremetal `json:"baremetal_2a2,omitempty"` +// Baremetal3 *CapacityPerBaremetal `json:"baremetal_3,omitempty"` +// } + +// CapacityPerBaremetal struct +type CapacityPerBaremetal struct { + Level string `json:"level,omitempty"` +} + +// CapacityList struct +type CapacityList struct { + Capacity CapacityReport `json:"capacity,omitempty"` +} + +// CapacityServiceOp implements CapacityService +type CapacityServiceOp struct { + client *Client +} + +func capacityList(client *Client, capUrl string) (*CapacityReport, *Response, error) { + root := new(capacityRoot) + + resp, err := client.DoRequest("GET", capUrl, nil, root) + if err != nil { + return nil, resp, err + } + + return &root.Capacity, nil, nil +} + +// List returns a list of facilities and plans with their current capacity. +func (s *CapacityServiceOp) List() (*CapacityReport, *Response, error) { + return capacityList(s.client, capacityBasePath) +} + +// ListMetros returns a list of metros and plans with their current capacity. +func (s *CapacityServiceOp) ListMetros() (*CapacityReport, *Response, error) { + return capacityList(s.client, capacityBasePathMetros) +} + +func checkCapacity(client *Client, input *CapacityInput, capUrl string) (capInput *CapacityInput, resp *Response, err error) { + capInput = new(CapacityInput) + resp, err = client.DoRequest("POST", capUrl, input, capInput) + return capInput, resp, err +} + +// Check validates if a deploy can be fulfilled in a capacity. +func (s *CapacityServiceOp) Check(input *CapacityInput) (capInput *CapacityInput, resp *Response, err error) { + return checkCapacity(s.client, input, capacityBasePath) +} + +// Check validates if a deploy can be fulfilled in a metro. +func (s *CapacityServiceOp) CheckMetros(input *CapacityInput) (capInput *CapacityInput, resp *Response, err error) { + return checkCapacity(s.client, input, capacityBasePathMetros) +} diff --git a/provider/packet/vendor/github.com/packethost/packngo/code-of-conduct.md b/provider/packet/vendor/github.com/packethost/packngo/code-of-conduct.md new file mode 100644 index 00000000..3a14e528 --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/code-of-conduct.md @@ -0,0 +1,3 @@ +# Code Of Conduct + +Please refer to the [Contributor Covenant](https://www.contributor-covenant.org/version/1/2/0/code-of-conduct/). diff --git a/provider/packet/vendor/github.com/packethost/packngo/connections.go b/provider/packet/vendor/github.com/packethost/packngo/connections.go new file mode 100644 index 00000000..e489d845 --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/connections.go @@ -0,0 +1,218 @@ +package packngo + +import ( + "path" +) + +type ConnectionRedundancy string +type ConnectionType string +type ConnectionPortRole string + +const ( + connectionBasePath = "/connections" + ConnectionShared ConnectionType = "shared" + ConnectionDedicated ConnectionType = "dedicated" + ConnectionRedundant ConnectionRedundancy = "redundant" + ConnectionPrimary ConnectionRedundancy = "primary" + ConnectionPortPrimary ConnectionPortRole = "primary" + ConnectionPortSecondary ConnectionPortRole = "secondary" +) + +type ConnectionService interface { + OrganizationCreate(string, *ConnectionCreateRequest) (*Connection, *Response, error) + ProjectCreate(string, *ConnectionCreateRequest) (*Connection, *Response, error) + OrganizationList(string, *GetOptions) ([]Connection, *Response, error) + ProjectList(string, *GetOptions) ([]Connection, *Response, error) + Delete(string) (*Response, error) + Get(string, *GetOptions) (*Connection, *Response, error) + Events(string, *GetOptions) ([]Event, *Response, error) + PortEvents(string, string, *GetOptions) ([]Event, *Response, error) + Ports(string, *GetOptions) ([]ConnectionPort, *Response, error) + Port(string, string, *GetOptions) (*ConnectionPort, *Response, error) + VirtualCircuits(string, string, *GetOptions) ([]VirtualCircuit, *Response, error) +} + +type ConnectionServiceOp struct { + client *Client +} + +type connectionPortsRoot struct { + Ports []ConnectionPort `json:"ports"` +} + +type connectionsRoot struct { + Connections []Connection `json:"interconnections"` + Meta meta `json:"meta"` +} + +type ConnectionPort struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Status string `json:"status,omitempty"` + Role ConnectionPortRole `json:"role,omitempty"` + Speed int `json:"speed,omitempty"` + Organization *Organization `json:"organization,omitempty"` + VirtualCircuits []VirtualCircuit `json:"virtual_circuits,omitempty"` + LinkStatus string `json:"link_status,omitempty"` + Href string `json:"href,omitempty"` +} + +type Connection struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Status string `json:"status,omitempty"` + Redundancy ConnectionRedundancy `json:"redundancy,omitempty"` + Facility *Facility `json:"facility,omitempty"` + Metro *Metro `json:"metro,omitempty"` + Type ConnectionType `json:"type,omitempty"` + Description string `json:"description,omitempty"` + Project *Project `json:"project,omitempty"` + Organization *Organization `json:"organization,omitempty"` + Speed int `json:"speed,omitempty"` + Token string `json:"token,omitempty"` + Tags []string `json:"tags,omitempty"` + Ports []ConnectionPort `json:"ports,omitempty"` +} + +type ConnectionCreateRequest struct { + Name string `json:"name,omitempty"` + Redundancy ConnectionRedundancy `json:"redundancy,omitempty"` + Facility string `json:"facility,omitempty"` + Metro string `json:"metro,omitempty"` + Type ConnectionType `json:"type,omitempty"` + Description *string `json:"description,omitempty"` + Project string `json:"project,omitempty"` + Speed int `json:"speed,omitempty"` + Tags []string `json:"tags,omitempty"` +} + +func (c *Connection) PortByRole(r ConnectionPortRole) *ConnectionPort { + for _, p := range c.Ports { + if p.Role == r { + return &p + } + } + return nil +} + +func (s *ConnectionServiceOp) create(apiUrl string, createRequest *ConnectionCreateRequest) (*Connection, *Response, error) { + connection := new(Connection) + resp, err := s.client.DoRequest("POST", apiUrl, createRequest, connection) + if err != nil { + return nil, resp, err + } + + return connection, resp, err +} + +func (s *ConnectionServiceOp) OrganizationCreate(id string, createRequest *ConnectionCreateRequest) (*Connection, *Response, error) { + apiUrl := path.Join(organizationBasePath, id, connectionBasePath) + return s.create(apiUrl, createRequest) +} + +func (s *ConnectionServiceOp) ProjectCreate(id string, createRequest *ConnectionCreateRequest) (*Connection, *Response, error) { + apiUrl := path.Join(projectBasePath, id, connectionBasePath) + return s.create(apiUrl, createRequest) +} + +func (s *ConnectionServiceOp) list(url string, opts *GetOptions) (connections []Connection, resp *Response, err error) { + apiPathQuery := opts.WithQuery(url) + + for { + subset := new(connectionsRoot) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + connections = append(connections, subset.Connections...) + + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + + return + } + +} + +func (s *ConnectionServiceOp) OrganizationList(id string, opts *GetOptions) ([]Connection, *Response, error) { + apiUrl := path.Join(organizationBasePath, id, connectionBasePath) + return s.list(apiUrl, opts) +} + +func (s *ConnectionServiceOp) ProjectList(id string, opts *GetOptions) ([]Connection, *Response, error) { + apiUrl := path.Join(projectBasePath, id, connectionBasePath) + return s.list(apiUrl, opts) +} + +func (s *ConnectionServiceOp) Delete(id string) (*Response, error) { + apiPath := path.Join(connectionBasePath, id) + return s.client.DoRequest("DELETE", apiPath, nil, nil) +} + +func (s *ConnectionServiceOp) Port(connID, portID string, opts *GetOptions) (*ConnectionPort, *Response, error) { + endpointPath := path.Join(connectionBasePath, connID, portBasePath, portID) + apiPathQuery := opts.WithQuery(endpointPath) + port := new(ConnectionPort) + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, port) + if err != nil { + return nil, resp, err + } + return port, resp, err +} + +func (s *ConnectionServiceOp) Get(id string, opts *GetOptions) (*Connection, *Response, error) { + endpointPath := path.Join(connectionBasePath, id) + apiPathQuery := opts.WithQuery(endpointPath) + connection := new(Connection) + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, connection) + if err != nil { + return nil, resp, err + } + return connection, resp, err +} + +func (s *ConnectionServiceOp) Ports(connID string, opts *GetOptions) ([]ConnectionPort, *Response, error) { + endpointPath := path.Join(connectionBasePath, connID, portBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + ports := new(connectionPortsRoot) + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, ports) + if err != nil { + return nil, resp, err + } + return ports.Ports, resp, nil + +} + +func (s *ConnectionServiceOp) Events(id string, opts *GetOptions) ([]Event, *Response, error) { + apiPath := path.Join(connectionBasePath, id, eventBasePath) + return listEvents(s.client, apiPath, opts) +} + +func (s *ConnectionServiceOp) PortEvents(connID, portID string, opts *GetOptions) ([]Event, *Response, error) { + apiPath := path.Join(connectionBasePath, connID, portBasePath, portID, eventBasePath) + return listEvents(s.client, apiPath, opts) +} + +func (s *ConnectionServiceOp) VirtualCircuits(connID, portID string, opts *GetOptions) (vcs []VirtualCircuit, resp *Response, err error) { + endpointPath := path.Join(connectionBasePath, connID, portBasePath, portID, virtualCircuitBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + for { + subset := new(virtualCircuitsRoot) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + vcs = append(vcs, subset.VirtualCircuits...) + + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + + return + } +} diff --git a/provider/packet/vendor/github.com/packethost/packngo/device_ports.go b/provider/packet/vendor/github.com/packethost/packngo/device_ports.go new file mode 100644 index 00000000..0b02acfd --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/device_ports.go @@ -0,0 +1,323 @@ +package packngo + +import ( + "fmt" + "strings" +) + +const portBasePath = "/ports" + +// DevicePortService handles operations on a port which belongs to a particular device +// +// Deprecated: use PortService or Device methods +type DevicePortService interface { + Assign(*PortAssignRequest) (*Port, *Response, error) + Unassign(*PortAssignRequest) (*Port, *Response, error) + AssignNative(*PortAssignRequest) (*Port, *Response, error) + UnassignNative(string) (*Port, *Response, error) + Bond(*Port, bool) (*Port, *Response, error) + Disbond(*Port, bool) (*Port, *Response, error) + DeviceToNetworkType(string, string) (*Device, error) + DeviceNetworkType(string) (string, error) + PortToLayerTwo(string, string) (*Port, *Response, error) + PortToLayerThree(string, string) (*Port, *Response, error) + GetPortByName(string, string) (*Port, error) + GetOddEthPorts(*Device) (map[string]*Port, error) + GetAllEthPorts(*Device) (map[string]*Port, error) + ConvertDevice(*Device, string) error +} + +// DevicePortServiceOp implements DevicePortService on the Equinix Metal API +// +// Deprecated: use PortServiceOp or Device methods +type DevicePortServiceOp struct { + client *Client +} + +// GetPortByName returns the matching Port on the specified device +// +// Deprecated: use Device.GetPortByName +func (i *DevicePortServiceOp) GetPortByName(deviceID, name string) (*Port, error) { + device, _, err := i.client.Devices.Get(deviceID, nil) + if err != nil { + return nil, err + } + return device.GetPortByName(name) +} + +// Assign the specified VLAN to the specified Port +// +// Deprecated: use PortServiceOp.Assign +func (i *DevicePortServiceOp) Assign(par *PortAssignRequest) (*Port, *Response, error) { + return i.client.Ports.Assign(par.PortID, par.VirtualNetworkID) +} + +// AssignNative designates the specified VLAN as the native VLAN for the +// specified Port +// +// Deprecated: use PortServiceOp.AssignNative +func (i *DevicePortServiceOp) AssignNative(par *PortAssignRequest) (*Port, *Response, error) { + return i.client.Ports.AssignNative(par.PortID, par.VirtualNetworkID) +} + +// UnassignNative removes the native VLAN from the specified Port +// +// Deprecated: use PortServiceOp.UnassignNative +func (i *DevicePortServiceOp) UnassignNative(portID string) (*Port, *Response, error) { + return i.client.Ports.UnassignNative(portID) +} + +// Unassign removes the specified VLAN from the specified Port +// +// Deprecated: use PortServiceOp.Unassign +func (i *DevicePortServiceOp) Unassign(par *PortAssignRequest) (*Port, *Response, error) { + return i.client.Ports.Unassign(par.PortID, par.VirtualNetworkID) +} + +// Bond enabled bonding on the specified port +// +// Deprecated: use PortServiceOp.Bond +func (i *DevicePortServiceOp) Bond(p *Port, bulk_enable bool) (*Port, *Response, error) { + if p.Data.Bonded { + return p, nil, nil + } + + return i.client.Ports.Bond(p.ID, bulk_enable) +} + +// Disbond disables bonding on the specified port +// +// Deprecated: use PortServiceOp.Disbond +func (i *DevicePortServiceOp) Disbond(p *Port, bulk_disable bool) (*Port, *Response, error) { + if !p.Data.Bonded { + return p, nil, nil + } + return i.client.Ports.Disbond(p.ID, bulk_disable) +} + +// PortToLayerTwo fetches the specified device, finds the matching port by name, +// and converts it to layer2. A port may already be in a layer2 mode, in which +// case the port will be returned with a nil response and nil error with no +// additional action taking place. +// +// Deprecated: use PortServiceOp.ConvertToLayerTwo +func (i *DevicePortServiceOp) PortToLayerTwo(deviceID, portName string) (*Port, *Response, error) { + p, err := i.GetPortByName(deviceID, portName) + if err != nil { + return nil, nil, err + } + if strings.HasPrefix(p.NetworkType, "layer2") { + return p, nil, nil + } + + return i.client.Ports.ConvertToLayerTwo(p.ID) +} + +// PortToLayerThree fetches the specified device, finds the matching port by +// name, and converts it to layer3. A port may already be in a layer3 mode, in +// which case the port will be returned with a nil response and nil error with +// no additional action taking place. +// +// When switching to Layer3, a new set of IP addresses will be requested +// including Public IPv4, Public IPv6, and Private IPv6 addresses. +// +// Deprecated: use PortServiceOp.ConvertToLayerTwo +func (i *DevicePortServiceOp) PortToLayerThree(deviceID, portName string) (*Port, *Response, error) { + p, err := i.GetPortByName(deviceID, portName) + if err != nil { + return nil, nil, err + } + if (p.NetworkType == NetworkTypeL3) || (p.NetworkType == NetworkTypeHybrid) { + return p, nil, nil + } + + ips := []AddressRequest{ + {AddressFamily: 4, Public: true}, + {AddressFamily: 4, Public: false}, + {AddressFamily: 6, Public: true}, + } + + return i.client.Ports.ConvertToLayerThree(p.ID, ips) +} + +// DeviceNetworkType fetches the specified Device and returns a heuristic single +// word network type consistent with the Equinix Metal console experience. +// +// Deprecated: use Device.GetNetworkType +func (i *DevicePortServiceOp) DeviceNetworkType(deviceID string) (string, error) { + d, _, err := i.client.Devices.Get(deviceID, nil) + if err != nil { + return "", err + } + return d.GetNetworkType(), nil +} + +// GetAllEthPorts fetches the specified Device and returns a heuristic single +// word network type consistent with the Equinix Metal console experience. +// +// Deprecated: use Device.GetPhysicalPorts +func (i *DevicePortServiceOp) GetAllEthPorts(d *Device) (map[string]*Port, error) { + d, _, err := i.client.Devices.Get(d.ID, nil) + if err != nil { + return nil, err + } + return d.GetPhysicalPorts(), nil +} + +// GetOddEthPorts fetches the specified Device and returns physical +// ports eth1 and eth3. +// +// Deprecated: use Device.GetPhysicalPorts and filter the map to only the keys +// ending with odd digits +func (i *DevicePortServiceOp) GetOddEthPorts(d *Device) (map[string]*Port, error) { + d, _, err := i.client.Devices.Get(d.ID, nil) + if err != nil { + return nil, err + } + ret := map[string]*Port{} + eth1, err := d.GetPortByName("eth1") + if err != nil { + return nil, err + } + ret["eth1"] = eth1 + + eth3, err := d.GetPortByName("eth3") + if err != nil { + return ret, nil + } + ret["eth3"] = eth3 + return ret, nil + +} + +// ConvertDevice converts the specified device's network ports (including +// addresses and vlans) to the named network type, consistent with the Equinix +// Metal console experience. +// +// Deprecated: Equinix Metal devices may support more than two ports and the +// whole-device single word network type can no longer capture the capabilities +// and permutations of device port configurations. +func (i *DevicePortServiceOp) ConvertDevice(d *Device, targetType string) error { + bondPorts := d.GetBondPorts() + + if targetType == NetworkTypeL3 { + // TODO: remove vlans from all the ports + for _, p := range bondPorts { + _, _, err := i.Bond(p, false) + if err != nil { + return err + } + } + _, _, err := i.PortToLayerThree(d.ID, "bond0") + if err != nil { + return err + } + allEthPorts, err := i.GetAllEthPorts(d) + if err != nil { + return err + } + for _, p := range allEthPorts { + _, _, err := i.Bond(p, false) + if err != nil { + return err + } + } + } + if targetType == NetworkTypeHybrid { + for _, p := range bondPorts { + _, _, err := i.Bond(p, false) + if err != nil { + return err + } + } + + _, _, err := i.PortToLayerThree(d.ID, "bond0") + if err != nil { + return err + } + + // ports need to be refreshed before bonding/disbonding + oddEthPorts, err := i.GetOddEthPorts(d) + if err != nil { + return err + } + + for _, p := range oddEthPorts { + _, _, err := i.Disbond(p, false) + if err != nil { + return err + } + } + } + if targetType == NetworkTypeL2Individual { + _, _, err := i.PortToLayerTwo(d.ID, "bond0") + if err != nil { + return err + } + for _, p := range bondPorts { + _, _, err = i.Disbond(p, true) + if err != nil { + return err + } + } + } + if targetType == NetworkTypeL2Bonded { + + for _, p := range bondPorts { + _, _, err := i.PortToLayerTwo(d.ID, p.Name) + if err != nil { + return err + } + } + allEthPorts, err := i.GetAllEthPorts(d) + if err != nil { + return err + } + for _, p := range allEthPorts { + _, _, err := i.Bond(p, false) + if err != nil { + return err + } + } + } + return nil +} + +// DeviceToNetworkType fetches the specified device and converts its network +// ports (including addresses and vlans) to the named network type, consistent +// with the Equinix Metal console experience. +// +// Deprecated: use DevicePortServiceOp.ConvertDevice which this function thinly +// wraps. +func (i *DevicePortServiceOp) DeviceToNetworkType(deviceID string, targetType string) (*Device, error) { + d, _, err := i.client.Devices.Get(deviceID, nil) + if err != nil { + return nil, err + } + + curType := d.GetNetworkType() + + if curType == targetType { + return nil, fmt.Errorf("Device already is in state %s", targetType) + } + err = i.ConvertDevice(d, targetType) + if err != nil { + return nil, err + } + + d, _, err = i.client.Devices.Get(deviceID, nil) + + if err != nil { + return nil, err + } + + finalType := d.GetNetworkType() + + if finalType != targetType { + return nil, fmt.Errorf( + "Failed to convert device %s from %s to %s. New type was %s", + deviceID, curType, targetType, finalType) + + } + return d, err +} diff --git a/provider/packet/vendor/github.com/packethost/packngo/devices.go b/provider/packet/vendor/github.com/packethost/packngo/devices.go index b41bcf07..d73aee3d 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/devices.go +++ b/provider/packet/vendor/github.com/packethost/packngo/devices.go @@ -1,25 +1,38 @@ package packngo import ( + "encoding/json" "fmt" - "strings" + "net/url" + "path" + "strconv" ) const deviceBasePath = "/devices" +const ( + NetworkTypeHybrid = "hybrid" + NetworkTypeL2Bonded = "layer2-bonded" + NetworkTypeL2Individual = "layer2-individual" + NetworkTypeL3 = "layer3" +) + // DeviceService interface defines available device methods type DeviceService interface { - List(ProjectID string, listOpt *ListOptions) ([]Device, *Response, error) - Get(string) (*Device, *Response, error) - GetExtra(deviceID string, includes, excludes []string) (*Device, *Response, error) + List(ProjectID string, opts *ListOptions) ([]Device, *Response, error) + Get(DeviceID string, opts *GetOptions) (*Device, *Response, error) Create(*DeviceCreateRequest) (*Device, *Response, error) Update(string, *DeviceUpdateRequest) (*Device, *Response, error) - Delete(string) (*Response, error) + Delete(string, bool) (*Response, error) Reboot(string) (*Response, error) PowerOff(string) (*Response, error) PowerOn(string) (*Response, error) Lock(string) (*Response, error) Unlock(string) (*Response, error) + ListBGPSessions(deviceID string, opts *ListOptions) ([]BGPSession, *Response, error) + ListBGPNeighbors(deviceID string, opts *ListOptions) ([]BGPNeighbor, *Response, error) + ListEvents(deviceID string, opts *ListOptions) ([]Event, *Response, error) + GetBandwidth(deviceID string, opts *BandwidthOpts) (*BandwidthIO, *Response, error) } type devicesRoot struct { @@ -27,64 +40,356 @@ type devicesRoot struct { Meta meta `json:"meta"` } -// Device represents a Packet device +// Device represents an Equinix Metal device from API type Device struct { ID string `json:"id"` Href string `json:"href,omitempty"` Hostname string `json:"hostname,omitempty"` + Description *string `json:"description,omitempty"` State string `json:"state,omitempty"` Created string `json:"created_at,omitempty"` Updated string `json:"updated_at,omitempty"` Locked bool `json:"locked,omitempty"` BillingCycle string `json:"billing_cycle,omitempty"` - Storage map[string]interface{} `json:"storage,omitempty"` + Storage *CPR `json:"storage,omitempty"` Tags []string `json:"tags,omitempty"` Network []*IPAddressAssignment `json:"ip_addresses"` Volumes []*Volume `json:"volumes"` OS *OS `json:"operating_system,omitempty"` Plan *Plan `json:"plan,omitempty"` Facility *Facility `json:"facility,omitempty"` + Metro *Metro `json:"metro,omitempty"` Project *Project `json:"project,omitempty"` - ProvisionEvents []*ProvisionEvent `json:"provisioning_events,omitempty"` + ProvisionEvents []*Event `json:"provisioning_events,omitempty"` ProvisionPer float32 `json:"provisioning_percentage,omitempty"` UserData string `json:"userdata,omitempty"` + User string `json:"user,omitempty"` RootPassword string `json:"root_password,omitempty"` IPXEScriptURL string `json:"ipxe_script_url,omitempty"` AlwaysPXE bool `json:"always_pxe,omitempty"` - HardwareReservation Href `json:"hardware_reservation,omitempty"` + HardwareReservation *HardwareReservation `json:"hardware_reservation,omitempty"` SpotInstance bool `json:"spot_instance,omitempty"` SpotPriceMax float64 `json:"spot_price_max,omitempty"` TerminationTime *Timestamp `json:"termination_time,omitempty"` NetworkPorts []Port `json:"network_ports,omitempty"` CustomData map[string]interface{} `json:"customdata,omitempty"` + SSHKeys []SSHKey `json:"ssh_keys,omitempty"` + ShortID string `json:"short_id,omitempty"` + SwitchUUID string `json:"switch_uuid,omitempty"` } -type ProvisionEvent struct { - ID string `json:"id"` - Body string `json:"body"` - CreatedAt *Timestamp `json:"created_at,omitempty"` - Href string `json:"href"` - Interpolated string `json:"interpolated"` - Relationships []Href `json:"relationships"` - State string `json:"state"` - Type string `json:"type"` +type NetworkInfo struct { + PublicIPv4 string + PublicIPv6 string + PrivateIPv4 string +} + +type BandwidthIO struct { + Inbound BandwidthComponent `json:"inbound"` + Outbound BandwidthComponent `json:"outbound"` +} + +func (b *BandwidthIO) UnmarshalJSON(buf []byte) error { + tmp := []interface{}{&b.Inbound, &b.Outbound} + wantLen := len(tmp) + if err := json.Unmarshal(buf, &tmp); err != nil { + return err + } + if g, e := len(tmp), wantLen; g != e { + return fmt.Errorf("wrong number of fields in BandwidthIO: %d != %d", g, e) + } + if b.Inbound.Target == BandwidthOutbound { + b.Inbound, b.Outbound = b.Outbound, b.Inbound + } + return nil +} + +type bandwidthRoot struct { + Bandwidth BandwidthIO `json:"bandwidth"` +} + +type BandwidthTarget string + +// BandwidthTarget enums +const ( + BandwidthInbound BandwidthTarget = "inbound" + BandwidthOutbound BandwidthTarget = "outbound" +) + +// BandwidthTags +type BandwidthTags struct { + // AggregatedBy + AggregatedBy string `json:"aggregatedBy"` + + // Name + Name string `json:"name"` +} + +// BandwidthComponent +type BandwidthComponent struct { + // Datapoints + Datapoints []Datapoint `json:"datapoints"` + + // Tags + Tags BandwidthTags `json:"tags"` + + // Target + Target BandwidthTarget `json:"target"` +} +type Datapoint struct { + // Rate is the aggregated sum of Bytes/Second across all ports + Rate *float64 `json:"rate"` + + // When the rate was captured + When Timestamp `json:"when"` +} + +func (d *Datapoint) UnmarshalJSON(buf []byte) error { + tmp := []interface{}{&d.Rate, &d.When} + wantLen := len(tmp) + if err := json.Unmarshal(buf, &tmp); err != nil { + return err + } + if g, e := len(tmp), wantLen; g != e { + return fmt.Errorf("wrong number of fields in BandwidthComponent: %d != %d", g, e) + } + return nil +} + +type BandwidthOpts struct { + From *Timestamp `json:"from,omitempty"` + Until *Timestamp `json:"until,omitempty"` +} + +func (b *BandwidthOpts) Encode() string { + if b == nil { + return "" + } + v := url.Values{} + if b.From != nil { + v.Add("from", strconv.FormatInt(b.From.UTC().Unix(), 10)) + } + if b.Until != nil { + v.Add("until", strconv.FormatInt(b.Until.UTC().Unix(), 10)) + } + return v.Encode() +} + +func (b *BandwidthOpts) WithQuery(apiPath string) string { + params := b.Encode() + if params != "" { + // parse path, take existing vars + return fmt.Sprintf("%s?%s", apiPath, params) + } + return apiPath +} + +func (d *DeviceServiceOp) GetBandwidth(deviceID string, opts *BandwidthOpts) (*BandwidthIO, *Response, error) { + endpointPath := path.Join(deviceBasePath, deviceID, "bandwidth") + apiPathQuery := opts.WithQuery(endpointPath) + bw := new(bandwidthRoot) + resp, err := d.client.DoRequest("GET", apiPathQuery, nil, bw) + if err != nil { + return nil, resp, err + } + return &bw.Bandwidth, resp, nil +} + +func (d *Device) GetNetworkInfo() NetworkInfo { + ni := NetworkInfo{} + for _, ip := range d.Network { + // Initial device IPs are fixed and marked as "Management" + if ip.Management { + if ip.AddressFamily == 4 { + if ip.Public { + ni.PublicIPv4 = ip.Address + } else { + ni.PrivateIPv4 = ip.Address + } + } else { + ni.PublicIPv6 = ip.Address + } + } + } + return ni } func (d Device) String() string { return Stringify(d) } -// DeviceCreateRequest type used to create a Packet device +func (d *Device) NumOfBonds() int { + numOfBonds := 0 + for _, p := range d.NetworkPorts { + if p.Type == "NetworkBondPort" { + numOfBonds++ + } + } + return numOfBonds +} + +func (d *Device) GetPortsInBond(name string) map[string]*Port { + ports := map[string]*Port{} + for _, port := range d.NetworkPorts { + if port.Bond != nil && port.Bond.Name == name { + p := port + ports[p.Name] = &p + } + } + return ports +} + +func (d *Device) GetBondPorts() map[string]*Port { + ports := map[string]*Port{} + for _, port := range d.NetworkPorts { + if port.Type == "NetworkBondPort" { + p := port + ports[p.Name] = &p + } + } + return ports +} + +func (d *Device) GetPhysicalPorts() map[string]*Port { + ports := map[string]*Port{} + for _, port := range d.NetworkPorts { + if port.Type == "NetworkPort" { + p := port + ports[p.Name] = &p + } + } + return ports +} + +func (d *Device) GetPortByName(name string) (*Port, error) { + for _, port := range d.NetworkPorts { + if port.Name == name { + return &port, nil + } + } + return nil, fmt.Errorf("Port %s not found in device %s", name, d.ID) +} + +type ports map[string]*Port + +func (ports ports) allBonded() bool { + if ports == nil { + return false + } + + if len(ports) == 0 { + return false + } + + for _, p := range ports { + if (p == nil) || (!p.Data.Bonded) { + return false + } + } + return true +} + +func (d *Device) HasManagementIPs() bool { + for _, ip := range d.Network { + if ip.Management { + return true + } + } + return false +} + +// GetNetworkType returns a composite network type identification for a device +// based on the plan, network_type, and IP management state of the device. +// GetNetworkType provides the same composite state rendered in the Packet +// Portal for a given device. +func (d *Device) GetNetworkType() string { + if d.Plan != nil { + if d.Plan.Slug == "baremetal_0" || d.Plan.Slug == "baremetal_1" { + return NetworkTypeL3 + } + if d.Plan.Slug == "baremetal_1e" { + return NetworkTypeHybrid + } + } + + bonds := ports(d.GetBondPorts()) + phys := ports(d.GetPhysicalPorts()) + + if bonds.allBonded() { + if phys.allBonded() { + if !d.HasManagementIPs() { + return NetworkTypeL2Bonded + } + return NetworkTypeL3 + } + return NetworkTypeHybrid + } + return NetworkTypeL2Individual +} + +type IPAddressCreateRequest struct { + // Address Family for IP Address + AddressFamily int `json:"address_family"` + + // Address Type for IP Address + Public bool `json:"public"` + + // CIDR Size for the IP Block created. Valid values depends on the operating system provisioned. + CIDR int `json:"cidr,omitempty"` + + // Reservations are UUIDs of any IP reservations to use when assigning IPs + Reservations []string `json:"ip_reservations,omitempty"` +} + +// CPR is a struct for custom partitioning and RAID +// If you don't want to bother writing the struct, just write the CPR conf to +// a string and then do +// +// var cpr CPR +// err := json.Unmarshal([]byte(cprString), &cpr) +// if err != nil { +// log.Fatal(err) +// } +type CPR struct { + Disks []struct { + Device string `json:"device"` + WipeTable bool `json:"wipeTable"` + Partitions []struct { + Label string `json:"label"` + Number int `json:"number"` + Size string `json:"size"` + } `json:"partitions"` + } `json:"disks"` + Raid []struct { + Devices []string `json:"devices"` + Level string `json:"level"` + Name string `json:"name"` + } `json:"raid,omitempty"` + Filesystems []struct { + Mount struct { + Device string `json:"device"` + Format string `json:"format"` + Point string `json:"point"` + Create struct { + Options []string `json:"options"` + } `json:"create"` + } `json:"mount"` + } `json:"filesystems"` +} + +// DeviceCreateRequest type used to create an Equinix Metal device type DeviceCreateRequest struct { Hostname string `json:"hostname"` Plan string `json:"plan"` - Facility string `json:"facility"` + Facility []string `json:"facility,omitempty"` + Metro string `json:"metro,omitempty"` OS string `json:"operating_system"` BillingCycle string `json:"billing_cycle"` ProjectID string `json:"project_id"` UserData string `json:"userdata"` - Storage string `json:"storage,omitempty"` + Storage *CPR `json:"storage,omitempty"` Tags []string `json:"tags"` + Description string `json:"description,omitempty"` IPXEScriptURL string `json:"ipxe_script_url,omitempty"` PublicIPv4SubnetSize int `json:"public_ipv4_subnet_size,omitempty"` AlwaysPXE bool `json:"always_pxe,omitempty"` @@ -93,9 +398,21 @@ type DeviceCreateRequest struct { SpotPriceMax float64 `json:"spot_price_max,omitempty,string"` TerminationTime *Timestamp `json:"termination_time,omitempty"` CustomData string `json:"customdata,omitempty"` + // UserSSHKeys is a list of user UUIDs - essentially a list of + // collaborators. The users must be a collaborator in the same project + // where the device is created. The user's SSH keys then go to the + // device + UserSSHKeys []string `json:"user_ssh_keys,omitempty"` + // Project SSHKeys is a list of SSHKeys resource UUIDs. If this param + // is supplied, only the listed SSHKeys will go to the device. + // Any other Project SSHKeys and any User SSHKeys will not be present + // in the device. + ProjectSSHKeys []string `json:"project_ssh_keys,omitempty"` + Features map[string]string `json:"features,omitempty"` + IPAddresses []IPAddressCreateRequest `json:"ip_addresses,omitempty"` } -// DeviceUpdateRequest type used to update a Packet device +// DeviceUpdateRequest type used to update an Equinix Metal device type DeviceUpdateRequest struct { Hostname *string `json:"hostname,omitempty"` Description *string `json:"description,omitempty"` @@ -116,6 +433,10 @@ type DeviceActionRequest struct { Type string `json:"type"` } +type DeviceDeleteRequest struct { + Force bool `json:"force_delete"` +} + func (d DeviceActionRequest) String() string { return Stringify(d) } @@ -126,28 +447,31 @@ type DeviceServiceOp struct { } // List returns devices on a project -func (s *DeviceServiceOp) List(projectID string, listOpt *ListOptions) (devices []Device, resp *Response, err error) { - params := "include=facility" - if listOpt != nil { - params = listOpt.createURL() - } - path := fmt.Sprintf("%s/%s%s?%s", projectBasePath, projectID, deviceBasePath, params) +// +// Regarding ListOptions.Search: The API documentation does not provide guidance +// on the fields that will be searched using this parameter, so this behavior is +// undefined and prone to change. +// +// As of 2020-10-20, ListOptions.Search will look for matches in the following +// Device properties: Hostname, Description, Tags, ID, ShortID, Network.Address, +// Plan.Name, Plan.Slug, Facility.Code, Facility.Name, OS.Name, OS.Slug, +// HardwareReservation.ID, HardwareReservation.ShortID +func (s *DeviceServiceOp) List(projectID string, opts *ListOptions) (devices []Device, resp *Response, err error) { + opts = opts.Including("facility") + endpointPath := path.Join(projectBasePath, projectID, deviceBasePath) + apiPathQuery := opts.WithQuery(endpointPath) for { subset := new(devicesRoot) - resp, err = s.client.DoRequest("GET", path, nil, subset) + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) if err != nil { return nil, resp, err } devices = append(devices, subset.Devices...) - if subset.Meta.Next != nil && (listOpt == nil || listOpt.Page == 0) { - path = subset.Meta.Next.Href - if params != "" { - path = fmt.Sprintf("%s&%s", path, params) - } + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { continue } @@ -156,48 +480,39 @@ func (s *DeviceServiceOp) List(projectID string, listOpt *ListOptions) (devices } // Get returns a device by id -func (s *DeviceServiceOp) Get(deviceID string) (*Device, *Response, error) { - return s.GetExtra(deviceID, []string{"facility"}, nil) -} - -// GetExtra returns a device by id. Specifying either includes/excludes provides more or less desired -// detailed information about resources which would otherwise be represented with an href link -func (s *DeviceServiceOp) GetExtra(deviceID string, includes, excludes []string) (*Device, *Response, error) { - path := fmt.Sprintf("%s/%s", deviceBasePath, deviceID) - if includes != nil { - path += fmt.Sprintf("?include=%s", strings.Join(includes, ",")) - } else if excludes != nil { - path += fmt.Sprintf("?exclude=%s", strings.Join(excludes, ",")) - } +func (s *DeviceServiceOp) Get(deviceID string, opts *GetOptions) (*Device, *Response, error) { + opts = opts.Including("facility") + endpointPath := path.Join(deviceBasePath, deviceID) + apiPathQuery := opts.WithQuery(endpointPath) device := new(Device) - - resp, err := s.client.DoRequest("GET", path, nil, device) + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, device) if err != nil { return nil, resp, err } - return device, resp, err } // Create creates a new device func (s *DeviceServiceOp) Create(createRequest *DeviceCreateRequest) (*Device, *Response, error) { - path := fmt.Sprintf("%s/%s%s", projectBasePath, createRequest.ProjectID, deviceBasePath) + apiPath := path.Join(projectBasePath, createRequest.ProjectID, deviceBasePath) device := new(Device) - resp, err := s.client.DoRequest("POST", path, createRequest, device) + resp, err := s.client.DoRequest("POST", apiPath, createRequest, device) if err != nil { return nil, resp, err } - return device, resp, err } // Update updates an existing device func (s *DeviceServiceOp) Update(deviceID string, updateRequest *DeviceUpdateRequest) (*Device, *Response, error) { - path := fmt.Sprintf("%s/%s?include=facility", deviceBasePath, deviceID) + opts := &GetOptions{} + opts = opts.Including("facility") + endpointPath := path.Join(deviceBasePath, deviceID) + apiPathQuery := opts.WithQuery(endpointPath) device := new(Device) - resp, err := s.client.DoRequest("PUT", path, updateRequest, device) + resp, err := s.client.DoRequest("PUT", apiPathQuery, updateRequest, device) if err != nil { return nil, resp, err } @@ -206,34 +521,35 @@ func (s *DeviceServiceOp) Update(deviceID string, updateRequest *DeviceUpdateReq } // Delete deletes a device -func (s *DeviceServiceOp) Delete(deviceID string) (*Response, error) { - path := fmt.Sprintf("%s/%s", deviceBasePath, deviceID) +func (s *DeviceServiceOp) Delete(deviceID string, force bool) (*Response, error) { + apiPath := path.Join(deviceBasePath, deviceID) + req := &DeviceDeleteRequest{Force: force} - return s.client.DoRequest("DELETE", path, nil, nil) + return s.client.DoRequest("DELETE", apiPath, req, nil) } // Reboot reboots on a device func (s *DeviceServiceOp) Reboot(deviceID string) (*Response, error) { - path := fmt.Sprintf("%s/%s/actions", deviceBasePath, deviceID) + apiPath := path.Join(deviceBasePath, deviceID, "actions") action := &DeviceActionRequest{Type: "reboot"} - return s.client.DoRequest("POST", path, action, nil) + return s.client.DoRequest("POST", apiPath, action, nil) } // PowerOff powers on a device func (s *DeviceServiceOp) PowerOff(deviceID string) (*Response, error) { - path := fmt.Sprintf("%s/%s/actions", deviceBasePath, deviceID) + apiPath := path.Join(deviceBasePath, deviceID, "actions") action := &DeviceActionRequest{Type: "power_off"} - return s.client.DoRequest("POST", path, action, nil) + return s.client.DoRequest("POST", apiPath, action, nil) } // PowerOn powers on a device func (s *DeviceServiceOp) PowerOn(deviceID string) (*Response, error) { - path := fmt.Sprintf("%s/%s/actions", deviceBasePath, deviceID) + apiPath := path.Join(deviceBasePath, deviceID, "actions") action := &DeviceActionRequest{Type: "power_on"} - return s.client.DoRequest("POST", path, action, nil) + return s.client.DoRequest("POST", apiPath, action, nil) } type lockType struct { @@ -242,16 +558,59 @@ type lockType struct { // Lock sets a device to "locked" func (s *DeviceServiceOp) Lock(deviceID string) (*Response, error) { - path := fmt.Sprintf("%s/%s", deviceBasePath, deviceID) + apiPath := path.Join(deviceBasePath, deviceID) action := lockType{Locked: true} - return s.client.DoRequest("PATCH", path, action, nil) + return s.client.DoRequest("PATCH", apiPath, action, nil) } // Unlock sets a device to "unlocked" func (s *DeviceServiceOp) Unlock(deviceID string) (*Response, error) { - path := fmt.Sprintf("%s/%s", deviceBasePath, deviceID) + apiPath := path.Join(deviceBasePath, deviceID) action := lockType{Locked: false} - return s.client.DoRequest("PATCH", path, action, nil) + return s.client.DoRequest("PATCH", apiPath, action, nil) +} + +func (s *DeviceServiceOp) ListBGPNeighbors(deviceID string, opts *ListOptions) ([]BGPNeighbor, *Response, error) { + root := new(bgpNeighborsRoot) + endpointPath := path.Join(deviceBasePath, deviceID, bgpNeighborsBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, root) + if err != nil { + return nil, resp, err + } + + return root.BGPNeighbors, resp, err +} + +// ListBGPSessions returns all BGP Sessions associated with the device +func (s *DeviceServiceOp) ListBGPSessions(deviceID string, opts *ListOptions) (bgpSessions []BGPSession, resp *Response, err error) { + + endpointPath := path.Join(deviceBasePath, deviceID, bgpSessionBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + + for { + subset := new(bgpSessionsRoot) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + bgpSessions = append(bgpSessions, subset.Sessions...) + + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + return + } +} + +// ListEvents returns list of device events +func (s *DeviceServiceOp) ListEvents(deviceID string, opts *ListOptions) ([]Event, *Response, error) { + apiPath := path.Join(deviceBasePath, deviceID, eventBasePath) + + return listEvents(s.client, apiPath, opts) } diff --git a/provider/packet/vendor/github.com/packethost/packngo/doc.go b/provider/packet/vendor/github.com/packethost/packngo/doc.go new file mode 100644 index 00000000..52a192a0 --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/doc.go @@ -0,0 +1,3 @@ +// Package packngo implements the Equinix Metal API +// documented at https://metal.equinix.com/developers/api. +package packngo diff --git a/provider/packet/vendor/github.com/packethost/packngo/email.go b/provider/packet/vendor/github.com/packethost/packngo/email.go index 4c77d0f6..e82ed32d 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/email.go +++ b/provider/packet/vendor/github.com/packethost/packngo/email.go @@ -1,10 +1,23 @@ package packngo +import ( + "path" +) + const emailBasePath = "/emails" +// EmailRequest type used to add an email address to the current user +type EmailRequest struct { + Address string `json:"address,omitempty"` + Default *bool `json:"default,omitempty"` +} + // EmailService interface defines available email methods type EmailService interface { - Get(string) (*Email, *Response, error) + Get(string, *GetOptions) (*Email, *Response, error) + Create(*EmailRequest) (*Email, *Response, error) + Update(string, *EmailRequest) (*Email, *Response, error) + Delete(string) (*Response, error) } // Email represents a user's email address @@ -25,10 +38,49 @@ type EmailServiceOp struct { } // Get retrieves an email by id -func (s *EmailServiceOp) Get(emailID string) (*Email, *Response, error) { +func (s *EmailServiceOp) Get(emailID string, opts *GetOptions) (*Email, *Response, error) { + endpointPath := path.Join(emailBasePath, emailID) + apiPathQuery := opts.WithQuery(endpointPath) + email := new(Email) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, email) + if err != nil { + return nil, resp, err + } + + return email, resp, err +} + +// Create adds a new email address to the current user. +func (s *EmailServiceOp) Create(request *EmailRequest) (*Email, *Response, error) { + email := new(Email) + + resp, err := s.client.DoRequest("POST", emailBasePath, request, email) + if err != nil { + return nil, resp, err + } + + return email, resp, err +} + +// Delete removes the email address from the current user account +func (s *EmailServiceOp) Delete(emailID string) (*Response, error) { + apiPath := path.Join(emailBasePath, emailID) + + resp, err := s.client.DoRequest("DELETE", apiPath, nil, nil) + if err != nil { + return resp, err + } + + return resp, err +} + +// Update email parameters +func (s *EmailServiceOp) Update(emailID string, request *EmailRequest) (*Email, *Response, error) { email := new(Email) + apiPath := path.Join(emailBasePath, emailID) - resp, err := s.client.DoRequest("GET", emailBasePath, nil, email) + resp, err := s.client.DoRequest("PUT", apiPath, request, email) if err != nil { return nil, resp, err } diff --git a/provider/packet/vendor/github.com/packethost/packngo/events.go b/provider/packet/vendor/github.com/packethost/packngo/events.go new file mode 100644 index 00000000..cfc7bcc3 --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/events.go @@ -0,0 +1,81 @@ +package packngo + +import ( + "path" +) + +const eventBasePath = "/events" + +// Event struct +type Event struct { + ID string `json:"id,omitempty"` + State string `json:"state,omitempty"` + Type string `json:"type,omitempty"` + Body string `json:"body,omitempty"` + Relationships []Href `json:"relationships,omitempty"` + Interpolated string `json:"interpolated,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + Href string `json:"href,omitempty"` +} + +type eventsRoot struct { + Events []Event `json:"events,omitempty"` + Meta meta `json:"meta,omitempty"` +} + +// EventService interface defines available event functions +type EventService interface { + List(*ListOptions) ([]Event, *Response, error) + Get(string, *GetOptions) (*Event, *Response, error) +} + +// EventServiceOp implements EventService +type EventServiceOp struct { + client *Client +} + +// List returns all events +func (s *EventServiceOp) List(listOpt *ListOptions) ([]Event, *Response, error) { + return listEvents(s.client, eventBasePath, listOpt) +} + +// Get returns an event by ID +func (s *EventServiceOp) Get(eventID string, getOpt *GetOptions) (*Event, *Response, error) { + apiPath := path.Join(eventBasePath, eventID) + return get(s.client, apiPath, getOpt) +} + +// list helper function for all event functions +func listEvents(client requestDoer, endpointPath string, opts *ListOptions) (events []Event, resp *Response, err error) { + apiPathQuery := opts.WithQuery(endpointPath) + + for { + subset := new(eventsRoot) + + resp, err = client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + events = append(events, subset.Events...) + + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + return + } + +} + +func get(client *Client, endpointPath string, opts *GetOptions) (*Event, *Response, error) { + event := new(Event) + + apiPathQuery := opts.WithQuery(endpointPath) + + resp, err := client.DoRequest("GET", apiPathQuery, nil, event) + if err != nil { + return nil, resp, err + } + + return event, resp, err +} diff --git a/provider/packet/vendor/github.com/packethost/packngo/facilities.go b/provider/packet/vendor/github.com/packethost/packngo/facilities.go index 12aac919..91c21d79 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/facilities.go +++ b/provider/packet/vendor/github.com/packethost/packngo/facilities.go @@ -4,20 +4,21 @@ const facilityBasePath = "/facilities" // FacilityService interface defines available facility methods type FacilityService interface { - List() ([]Facility, *Response, error) + List(*ListOptions) ([]Facility, *Response, error) } type facilityRoot struct { Facilities []Facility `json:"facilities"` } -// Facility represents a Packet facility +// Facility represents an Equinix Metal facility type Facility struct { ID string `json:"id"` Name string `json:"name,omitempty"` Code string `json:"code,omitempty"` Features []string `json:"features,omitempty"` Address *Address `json:"address,omitempty"` + Metro *Metro `json:"metro,omitempty"` URL string `json:"href,omitempty"` } @@ -39,11 +40,12 @@ type FacilityServiceOp struct { client *Client } -// List returns all available Packet facilities -func (s *FacilityServiceOp) List() ([]Facility, *Response, error) { +// List returns all facilities +func (s *FacilityServiceOp) List(opts *ListOptions) ([]Facility, *Response, error) { root := new(facilityRoot) + apiPathQuery := opts.WithQuery(facilityBasePath) - resp, err := s.client.DoRequest("GET", facilityBasePath, nil, root) + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, root) if err != nil { return nil, resp, err } diff --git a/provider/packet/vendor/github.com/packethost/packngo/go.mod b/provider/packet/vendor/github.com/packethost/packngo/go.mod new file mode 100644 index 00000000..dc09690a --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/go.mod @@ -0,0 +1,9 @@ +module github.com/packethost/packngo + +require ( + github.com/dnaeon/go-vcr v1.0.1 + github.com/stretchr/testify v1.5.1 + golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a +) + +go 1.15 diff --git a/provider/packet/vendor/github.com/packethost/packngo/go.sum b/provider/packet/vendor/github.com/packethost/packngo/go.sum new file mode 100644 index 00000000..86d940d3 --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/go.sum @@ -0,0 +1,21 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a h1:y6sBfNd1b9Wy08a6K1Z1DZc4aXABUN5TKjkYhz7UKmo= +golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/provider/packet/vendor/github.com/packethost/packngo/hardware_reservations.go b/provider/packet/vendor/github.com/packethost/packngo/hardware_reservations.go new file mode 100644 index 00000000..c1b848dd --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/hardware_reservations.go @@ -0,0 +1,92 @@ +package packngo + +import ( + "path" +) + +const hardwareReservationBasePath = "/hardware-reservations" + +// HardwareReservationService interface defines available hardware reservation functions +type HardwareReservationService interface { + Get(hardwareReservationID string, getOpt *GetOptions) (*HardwareReservation, *Response, error) + List(projectID string, listOpt *ListOptions) ([]HardwareReservation, *Response, error) + Move(string, string) (*HardwareReservation, *Response, error) +} + +// HardwareReservationServiceOp implements HardwareReservationService +type HardwareReservationServiceOp struct { + client requestDoer +} + +// HardwareReservation struct +type HardwareReservation struct { + ID string `json:"id,omitempty"` + ShortID string `json:"short_id,omitempty"` + Facility Facility `json:"facility,omitempty"` + Plan Plan `json:"plan,omitempty"` + Provisionable bool `json:"provisionable,omitempty"` + Spare bool `json:"spare,omitempty"` + SwitchUUID string `json:"switch_uuid,omitempty"` + Intervals int `json:"intervals,omitempty"` + CurrentPeriod int `json:"current_period,omitempty"` + Href string `json:"href,omitempty"` + Project Project `json:"project,omitempty"` + Device *Device `json:"device,omitempty"` + CreatedAt Timestamp `json:"created_at,omitempty"` +} + +type hardwareReservationRoot struct { + HardwareReservations []HardwareReservation `json:"hardware_reservations"` + Meta meta `json:"meta"` +} + +// List returns all hardware reservations for a given project +func (s *HardwareReservationServiceOp) List(projectID string, opts *ListOptions) (reservations []HardwareReservation, resp *Response, err error) { + endpointPath := path.Join(projectBasePath, projectID, hardwareReservationBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + + for { + subset := new(hardwareReservationRoot) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + reservations = append(reservations, subset.HardwareReservations...) + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + return + } +} + +// Get returns a single hardware reservation +func (s *HardwareReservationServiceOp) Get(hardwareReservationdID string, opts *GetOptions) (*HardwareReservation, *Response, error) { + hardwareReservation := new(HardwareReservation) + + endpointPath := path.Join(hardwareReservationBasePath, hardwareReservationdID) + apiPathQuery := opts.WithQuery(endpointPath) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, hardwareReservation) + if err != nil { + return nil, resp, err + } + + return hardwareReservation, resp, err +} + +// Move a hardware reservation to another project +func (s *HardwareReservationServiceOp) Move(hardwareReservationdID, projectID string) (*HardwareReservation, *Response, error) { + hardwareReservation := new(HardwareReservation) + apiPath := path.Join(hardwareReservationBasePath, hardwareReservationdID, "move") + body := map[string]string{} + body["project_id"] = projectID + + resp, err := s.client.DoRequest("POST", apiPath, body, hardwareReservation) + if err != nil { + return nil, resp, err + } + + return hardwareReservation, resp, err +} diff --git a/provider/packet/vendor/github.com/packethost/packngo/ip.go b/provider/packet/vendor/github.com/packethost/packngo/ip.go index 671eea60..e740cc4c 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/ip.go +++ b/provider/packet/vendor/github.com/packethost/packngo/ip.go @@ -2,51 +2,73 @@ package packngo import ( "fmt" + "path" ) const ipBasePath = "/ips" +const ( + // PublicIPv4 fixed string representation of public ipv4 + PublicIPv4 = "public_ipv4" + // PrivateIPv4 fixed string representation of private ipv4 + PrivateIPv4 = "private_ipv4" + // GlobalIPv4 fixed string representation of global ipv4 + GlobalIPv4 = "global_ipv4" + // PublicIPv6 fixed string representation of public ipv6 + PublicIPv6 = "public_ipv6" + // PrivateIPv6 fixed string representation of private ipv6 + PrivateIPv6 = "private_ipv6" + // GlobalIPv6 fixed string representation of global ipv6 + GlobalIPv6 = "global_ipv6" +) + // DeviceIPService handles assignment of addresses from reserved blocks to instances in a project. type DeviceIPService interface { Assign(deviceID string, assignRequest *AddressStruct) (*IPAddressAssignment, *Response, error) Unassign(assignmentID string) (*Response, error) - Get(assignmentID string) (*IPAddressAssignment, *Response, error) + Get(assignmentID string, getOpt *GetOptions) (*IPAddressAssignment, *Response, error) + List(deviceID string, opts *ListOptions) ([]IPAddressAssignment, *Response, error) } // ProjectIPService handles reservation of IP address blocks for a project. type ProjectIPService interface { - Get(reservationID string) (*IPAddressReservation, *Response, error) - List(projectID string) ([]IPAddressReservation, *Response, error) + Get(reservationID string, getOpt *GetOptions) (*IPAddressReservation, *Response, error) + List(projectID string, opts *ListOptions) ([]IPAddressReservation, *Response, error) Request(projectID string, ipReservationReq *IPReservationRequest) (*IPAddressReservation, *Response, error) Remove(ipReservationID string) (*Response, error) AvailableAddresses(ipReservationID string, r *AvailableRequest) ([]string, *Response, error) } -type ipAddressCommon struct { - ID string `json:"id"` - Address string `json:"address"` - Gateway string `json:"gateway"` - Network string `json:"network"` - AddressFamily int `json:"address_family"` - Netmask string `json:"netmask"` - Public bool `json:"public"` - CIDR int `json:"cidr"` - Created string `json:"created_at,omitempty"` - Updated string `json:"updated_at,omitempty"` - Href string `json:"href"` - Management bool `json:"management"` - Manageable bool `json:"manageable"` - Project Href `json:"project"` +type IpAddressCommon struct { //nolint:golint + ID string `json:"id"` + Address string `json:"address"` + Gateway string `json:"gateway"` + Network string `json:"network"` + AddressFamily int `json:"address_family"` + Netmask string `json:"netmask"` + Public bool `json:"public"` + CIDR int `json:"cidr"` + Created string `json:"created_at,omitempty"` + Updated string `json:"updated_at,omitempty"` + Href string `json:"href"` + Management bool `json:"management"` + Manageable bool `json:"manageable"` + Metro *Metro `json:"metro,omitempty"` + Project Href `json:"project"` + Global bool `json:"global_ip"` + Tags []string `json:"tags,omitempty"` + CustomData interface{} `json:"customdata,omitempty"` } // IPAddressReservation is created when user sends IP reservation request for a project (considering it's within quota). type IPAddressReservation struct { - ipAddressCommon - Assignments []Href `json:"assignments"` - Facility Facility `json:"facility,omitempty"` - Available string `json:"available"` - Addon bool `json:"addon"` - Bill bool `json:"bill"` + IpAddressCommon + Assignments []*IPAddressAssignment `json:"assignments"` + Facility *Facility `json:"facility,omitempty"` + Available string `json:"available"` + Addon bool `json:"addon"` + Bill bool `json:"bill"` + Description *string `json:"details"` } // AvailableResponse is a type for listing of available addresses from a reserved block. @@ -61,16 +83,22 @@ type AvailableRequest struct { // IPAddressAssignment is created when an IP address from reservation block is assigned to a device. type IPAddressAssignment struct { - ipAddressCommon + IpAddressCommon AssignedTo Href `json:"assigned_to"` } // IPReservationRequest represents the body of a reservation request. type IPReservationRequest struct { - Type string `json:"type"` - Quantity int `json:"quantity"` - Comments string `json:"comments"` - Facility string `json:"facility"` + Type string `json:"type"` + Quantity int `json:"quantity"` + Description string `json:"details,omitempty"` + Facility *string `json:"facility,omitempty"` + Metro *string `json:"metro,omitempty"` + Tags []string `json:"tags,omitempty"` + CustomData interface{} `json:"customdata,omitempty"` + // FailOnApprovalRequired if the IP request cannot be approved automatically, rather than sending to + // the longer Equinix Metal approval process, fail immediately with a 422 error + FailOnApprovalRequired bool `json:"fail_on_approval_required,omitempty"` } // AddressStruct is a helper type for request/response with dict like {"address": ... } @@ -79,9 +107,9 @@ type AddressStruct struct { } func deleteFromIP(client *Client, resourceID string) (*Response, error) { - path := fmt.Sprintf("%s/%s", ipBasePath, resourceID) + apiPath := path.Join(ipBasePath, resourceID) - return client.DoRequest("DELETE", path, nil, nil) + return client.DoRequest("DELETE", apiPath, nil, nil) } func (i IPAddressReservation) String() string { @@ -107,10 +135,10 @@ func (i *DeviceIPServiceOp) Unassign(assignmentID string) (*Response, error) { // Assign assigns an IP address to a device. // The IP address must be in one of the IP ranges assigned to the device’s project. func (i *DeviceIPServiceOp) Assign(deviceID string, assignRequest *AddressStruct) (*IPAddressAssignment, *Response, error) { - path := fmt.Sprintf("%s/%s%s", deviceBasePath, deviceID, ipBasePath) + apiPath := path.Join(deviceBasePath, deviceID, ipBasePath) ipa := new(IPAddressAssignment) - resp, err := i.client.DoRequest("POST", path, assignRequest, ipa) + resp, err := i.client.DoRequest("POST", apiPath, assignRequest, ipa) if err != nil { return nil, resp, err } @@ -119,11 +147,12 @@ func (i *DeviceIPServiceOp) Assign(deviceID string, assignRequest *AddressStruct } // Get returns assignment by ID. -func (i *DeviceIPServiceOp) Get(assignmentID string) (*IPAddressAssignment, *Response, error) { - path := fmt.Sprintf("%s/%s", ipBasePath, assignmentID) +func (i *DeviceIPServiceOp) Get(assignmentID string, opts *GetOptions) (*IPAddressAssignment, *Response, error) { + endpointPath := path.Join(ipBasePath, assignmentID) + apiPathQuery := opts.WithQuery(endpointPath) ipa := new(IPAddressAssignment) - resp, err := i.client.DoRequest("GET", path, nil, ipa) + resp, err := i.client.DoRequest("GET", apiPathQuery, nil, ipa) if err != nil { return nil, resp, err } @@ -131,17 +160,38 @@ func (i *DeviceIPServiceOp) Get(assignmentID string) (*IPAddressAssignment, *Res return ipa, resp, err } +// List list all of the IP address assignments on a device +func (i *DeviceIPServiceOp) List(deviceID string, opts *ListOptions) ([]IPAddressAssignment, *Response, error) { + endpointPath := path.Join(deviceBasePath, deviceID, ipBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + + //ipList represents collection of IP Address reservations + type ipList struct { + IPs []IPAddressAssignment `json:"ip_addresses,omitempty"` + } + + ips := new(ipList) + + resp, err := i.client.DoRequest("GET", apiPathQuery, nil, ips) + if err != nil { + return nil, resp, err + } + + return ips.IPs, resp, err +} + // ProjectIPServiceOp is interface for IP assignment methods. type ProjectIPServiceOp struct { client *Client } // Get returns reservation by ID. -func (i *ProjectIPServiceOp) Get(reservationID string) (*IPAddressReservation, *Response, error) { - path := fmt.Sprintf("%s/%s", ipBasePath, reservationID) +func (i *ProjectIPServiceOp) Get(reservationID string, opts *GetOptions) (*IPAddressReservation, *Response, error) { + endpointPath := path.Join(ipBasePath, reservationID) + apiPathQuery := opts.WithQuery(endpointPath) ipr := new(IPAddressReservation) - resp, err := i.client.DoRequest("GET", path, nil, ipr) + resp, err := i.client.DoRequest("GET", apiPathQuery, nil, ipr) if err != nil { return nil, resp, err } @@ -150,13 +200,14 @@ func (i *ProjectIPServiceOp) Get(reservationID string) (*IPAddressReservation, * } // List provides a list of IP resevations for a single project. -func (i *ProjectIPServiceOp) List(projectID string) ([]IPAddressReservation, *Response, error) { - path := fmt.Sprintf("%s/%s%s", projectBasePath, projectID, ipBasePath) +func (i *ProjectIPServiceOp) List(projectID string, opts *ListOptions) ([]IPAddressReservation, *Response, error) { + endpointPath := path.Join(projectBasePath, projectID, ipBasePath) + apiPathQuery := opts.WithQuery(endpointPath) reservations := new(struct { Reservations []IPAddressReservation `json:"ip_addresses"` }) - resp, err := i.client.DoRequest("GET", path, nil, reservations) + resp, err := i.client.DoRequest("GET", apiPathQuery, nil, reservations) if err != nil { return nil, resp, err } @@ -165,10 +216,10 @@ func (i *ProjectIPServiceOp) List(projectID string) ([]IPAddressReservation, *Re // Request requests more IP space for a project in order to have additional IP addresses to assign to devices. func (i *ProjectIPServiceOp) Request(projectID string, ipReservationReq *IPReservationRequest) (*IPAddressReservation, *Response, error) { - path := fmt.Sprintf("%s/%s%s", projectBasePath, projectID, ipBasePath) + apiPath := path.Join(projectBasePath, projectID, ipBasePath) ipr := new(IPAddressReservation) - resp, err := i.client.DoRequest("POST", path, ipReservationReq, ipr) + resp, err := i.client.DoRequest("POST", apiPath, ipReservationReq, ipr) if err != nil { return nil, resp, err } @@ -182,10 +233,10 @@ func (i *ProjectIPServiceOp) Remove(ipReservationID string) (*Response, error) { // AvailableAddresses lists addresses available from a reserved block func (i *ProjectIPServiceOp) AvailableAddresses(ipReservationID string, r *AvailableRequest) ([]string, *Response, error) { - path := fmt.Sprintf("%s/%s/available?cidr=%d", ipBasePath, ipReservationID, r.CIDR) + apiPathQuery := fmt.Sprintf("%s/%s/available?cidr=%d", ipBasePath, ipReservationID, r.CIDR) ar := new(AvailableResponse) - resp, err := i.client.DoRequest("GET", path, r, ar) + resp, err := i.client.DoRequest("GET", apiPathQuery, r, ar) if err != nil { return nil, resp, err } diff --git a/provider/packet/vendor/github.com/packethost/packngo/metros.go b/provider/packet/vendor/github.com/packethost/packngo/metros.go new file mode 100644 index 00000000..2a3924dc --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/metros.go @@ -0,0 +1,42 @@ +package packngo + +const metroBasePath = "/locations/metros" + +// MetroService interface defines available metro methods +type MetroService interface { + List(*ListOptions) ([]Metro, *Response, error) +} + +type metroRoot struct { + Metros []Metro `json:"metros"` +} + +// Metro represents an Equinix Metal metro +type Metro struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Code string `json:"code,omitempty"` + Country string `json:"country,omitempty"` +} + +func (f Metro) String() string { + return Stringify(f) +} + +// MetroServiceOp implements MetroService +type MetroServiceOp struct { + client *Client +} + +// List returns all metros +func (s *MetroServiceOp) List(opts *ListOptions) ([]Metro, *Response, error) { + root := new(metroRoot) + apiPathQuery := opts.WithQuery(metroBasePath) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, root) + if err != nil { + return nil, resp, err + } + + return root.Metros, resp, err +} diff --git a/provider/packet/vendor/github.com/packethost/packngo/notifications.go b/provider/packet/vendor/github.com/packethost/packngo/notifications.go new file mode 100644 index 00000000..073484cd --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/notifications.go @@ -0,0 +1,94 @@ +package packngo + +import ( + "path" +) + +const notificationBasePath = "/notifications" + +// Notification struct +type Notification struct { + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Body string `json:"body,omitempty"` + Severity string `json:"severity,omitempty"` + Read bool `json:"read,omitempty"` + Context string `json:"context,omitempty"` + CreatedAt Timestamp `json:"created_at,omitempty"` + UpdatedAt Timestamp `json:"updated_at,omitempty"` + User Href `json:"user,omitempty"` + Href string `json:"href,omitempty"` +} + +type notificationsRoot struct { + Notifications []Notification `json:"notifications,omitempty"` + Meta meta `json:"meta,omitempty"` +} + +// NotificationService interface defines available event functions +type NotificationService interface { + List(*ListOptions) ([]Notification, *Response, error) + Get(string, *GetOptions) (*Notification, *Response, error) + MarkAsRead(string) (*Notification, *Response, error) +} + +// NotificationServiceOp implements NotificationService +type NotificationServiceOp struct { + client *Client +} + +// List returns all notifications +func (s *NotificationServiceOp) List(listOpt *ListOptions) ([]Notification, *Response, error) { + return listNotifications(s.client, notificationBasePath, listOpt) +} + +// Get returns a notification by ID +func (s *NotificationServiceOp) Get(notificationID string, opts *GetOptions) (*Notification, *Response, error) { + endpointPath := path.Join(notificationBasePath, notificationID) + apiPathQuery := opts.WithQuery(endpointPath) + return getNotifications(s.client, apiPathQuery) +} + +// Marks notification as read by ID +func (s *NotificationServiceOp) MarkAsRead(notificationID string) (*Notification, *Response, error) { + apiPath := path.Join(notificationBasePath, notificationID) + return markAsRead(s.client, apiPath) +} + +// list helper function for all notification functions +func listNotifications(client *Client, endpointPath string, opts *ListOptions) ([]Notification, *Response, error) { + root := new(notificationsRoot) + + apiPathQuery := opts.WithQuery(endpointPath) + + resp, err := client.DoRequest("GET", apiPathQuery, nil, root) + if err != nil { + return nil, resp, err + } + + return root.Notifications, resp, err +} + +func getNotifications(client *Client, apiPath string) (*Notification, *Response, error) { + + notification := new(Notification) + + resp, err := client.DoRequest("GET", apiPath, nil, notification) + if err != nil { + return nil, resp, err + } + + return notification, resp, err +} + +func markAsRead(client *Client, apiPath string) (*Notification, *Response, error) { + + notification := new(Notification) + + resp, err := client.DoRequest("PUT", apiPath, nil, notification) + if err != nil { + return nil, resp, err + } + + return notification, resp, err +} diff --git a/provider/packet/vendor/github.com/packethost/packngo/operatingsystems.go b/provider/packet/vendor/github.com/packethost/packngo/operatingsystems.go index 7fd7f27a..087fba6c 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/operatingsystems.go +++ b/provider/packet/vendor/github.com/packethost/packngo/operatingsystems.go @@ -11,12 +11,13 @@ type osRoot struct { OperatingSystems []OS `json:"operating_systems"` } -// OS represents a Packet operating system +// OS represents an Equinix Metal operating system type OS struct { - Name string `json:"name"` - Slug string `json:"slug"` - Distro string `json:"distro"` - Version string `json:"version"` + Name string `json:"name"` + Slug string `json:"slug"` + Distro string `json:"distro"` + Version string `json:"version"` + ProvisionableOn []string `json:"provisionable_on"` } func (o OS) String() string { diff --git a/provider/packet/vendor/github.com/packethost/packngo/organizations.go b/provider/packet/vendor/github.com/packethost/packngo/organizations.go index 36e76f1a..07b0d6bc 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/organizations.go +++ b/provider/packet/vendor/github.com/packethost/packngo/organizations.go @@ -1,25 +1,29 @@ package packngo -import "fmt" +import ( + "path" +) -// API documentation https://www.packet.net/developers/api/organizations/ +// API documentation https://metal.equinix.com/developers/api/organizations/ const organizationBasePath = "/organizations" // OrganizationService interface defines available organization methods type OrganizationService interface { - List() ([]Organization, *Response, error) - Get(string) (*Organization, *Response, error) + List(*ListOptions) ([]Organization, *Response, error) + Get(string, *GetOptions) (*Organization, *Response, error) Create(*OrganizationCreateRequest) (*Organization, *Response, error) Update(string, *OrganizationUpdateRequest) (*Organization, *Response, error) Delete(string) (*Response, error) ListPaymentMethods(string) ([]PaymentMethod, *Response, error) + ListEvents(string, *ListOptions) ([]Event, *Response, error) } type organizationsRoot struct { Organizations []Organization `json:"organizations"` + Meta meta `json:"meta"` } -// Organization represents a Packet organization +// Organization represents an Equinix Metal organization type Organization struct { ID string `json:"id"` Name string `json:"name,omitempty"` @@ -45,7 +49,7 @@ func (o Organization) String() string { return Stringify(o) } -// OrganizationCreateRequest type used to create a Packet organization +// OrganizationCreateRequest type used to create an Equinix Metal organization type OrganizationCreateRequest struct { Name string `json:"name"` Description string `json:"description"` @@ -58,7 +62,7 @@ func (o OrganizationCreateRequest) String() string { return Stringify(o) } -// OrganizationUpdateRequest type used to update a Packet organization +// OrganizationUpdateRequest type used to update an Equinix Metal organization type OrganizationUpdateRequest struct { Name *string `json:"name,omitempty"` Description *string `json:"description,omitempty"` @@ -77,23 +81,33 @@ type OrganizationServiceOp struct { } // List returns the user's organizations -func (s *OrganizationServiceOp) List() ([]Organization, *Response, error) { - root := new(organizationsRoot) +func (s *OrganizationServiceOp) List(opts *ListOptions) (orgs []Organization, resp *Response, err error) { + subset := new(organizationsRoot) - resp, err := s.client.DoRequest("GET", organizationBasePath, nil, root) - if err != nil { - return nil, resp, err - } + apiPathQuery := opts.WithQuery(organizationBasePath) + + for { + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + orgs = append(orgs, subset.Organizations...) - return root.Organizations, resp, err + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + return + } } // Get returns a organization by id -func (s *OrganizationServiceOp) Get(organizationID string) (*Organization, *Response, error) { - path := fmt.Sprintf("%s/%s", organizationBasePath, organizationID) +func (s *OrganizationServiceOp) Get(organizationID string, opts *GetOptions) (*Organization, *Response, error) { + endpointPath := path.Join(organizationBasePath, organizationID) + apiPathQuery := opts.WithQuery(endpointPath) organization := new(Organization) - resp, err := s.client.DoRequest("GET", path, nil, organization) + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, organization) if err != nil { return nil, resp, err } @@ -115,10 +129,10 @@ func (s *OrganizationServiceOp) Create(createRequest *OrganizationCreateRequest) // Update updates an organization func (s *OrganizationServiceOp) Update(id string, updateRequest *OrganizationUpdateRequest) (*Organization, *Response, error) { - path := fmt.Sprintf("%s/%s", organizationBasePath, id) + apiPath := path.Join(organizationBasePath, id) organization := new(Organization) - resp, err := s.client.DoRequest("PATCH", path, updateRequest, organization) + resp, err := s.client.DoRequest("PATCH", apiPath, updateRequest, organization) if err != nil { return nil, resp, err } @@ -128,20 +142,27 @@ func (s *OrganizationServiceOp) Update(id string, updateRequest *OrganizationUpd // Delete deletes an organizationID func (s *OrganizationServiceOp) Delete(organizationID string) (*Response, error) { - path := fmt.Sprintf("%s/%s", organizationBasePath, organizationID) + apiPath := path.Join(organizationBasePath, organizationID) - return s.client.DoRequest("DELETE", path, nil, nil) + return s.client.DoRequest("DELETE", apiPath, nil, nil) } // ListPaymentMethods returns PaymentMethods for an organization func (s *OrganizationServiceOp) ListPaymentMethods(organizationID string) ([]PaymentMethod, *Response, error) { - url := fmt.Sprintf("%s/%s%s", organizationBasePath, organizationID, paymentMethodBasePath) + apiPath := path.Join(organizationBasePath, organizationID, paymentMethodBasePath) root := new(paymentMethodsRoot) - resp, err := s.client.DoRequest("GET", url, nil, root) + resp, err := s.client.DoRequest("GET", apiPath, nil, root) if err != nil { return nil, resp, err } return root.PaymentMethods, resp, err } + +// ListEvents returns list of organization events +func (s *OrganizationServiceOp) ListEvents(organizationID string, listOpt *ListOptions) ([]Event, *Response, error) { + apiPath := path.Join(organizationBasePath, organizationID, eventBasePath) + + return listEvents(s.client, apiPath, listOpt) +} diff --git a/provider/packet/vendor/github.com/packethost/packngo/packngo.go b/provider/packet/vendor/github.com/packethost/packngo/packngo.go index 37b6c23a..0e3f9f36 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/packngo.go +++ b/provider/packet/vendor/github.com/packethost/packngo/packngo.go @@ -2,6 +2,7 @@ package packngo import ( "bytes" + "context" "encoding/json" "fmt" "io" @@ -11,58 +12,24 @@ import ( "net/http/httputil" "net/url" "os" + "regexp" "strconv" "strings" "time" ) const ( - packetTokenEnvVar = "PACKET_AUTH_TOKEN" - libraryVersion = "0.1.0" - baseURL = "https://api.packet.net/" - userAgent = "packngo/" + libraryVersion - mediaType = "application/json" - debugEnvVar = "PACKNGO_DEBUG" - - headerRateLimit = "X-RateLimit-Limit" - headerRateRemaining = "X-RateLimit-Remaining" - headerRateReset = "X-RateLimit-Reset" + authTokenEnvVar = "PACKET_AUTH_TOKEN" + baseURL = "https://api.equinix.com/metal/v1/" + mediaType = "application/json" + debugEnvVar = "PACKNGO_DEBUG" + + headerRateLimit = "X-RateLimit-Limit" + headerRateRemaining = "X-RateLimit-Remaining" + headerRateReset = "X-RateLimit-Reset" + expectedAPIContentTypePrefix = "application/json" ) -// ListOptions specifies optional global API parameters -type ListOptions struct { - // for paginated result sets, page of results to retrieve - Page int `url:"page,omitempty"` - - // for paginated result sets, the number of results to return per page - PerPage int `url:"per_page,omitempty"` - - // specify which resources you want to return as collections instead of references - Includes string -} - -func (l *ListOptions) createURL() (url string) { - if l.Includes != "" { - url += fmt.Sprintf("include=%s", l.Includes) - } - - if l.Page != 0 { - if url != "" { - url += "&" - } - url += fmt.Sprintf("page=%d", l.Page) - } - - if l.PerPage != 0 { - if url != "" { - url += "&" - } - url += fmt.Sprintf("per_page=%d", l.PerPage) - } - - return -} - // meta contains pagination information type meta struct { Self *Href `json:"self"` @@ -126,28 +93,68 @@ type Client struct { RateLimit Rate - // Packet Api Objects - Plans PlanService - Users UserService - Emails EmailService - SSHKeys SSHKeyService + // Equinix Metal Api Objects + APIKeys APIKeyService + BGPConfig BGPConfigService + BGPSessions BGPSessionService + Batches BatchService + CapacityService CapacityService + Connections ConnectionService + DeviceIPs DeviceIPService Devices DeviceService - Projects ProjectService + Emails EmailService + Events EventService Facilities FacilityService + HardwareReservations HardwareReservationService + Metros MetroService + Notifications NotificationService OperatingSystems OSService - DeviceIPs DeviceIPService - DevicePorts DevicePortService + Organizations OrganizationService + Plans PlanService + Ports PortService ProjectIPs ProjectIPService ProjectVirtualNetworks ProjectVirtualNetworkService - Volumes VolumeService - VolumeAttachments VolumeAttachmentService + Projects ProjectService + SSHKeys SSHKeyService SpotMarket SpotMarketService - Organizations OrganizationService + SpotMarketRequests SpotMarketRequestService + TwoFactorAuth TwoFactorAuthService + Users UserService + VirtualCircuits VirtualCircuitService + VolumeAttachments VolumeAttachmentService + Volumes VolumeService + + // DevicePorts + // + // Deprecated: Use Client.Ports or Device methods + DevicePorts DevicePortService + + // VPN + // + // Deprecated: As of March 31, 2021, Doorman service is no longer + // available. See https://metal.equinix.com/developers/docs/accounts/doorman/ + // for more details. + VPN VPNService +} + +// requestDoer provides methods for making HTTP requests and receiving the +// response, errors, and a structured result +// +// This interface is used in *ServiceOp as a mockable alternative to a full +// Client object. +type requestDoer interface { + NewRequest(method, path string, body interface{}) (*http.Request, error) + Do(req *http.Request, v interface{}) (*Response, error) + DoRequest(method, path string, body, v interface{}) (*Response, error) + DoRequestWithHeader(method string, headers map[string]string, path string, body, v interface{}) (*Response, error) } // NewRequest inits a new http request with the proper headers func (c *Client) NewRequest(method, path string, body interface{}) (*http.Request, error) { // relative path to append to the endpoint url, no leading slash please + if path[0] == '/' { + path = path[1:] + } rel, err := url.Parse(path) if err != nil { return nil, err @@ -176,7 +183,7 @@ func (c *Client) NewRequest(method, path string, body interface{}) (*http.Reques req.Header.Add("Content-Type", mediaType) req.Header.Add("Accept", mediaType) - req.Header.Add("User-Agent", userAgent) + req.Header.Add("User-Agent", c.UserAgent) return req, nil } @@ -192,9 +199,9 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { response := Response{Response: resp} response.populateRate() if c.debug { - o, _ := httputil.DumpResponse(response.Response, true) - log.Printf("\n=======[RESPONSE]============\n%s\n\n", string(o)) + dumpResponse(response.Response) } + dumpDeprecation(response.Response) c.RateLimit = response.Rate err = checkResponse(resp) @@ -206,7 +213,10 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { if v != nil { // if v implements the io.Writer interface, return the raw response if w, ok := v.(io.Writer); ok { - io.Copy(w, resp.Body) + _, err = io.Copy(w, resp.Body) + if err != nil { + return &response, err + } } else { err = json.NewDecoder(resp.Body).Decode(v) if err != nil { @@ -218,13 +228,93 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { return &response, err } +// dumpDeprecation logs headers defined by +// https://tools.ietf.org/html/rfc8594 +func dumpDeprecation(resp *http.Response) { + uri := "" + if resp.Request != nil { + uri = resp.Request.Method + " " + resp.Request.URL.Path + } + + deprecation := resp.Header.Get("Deprecation") + if deprecation != "" { + if deprecation == "true" { + deprecation = "" + } else { + deprecation = " on " + deprecation + } + log.Printf("WARNING: %q reported deprecation%s", uri, deprecation) + } + + sunset := resp.Header.Get("Sunset") + if sunset != "" { + log.Printf("WARNING: %q reported sunsetting on %s", uri, sunset) + } + + links := resp.Header.Values("Link") + + for _, s := range links { + for _, ss := range strings.Split(s, ",") { + if strings.Contains(ss, "rel=\"sunset\"") { + link := strings.Split(ss, ";")[0] + log.Printf("WARNING: See %s for sunset details", link) + } else if strings.Contains(ss, "rel=\"deprecation\"") { + link := strings.Split(ss, ";")[0] + log.Printf("WARNING: See %s for deprecation details", link) + } + } + } +} + +func dumpResponse(resp *http.Response) { + o, _ := httputil.DumpResponse(resp, true) + strResp := string(o) + reg, _ := regexp.Compile(`"token":(.+?),`) + reMatches := reg.FindStringSubmatch(strResp) + if len(reMatches) == 2 { + strResp = strings.Replace(strResp, reMatches[1], strings.Repeat("-", len(reMatches[1])), 1) + } + log.Printf("\n=======[RESPONSE]============\n%s\n\n", strResp) +} + +func dumpRequest(req *http.Request) { + r := req.Clone(context.TODO()) + r.Body, _ = req.GetBody() + h := r.Header + if len(h.Get("X-Auth-Token")) != 0 { + h.Set("X-Auth-Token", "**REDACTED**") + } + defer r.Body.Close() + + o, _ := httputil.DumpRequestOut(r, false) + bbs, _ := ioutil.ReadAll(r.Body) + + strReq := string(o) + log.Printf("\n=======[REQUEST]=============\n%s%s\n", string(strReq), string(bbs)) +} + // DoRequest is a convenience method, it calls NewRequest followed by Do // v is the interface to unmarshal the response JSON into func (c *Client) DoRequest(method, path string, body, v interface{}) (*Response, error) { req, err := c.NewRequest(method, path, body) if c.debug { - o, _ := httputil.DumpRequestOut(req, true) - log.Printf("\n=======[REQUEST]=============\n%s\n", string(o)) + dumpRequest(req) + } + if err != nil { + return nil, err + } + return c.Do(req, v) +} + +// DoRequestWithHeader same as DoRequest +func (c *Client) DoRequestWithHeader(method string, headers map[string]string, path string, body, v interface{}) (*Response, error) { + req, err := c.NewRequest(method, path, body) + for k, v := range headers { + req.Header.Add(k, v) + } + + if c.debug { + dumpRequest(req) } if err != nil { return nil, err @@ -232,10 +322,11 @@ func (c *Client) DoRequest(method, path string, body, v interface{}) (*Response, return c.Do(req, v) } +// NewClient initializes and returns a Client func NewClient() (*Client, error) { - apiToken := os.Getenv(packetTokenEnvVar) + apiToken := os.Getenv(authTokenEnvVar) if apiToken == "" { - return nil, fmt.Errorf("you must export %s.", packetTokenEnvVar) + return nil, fmt.Errorf("you must export %s", authTokenEnvVar) } c := NewClientWithAuth("packngo lib", apiToken, nil) return c, nil @@ -243,7 +334,7 @@ func NewClient() (*Client, error) { } // NewClientWithAuth initializes and returns a Client, use this to get an API Client to operate on -// N.B.: Packet's API certificate requires Go 1.5+ to successfully parse. If you are using +// N.B.: Equinix Metal's API certificate requires Go 1.5+ to successfully parse. If you are using // an older version of Go, pass in a custom http.Client with a custom TLS configuration // that sets "InsecureSkipVerify" to "true" func NewClientWithAuth(consumerToken string, apiKey string, httpClient *http.Client) *Client { @@ -255,10 +346,7 @@ func NewClientWithAuth(consumerToken string, apiKey string, httpClient *http.Cli // for mocking the remote API func NewClientWithBaseURL(consumerToken string, apiKey string, httpClient *http.Client, apiBaseURL string) (*Client, error) { if httpClient == nil { - // Don't fall back on http.DefaultClient as it's not nice to adjust state - // implicitly. If the client wants to use http.DefaultClient, they can - // pass it in explicitly. - httpClient = &http.Client{} + httpClient = http.DefaultClient } u, err := url.Parse(apiBaseURL) @@ -266,31 +354,46 @@ func NewClientWithBaseURL(consumerToken string, apiKey string, httpClient *http. return nil, err } - c := &Client{client: httpClient, BaseURL: u, UserAgent: userAgent, ConsumerToken: consumerToken, APIKey: apiKey} - c.debug = os.Getenv(debugEnvVar) != "" - c.Plans = &PlanServiceOp{client: c} - c.Organizations = &OrganizationServiceOp{client: c} - c.Users = &UserServiceOp{client: c} - c.Emails = &EmailServiceOp{client: c} - c.SSHKeys = &SSHKeyServiceOp{client: c} + c := &Client{client: httpClient, BaseURL: u, UserAgent: UserAgent, ConsumerToken: consumerToken, APIKey: apiKey} + c.APIKeys = &APIKeyServiceOp{client: c} + c.BGPConfig = &BGPConfigServiceOp{client: c} + c.BGPSessions = &BGPSessionServiceOp{client: c} + c.Batches = &BatchServiceOp{client: c} + c.CapacityService = &CapacityServiceOp{client: c} + c.Connections = &ConnectionServiceOp{client: c} + c.DeviceIPs = &DeviceIPServiceOp{client: c} + c.DevicePorts = &DevicePortServiceOp{client: c} c.Devices = &DeviceServiceOp{client: c} - c.Projects = &ProjectServiceOp{client: c} + c.Emails = &EmailServiceOp{client: c} + c.Events = &EventServiceOp{client: c} c.Facilities = &FacilityServiceOp{client: c} + c.HardwareReservations = &HardwareReservationServiceOp{client: c} + c.Metros = &MetroServiceOp{client: c} + c.Notifications = &NotificationServiceOp{client: c} c.OperatingSystems = &OSServiceOp{client: c} - c.DeviceIPs = &DeviceIPServiceOp{client: c} - c.DevicePorts = &DevicePortServiceOp{client: c} - c.ProjectVirtualNetworks = &ProjectVirtualNetworkServiceOp{client: c} + c.Organizations = &OrganizationServiceOp{client: c} + c.Plans = &PlanServiceOp{client: c} + c.Ports = &PortServiceOp{client: c} c.ProjectIPs = &ProjectIPServiceOp{client: c} - c.Volumes = &VolumeServiceOp{client: c} - c.VolumeAttachments = &VolumeAttachmentServiceOp{client: c} + c.ProjectVirtualNetworks = &ProjectVirtualNetworkServiceOp{client: c} + c.Projects = &ProjectServiceOp{client: c} + c.SSHKeys = &SSHKeyServiceOp{client: c} c.SpotMarket = &SpotMarketServiceOp{client: c} + c.SpotMarketRequests = &SpotMarketRequestServiceOp{client: c} + c.TwoFactorAuth = &TwoFactorAuthServiceOp{client: c} + c.Users = &UserServiceOp{client: c} + c.VirtualCircuits = &VirtualCircuitServiceOp{client: c} + c.VPN = &VPNServiceOp{client: c} + c.VolumeAttachments = &VolumeAttachmentServiceOp{client: c} + c.Volumes = &VolumeServiceOp{client: c} + c.debug = os.Getenv(debugEnvVar) != "" return c, nil } func checkResponse(r *http.Response) error { - // return if http status code is within 200 range - if c := r.StatusCode; c >= 200 && c <= 299 { + + if s := r.StatusCode; s >= 200 && s <= 299 { // response is good, return return nil } @@ -298,8 +401,21 @@ func checkResponse(r *http.Response) error { errorResponse := &ErrorResponse{Response: r} data, err := ioutil.ReadAll(r.Body) // if the response has a body, populate the message in errorResponse - if err == nil && len(data) > 0 { - json.Unmarshal(data, errorResponse) + if err != nil { + return err + } + + ct := r.Header.Get("Content-Type") + if !strings.HasPrefix(ct, expectedAPIContentTypePrefix) { + errorResponse.SingleError = fmt.Sprintf("Unexpected Content-Type %s with status %s", ct, r.Status) + return errorResponse + } + + if len(data) > 0 { + err = json.Unmarshal(data, errorResponse) + if err != nil { + return err + } } return errorResponse diff --git a/provider/packet/vendor/github.com/packethost/packngo/payment_methods.go b/provider/packet/vendor/github.com/packethost/packngo/payment_methods.go index 3479f092..8f9840f0 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/payment_methods.go +++ b/provider/packet/vendor/github.com/packethost/packngo/payment_methods.go @@ -1,6 +1,6 @@ package packngo -// API documentation https://www.packet.net/developers/api/paymentmethods/ +// API documentation https://metal.equinix.com/developers/api/paymentmethods/ const paymentMethodBasePath = "/payment-methods" // ProjectService interface defines available project methods @@ -16,7 +16,7 @@ type paymentMethodsRoot struct { PaymentMethods []PaymentMethod `json:"payment_methods"` } -// PaymentMethod represents a Packet payment method of an organization +// PaymentMethod represents an Equinix Metal payment method of an organization type PaymentMethod struct { ID string `json:"id"` Name string `json:"name,omitempty"` @@ -39,10 +39,10 @@ func (pm PaymentMethod) String() string { return Stringify(pm) } -// PaymentMethodCreateRequest type used to create a Packet payment method of an organization +// PaymentMethodCreateRequest type used to create an Equinix Metal payment method of an organization type PaymentMethodCreateRequest struct { Name string `json:"name"` - Nonce string `json:"name"` + Nonce string `json:"nonce"` CardholderName string `json:"cardholder_name,omitempty"` ExpMonth string `json:"expiration_month,omitempty"` ExpYear string `json:"expiration_year,omitempty"` @@ -53,7 +53,7 @@ func (pm PaymentMethodCreateRequest) String() string { return Stringify(pm) } -// PaymentMethodUpdateRequest type used to update a Packet payment method of an organization +// PaymentMethodUpdateRequest type used to update an Equinix Metal payment method of an organization type PaymentMethodUpdateRequest struct { Name *string `json:"name,omitempty"` CardholderName *string `json:"cardholder_name,omitempty"` @@ -68,5 +68,4 @@ func (pm PaymentMethodUpdateRequest) String() string { // PaymentMethodServiceOp implements PaymentMethodService type PaymentMethodServiceOp struct { - client *Client } diff --git a/provider/packet/vendor/github.com/packethost/packngo/plans.go b/provider/packet/vendor/github.com/packethost/packngo/plans.go index 148a2a5c..3bd5cd38 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/plans.go +++ b/provider/packet/vendor/github.com/packethost/packngo/plans.go @@ -1,25 +1,36 @@ package packngo +import "path" + const planBasePath = "/plans" // PlanService interface defines available plan methods type PlanService interface { - List() ([]Plan, *Response, error) + List(*ListOptions) ([]Plan, *Response, error) + ProjectList(string, *ListOptions) ([]Plan, *Response, error) + OrganizationList(string, *ListOptions) ([]Plan, *Response, error) } type planRoot struct { Plans []Plan `json:"plans"` } -// Plan represents a Packet service plan +// Plan represents an Equinix Metal service plan type Plan struct { - ID string `json:"id"` - Slug string `json:"slug,omitempty"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - Line string `json:"line,omitempty"` - Specs *Specs `json:"specs,omitempty"` - Pricing *Pricing `json:"pricing,omitempty"` + ID string `json:"id"` + Slug string `json:"slug,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Line string `json:"line,omitempty"` + Legacy bool `json:"legacy,omitempty"` + Specs *Specs `json:"specs,omitempty"` + Pricing *Pricing `json:"pricing,omitempty"` + DeploymentTypes []string `json:"deployment_types"` + Class string `json:"class"` + AvailableIn []Facility `json:"available_in"` + AvailableInMetros []Metro `json:"available_in_metros"` + + Href string `json:"href,omitempty"` } func (p Plan) String() string { @@ -91,8 +102,8 @@ func (f Features) String() string { // Pricing - the pricing options on a plan type Pricing struct { - Hourly float32 `json:"hourly,omitempty"` - Monthly float32 `json:"monthly,omitempty"` + Hour float32 `json:"hour,omitempty"` + Month float32 `json:"month,omitempty"` } func (p Pricing) String() string { @@ -104,14 +115,31 @@ type PlanServiceOp struct { client *Client } -// List method returns all available plans -func (s *PlanServiceOp) List() ([]Plan, *Response, error) { +func planList(c *Client, apiPath string, opts *ListOptions) ([]Plan, *Response, error) { root := new(planRoot) + apiPathQuery := opts.WithQuery(apiPath) - resp, err := s.client.DoRequest("GET", planBasePath, nil, root) + resp, err := c.DoRequest("GET", apiPathQuery, nil, root) if err != nil { return nil, resp, err } return root.Plans, resp, err + +} + +// List method returns all available plans +func (s *PlanServiceOp) List(opts *ListOptions) ([]Plan, *Response, error) { + return planList(s.client, planBasePath, opts) + +} + +// ProjectList method returns plans available in a project +func (s *PlanServiceOp) ProjectList(projectID string, opts *ListOptions) ([]Plan, *Response, error) { + return planList(s.client, path.Join(projectBasePath, projectID, planBasePath), opts) +} + +// OrganizationList method returns plans available in an organization +func (s *PlanServiceOp) OrganizationList(organizationID string, opts *ListOptions) ([]Plan, *Response, error) { + return planList(s.client, path.Join(organizationBasePath, organizationID, planBasePath), opts) } diff --git a/provider/packet/vendor/github.com/packethost/packngo/ports.go b/provider/packet/vendor/github.com/packethost/packngo/ports.go index c0f79d38..d820e722 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/ports.go +++ b/provider/packet/vendor/github.com/packethost/packngo/ports.go @@ -1,47 +1,77 @@ package packngo import ( - "fmt" + "path" ) -const portBasePath = "/ports" +// PortService handles operations on a port +type PortService interface { + Assign(string, string) (*Port, *Response, error) + Unassign(string, string) (*Port, *Response, error) + AssignNative(string, string) (*Port, *Response, error) + UnassignNative(string) (*Port, *Response, error) + Bond(string, bool) (*Port, *Response, error) + Disbond(string, bool) (*Port, *Response, error) + ConvertToLayerTwo(string) (*Port, *Response, error) + ConvertToLayerThree(string, []AddressRequest) (*Port, *Response, error) + Get(string, *GetOptions) (*Port, *Response, error) +} -type NetworkType int +type PortServiceOp struct { + client requestDoer +} -const ( - NetworkL3 NetworkType = iota - NetworkHybrid - NetworkL2Bonded - NetworkL2Individual - NetworkUnknown -) +var _ PortService = (*PortServiceOp)(nil) -// DevicePortService handles operations on a port which belongs to a particular device -type DevicePortService interface { - Assign(*PortAssignRequest) (*Port, *Response, error) - Unassign(*PortAssignRequest) (*Port, *Response, error) - Bond(*BondRequest) (*Port, *Response, error) - Disbond(*DisbondRequest) (*Port, *Response, error) - PortToLayerTwo(string) (*Port, *Response, error) - PortToLayerThree(string) (*Port, *Response, error) - DeviceToLayerTwo(string) (*Device, error) - DeviceToLayerThree(string) (*Device, error) - DeviceNetworkType(string) (NetworkType, error) - GetBondPort(string) (*Port, error) - GetPortByName(string, string) (*Port, error) +type PortData struct { + // MAC address is set for NetworkPort ports + MAC string `json:"mac,omitempty"` + + // Bonded is true for NetworkPort ports in a bond and NetworkBondPort ports + // that are active + Bonded bool `json:"bonded"` } -type PortData struct { - MAC string `json:"mac"` - Bonded bool `json:"bonded"` +type BondData struct { + // ID is the Port.ID of the bonding port + ID string `json:"id"` + + // Name of the port interface for the bond ("bond0") + Name string `json:"name"` } +// Port is a hardware port associated with a reserved or instantiated hardware +// device. type Port struct { - ID string `json:"id"` - Type string `json:"type"` - Name string `json:"name"` - Data PortData `json:"data"` - AttachedVirtualNetworks []VirtualNetwork `json:"virtual_networks"` + // ID of the Port + ID string `json:"id"` + + // Type is either "NetworkBondPort" for bond ports or "NetworkPort" for + // bondable ethernet ports + Type string `json:"type"` + + // Name of the interface for this port (such as "bond0" or "eth0") + Name string `json:"name"` + + // Data about the port + Data PortData `json:"data,omitempty"` + + // Indicates whether or not the bond can be broken on the port (when applicable). + DisbondOperationSupported bool `json:"disbond_operation_supported,omitempty"` + + // NetworkType is either of layer2-bonded, layer2-individual, layer3, + // hybrid, hybrid-bonded + NetworkType string `json:"network_type,omitempty"` + + // NativeVirtualNetwork is the Native VLAN attached to the port + // + NativeVirtualNetwork *VirtualNetwork `json:"native_virtual_network,omitempty"` + + // VLANs attached to the port + AttachedVirtualNetworks []VirtualNetwork `json:"virtual_networks,omitempty"` + + // Bond details for ports with a NetworkPort type + Bond *BondData `json:"bond,omitempty"` } type AddressRequest struct { @@ -53,77 +83,79 @@ type BackToL3Request struct { RequestIPs []AddressRequest `json:"request_ips"` } -type DevicePortServiceOp struct { - client *Client -} - type PortAssignRequest struct { - PortID string `json:"id"` + // PortID of the Port + // + // Deprecated: this is redundant to the portID parameter in request + // functions. This is kept for use by deprecated DevicePortServiceOps + // methods. + PortID string `json:"id,omitempty"` + VirtualNetworkID string `json:"vnid"` } type BondRequest struct { - PortID string `json:"id"` - BulkEnable bool `json:"bulk_enable"` + BulkEnable bool `json:"bulk_enable"` } type DisbondRequest struct { - PortID string `json:"id"` - BulkDisable bool `json:"bulk_disable"` + BulkDisable bool `json:"bulk_disable"` } -func (i *DevicePortServiceOp) GetBondPort(deviceID string) (*Port, error) { - device, _, err := i.client.Devices.Get(deviceID) - if err != nil { - return nil, err - } - for _, port := range device.NetworkPorts { - if port.Type == "NetworkBondPort" { - return &port, nil - } - } +// Assign adds a VLAN to a port +func (i *PortServiceOp) Assign(portID, vlanID string) (*Port, *Response, error) { + apiPath := path.Join(portBasePath, portID, "assign") + par := &PortAssignRequest{VirtualNetworkID: vlanID} + + return i.portAction(apiPath, par) +} - return nil, fmt.Errorf("No bonded port found in device %s", deviceID) +// AssignNative assigns a virtual network to the port as a "native VLAN" +func (i *PortServiceOp) AssignNative(portID, vlanID string) (*Port, *Response, error) { + apiPath := path.Join(portBasePath, portID, "native-vlan") + par := &PortAssignRequest{VirtualNetworkID: vlanID} + return i.portAction(apiPath, par) } -func (i *DevicePortServiceOp) GetPortByName(deviceID, name string) (*Port, error) { - device, _, err := i.client.Devices.Get(deviceID) +// UnassignNative removes native VLAN from the supplied port +func (i *PortServiceOp) UnassignNative(portID string) (*Port, *Response, error) { + apiPath := path.Join(portBasePath, portID, "native-vlan") + port := new(Port) + + resp, err := i.client.DoRequest("DELETE", apiPath, nil, port) if err != nil { - return nil, err - } - for _, port := range device.NetworkPorts { - if port.Name == name { - return &port, nil - } + return nil, resp, err } - return nil, fmt.Errorf("Port %s not found in device %s", name, deviceID) + return port, resp, err } -func (i *DevicePortServiceOp) Assign(par *PortAssignRequest) (*Port, *Response, error) { - path := fmt.Sprintf("%s/%s/assign", portBasePath, par.PortID) - return i.portAction(path, par) -} +// Unassign removes a VLAN from the port +func (i *PortServiceOp) Unassign(portID, vlanID string) (*Port, *Response, error) { + apiPath := path.Join(portBasePath, portID, "unassign") + par := &PortAssignRequest{VirtualNetworkID: vlanID} -func (i *DevicePortServiceOp) Unassign(par *PortAssignRequest) (*Port, *Response, error) { - path := fmt.Sprintf("%s/%s/unassign", portBasePath, par.PortID) - return i.portAction(path, par) + return i.portAction(apiPath, par) } -func (i *DevicePortServiceOp) Bond(br *BondRequest) (*Port, *Response, error) { - path := fmt.Sprintf("%s/%s/bond", portBasePath, br.PortID) - return i.portAction(path, br) +// Bond enables bonding for one or all ports +func (i *PortServiceOp) Bond(portID string, bulkEnable bool) (*Port, *Response, error) { + br := &BondRequest{BulkEnable: bulkEnable} + apiPath := path.Join(portBasePath, portID, "bond") + return i.portAction(apiPath, br) } -func (i *DevicePortServiceOp) Disbond(dr *DisbondRequest) (*Port, *Response, error) { - path := fmt.Sprintf("%s/%s/disbond", portBasePath, dr.PortID) - return i.portAction(path, dr) +// Disbond disables bonding for one or all ports +func (i *PortServiceOp) Disbond(portID string, bulkEnable bool) (*Port, *Response, error) { + dr := &DisbondRequest{BulkDisable: bulkEnable} + apiPath := path.Join(portBasePath, portID, "disbond") + return i.portAction(apiPath, dr) } -func (i *DevicePortServiceOp) portAction(path string, req interface{}) (*Port, *Response, error) { +func (i *PortServiceOp) portAction(apiPath string, req interface{}) (*Port, *Response, error) { port := new(Port) - resp, err := i.client.DoRequest("POST", path, req, port) + resp, err := i.client.DoRequest("POST", apiPath, req, port) if err != nil { return nil, resp, err } @@ -131,11 +163,14 @@ func (i *DevicePortServiceOp) portAction(path string, req interface{}) (*Port, * return port, resp, err } -func (i *DevicePortServiceOp) PortToLayerTwo(portID string) (*Port, *Response, error) { - path := fmt.Sprintf("%s/%s/convert/layer-2", portBasePath, portID) +// ConvertToLayerTwo converts a bond port to Layer 2. IP assignments of the port will be removed. +// +// portID is the UUID of a Bonding Port +func (i *PortServiceOp) ConvertToLayerTwo(portID string) (*Port, *Response, error) { + apiPath := path.Join(portBasePath, portID, "convert", "layer-2") port := new(Port) - resp, err := i.client.DoRequest("POST", path, nil, port) + resp, err := i.client.DoRequest("POST", apiPath, nil, port) if err != nil { return nil, resp, err } @@ -143,19 +178,16 @@ func (i *DevicePortServiceOp) PortToLayerTwo(portID string) (*Port, *Response, e return port, resp, err } -func (i *DevicePortServiceOp) PortToLayerThree(portID string) (*Port, *Response, error) { - path := fmt.Sprintf("%s/%s/convert/layer-3", portBasePath, portID) +// ConvertToLayerThree converts a bond port to Layer 3. VLANs must first be unassigned. +func (i *PortServiceOp) ConvertToLayerThree(portID string, ips []AddressRequest) (*Port, *Response, error) { + apiPath := path.Join(portBasePath, portID, "convert", "layer-3") port := new(Port) req := BackToL3Request{ - RequestIPs: []AddressRequest{ - AddressRequest{AddressFamily: 4, Public: true}, - AddressRequest{AddressFamily: 4, Public: false}, - AddressRequest{AddressFamily: 6, Public: true}, - }, + RequestIPs: ips, } - resp, err := i.client.DoRequest("POST", path, &req, port) + resp, err := i.client.DoRequest("POST", apiPath, &req, port) if err != nil { return nil, resp, err } @@ -163,63 +195,14 @@ func (i *DevicePortServiceOp) PortToLayerThree(portID string) (*Port, *Response, return port, resp, err } -func (i *DevicePortServiceOp) DeviceNetworkType(deviceID string) (NetworkType, error) { - d, _, err := i.client.Devices.Get(deviceID) - if err != nil { - return NetworkUnknown, err - } - if d.Plan.Slug == "baremetal_0" || d.Plan.Slug == "baremetal_1" { - return NetworkL3, nil - } - if d.Plan.Slug == "baremetal_1e" { - return NetworkHybrid, nil - } - if len(d.NetworkPorts) < 1 { - // really? - return NetworkL2Individual, nil - } - if d.NetworkPorts[0].Data.Bonded { - if d.NetworkPorts[2].Data.Bonded { - for _, ip := range d.Network { - if ip.Management { - return NetworkL3, nil - } - } - return NetworkL2Bonded, nil - } else { - return NetworkHybrid, nil - } - } - return NetworkL2Individual, nil -} - -func (i *DevicePortServiceOp) DeviceToLayerThree(deviceID string) (*Device, error) { - // hopefull all the VLANs are unassigned at this point - bond0, err := i.client.DevicePorts.GetBondPort(deviceID) - if err != nil { - return nil, err - } - - bond0, _, err = i.client.DevicePorts.PortToLayerThree(bond0.ID) - if err != nil { - return nil, err - } - d, _, err := i.client.Devices.Get(deviceID) - return d, err -} - -// DeviceToLayerTwo converts device to L2 networking. Use bond0 to attach VLAN. -func (i *DevicePortServiceOp) DeviceToLayerTwo(deviceID string) (*Device, error) { - bond0, err := i.client.DevicePorts.GetBondPort(deviceID) - if err != nil { - return nil, err - } - - bond0, _, err = i.client.DevicePorts.PortToLayerTwo(bond0.ID) +// Get returns a port by id +func (s *PortServiceOp) Get(portID string, opts *GetOptions) (*Port, *Response, error) { + endpointPath := path.Join(portBasePath, portID) + apiPathQuery := opts.WithQuery(endpointPath) + port := new(Port) + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, port) if err != nil { - return nil, err + return nil, resp, err } - d, _, err := i.client.Devices.Get(deviceID) - return d, err - + return port, resp, err } diff --git a/provider/packet/vendor/github.com/packethost/packngo/projects.go b/provider/packet/vendor/github.com/packethost/packngo/projects.go index 814b5e3c..24f56c6e 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/projects.go +++ b/provider/packet/vendor/github.com/packethost/packngo/projects.go @@ -1,16 +1,21 @@ package packngo -import "fmt" +import ( + "path" +) const projectBasePath = "/projects" // ProjectService interface defines available project methods type ProjectService interface { List(listOpt *ListOptions) ([]Project, *Response, error) - Get(string) (*Project, *Response, error) + Get(string, *GetOptions) (*Project, *Response, error) Create(*ProjectCreateRequest) (*Project, *Response, error) Update(string, *ProjectUpdateRequest) (*Project, *Response, error) Delete(string) (*Response, error) + ListBGPSessions(projectID string, listOpt *ListOptions) ([]BGPSession, *Response, error) + ListEvents(string, *ListOptions) ([]Event, *Response, error) + ListSSHKeys(projectID string, searchOpt *SearchOptions) ([]SSHKey, *Response, error) } type projectsRoot struct { @@ -18,25 +23,26 @@ type projectsRoot struct { Meta meta `json:"meta"` } -// Project represents a Packet project +// Project represents an Equinix Metal project type Project struct { - ID string `json:"id"` - Name string `json:"name,omitempty"` - Organization Organization `json:"organization,omitempty"` - Created string `json:"created_at,omitempty"` - Updated string `json:"updated_at,omitempty"` - Users []User `json:"members,omitempty"` - Devices []Device `json:"devices,omitempty"` - SSHKeys []SSHKey `json:"ssh_keys,omitempty"` - URL string `json:"href,omitempty"` - PaymentMethod PaymentMethod `json:"payment_method,omitempty"` + ID string `json:"id"` + Name string `json:"name,omitempty"` + Organization Organization `json:"organization,omitempty"` + Created string `json:"created_at,omitempty"` + Updated string `json:"updated_at,omitempty"` + Users []User `json:"members,omitempty"` + Devices []Device `json:"devices,omitempty"` + SSHKeys []SSHKey `json:"ssh_keys,omitempty"` + URL string `json:"href,omitempty"` + PaymentMethod PaymentMethod `json:"payment_method,omitempty"` + BackendTransfer bool `json:"backend_transfer_enabled"` } func (p Project) String() string { return Stringify(p) } -// ProjectCreateRequest type used to create a Packet project +// ProjectCreateRequest type used to create an Equinix Metal project type ProjectCreateRequest struct { Name string `json:"name"` PaymentMethodID string `json:"payment_method_id,omitempty"` @@ -47,10 +53,11 @@ func (p ProjectCreateRequest) String() string { return Stringify(p) } -// ProjectUpdateRequest type used to update a Packet project +// ProjectUpdateRequest type used to update an Equinix Metal project type ProjectUpdateRequest struct { Name *string `json:"name,omitempty"` PaymentMethodID *string `json:"payment_method_id,omitempty"` + BackendTransfer *bool `json:"backend_transfer_enabled,omitempty"` } func (p ProjectUpdateRequest) String() string { @@ -59,32 +66,24 @@ func (p ProjectUpdateRequest) String() string { // ProjectServiceOp implements ProjectService type ProjectServiceOp struct { - client *Client + client requestDoer } // List returns the user's projects -func (s *ProjectServiceOp) List(listOpt *ListOptions) (projects []Project, resp *Response, err error) { - var params string - if listOpt != nil { - params = listOpt.createURL() - } - root := new(projectsRoot) - - path := fmt.Sprintf("%s?%s", projectBasePath, params) +func (s *ProjectServiceOp) List(opts *ListOptions) (projects []Project, resp *Response, err error) { + apiPathQuery := opts.WithQuery(projectBasePath) for { - resp, err = s.client.DoRequest("GET", path, nil, root) + subset := new(projectsRoot) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) if err != nil { return nil, resp, err } - projects = append(projects, root.Projects...) + projects = append(projects, subset.Projects...) - if root.Meta.Next != nil && (listOpt == nil || listOpt.Page == 0) { - path = root.Meta.Next.Href - if params != "" { - path = fmt.Sprintf("%s&%s", path, params) - } + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { continue } @@ -93,15 +92,14 @@ func (s *ProjectServiceOp) List(listOpt *ListOptions) (projects []Project, resp } // Get returns a project by id -func (s *ProjectServiceOp) Get(projectID string) (*Project, *Response, error) { - path := fmt.Sprintf("%s/%s", projectBasePath, projectID) +func (s *ProjectServiceOp) Get(projectID string, opts *GetOptions) (*Project, *Response, error) { + endpointPath := path.Join(projectBasePath, projectID) + apiPathQuery := opts.WithQuery(endpointPath) project := new(Project) - - resp, err := s.client.DoRequest("GET", path, nil, project) + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, project) if err != nil { return nil, resp, err } - return project, resp, err } @@ -119,10 +117,10 @@ func (s *ProjectServiceOp) Create(createRequest *ProjectCreateRequest) (*Project // Update updates a project func (s *ProjectServiceOp) Update(id string, updateRequest *ProjectUpdateRequest) (*Project, *Response, error) { - path := fmt.Sprintf("%s/%s", projectBasePath, id) + apiPath := path.Join(projectBasePath, id) project := new(Project) - resp, err := s.client.DoRequest("PATCH", path, updateRequest, project) + resp, err := s.client.DoRequest("PATCH", apiPath, updateRequest, project) if err != nil { return nil, resp, err } @@ -132,7 +130,53 @@ func (s *ProjectServiceOp) Update(id string, updateRequest *ProjectUpdateRequest // Delete deletes a project func (s *ProjectServiceOp) Delete(projectID string) (*Response, error) { - path := fmt.Sprintf("%s/%s", projectBasePath, projectID) + apiPath := path.Join(projectBasePath, projectID) + + return s.client.DoRequest("DELETE", apiPath, nil, nil) +} + +// ListBGPSessions returns all BGP Sessions associated with the project +func (s *ProjectServiceOp) ListBGPSessions(projectID string, opts *ListOptions) (bgpSessions []BGPSession, resp *Response, err error) { + endpointPath := path.Join(projectBasePath, projectID, bgpSessionBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + + for { + subset := new(bgpSessionsRoot) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + bgpSessions = append(bgpSessions, subset.Sessions...) + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + return + } +} + +// ListSSHKeys returns all SSH Keys associated with the project +func (s *ProjectServiceOp) ListSSHKeys(projectID string, opts *SearchOptions) (sshKeys []SSHKey, resp *Response, err error) { + + endpointPath := path.Join(projectBasePath, projectID, sshKeyBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + + subset := new(sshKeyRoot) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + sshKeys = append(sshKeys, subset.SSHKeys...) + + return +} + +// ListEvents returns list of project events +func (s *ProjectServiceOp) ListEvents(projectID string, listOpt *ListOptions) ([]Event, *Response, error) { + apiPath := path.Join(projectBasePath, projectID, eventBasePath) - return s.client.DoRequest("DELETE", path, nil, nil) + return listEvents(s.client, apiPath, listOpt) } diff --git a/provider/packet/vendor/github.com/packethost/packngo/spotmarket.go b/provider/packet/vendor/github.com/packethost/packngo/spotmarket.go index 5dfb7d55..3c1ee943 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/spotmarket.go +++ b/provider/packet/vendor/github.com/packethost/packngo/spotmarket.go @@ -1,10 +1,27 @@ package packngo -const spotMarketBasePath = "/market/spot/prices" +import "path" + +const ( + spotMarketBasePath = "/market/spot/prices" + + spotMarketMetrosPath = "metros" +) // SpotMarketService expooses Spot Market methods type SpotMarketService interface { + // Prices gets current spot market prices by facility. + // + // Deprecated: Use PricesByFacility Prices() (PriceMap, *Response, error) + + // PricesByFacility gets current spot market prices by facility. The map is + // indexed by facility code and then plan name. + PricesByFacility() (PriceMap, *Response, error) + + // PricesByMetro gets current spot market prices by metro. The map is + // indexed by metro code and then plan name. + PricesByMetro() (PriceMap, *Response, error) } // SpotMarketServiceOp implements SpotMarketService @@ -12,11 +29,21 @@ type SpotMarketServiceOp struct { client *Client } -// PriceMap is a map of [facility][plan]-> float Price +// PriceMap is a map of [location][plan]-> float Price type PriceMap map[string]map[string]float64 -// Prices gets current PriceMap from the API +// Prices gets current spot market prices by facility. +// +// Deprecated: Use PricesByFacility which this function thinly wraps. func (s *SpotMarketServiceOp) Prices() (PriceMap, *Response, error) { + return s.PricesByFacility() +} + +// PricesByFacility gets current spot market prices by facility. The map is +// indexed by facility code and then plan name. +// +// price := client.SpotMarket.PricesByFacility()["ny5"]["c3.medium.x86"] +func (s *SpotMarketServiceOp) PricesByFacility() (PriceMap, *Response, error) { root := new(struct { SMPs map[string]map[string]struct { Price float64 `json:"price"` @@ -37,3 +64,29 @@ func (s *SpotMarketServiceOp) Prices() (PriceMap, *Response, error) { } return prices, resp, err } + +// PricesByMetro gets current spot market prices by metro. The map is +// indexed by metro code and then plan name. +// +// price := client.SpotMarket.PricesByMetro()["sv"]["c3.medium.x86"] +func (s *SpotMarketServiceOp) PricesByMetro() (PriceMap, *Response, error) { + root := new(struct { + SMPs map[string]map[string]struct { + Price float64 `json:"price"` + } `json:"spot_market_prices"` + }) + + resp, err := s.client.DoRequest("GET", path.Join(spotMarketBasePath, spotMarketMetrosPath), nil, root) + if err != nil { + return nil, resp, err + } + + prices := make(PriceMap) + for metro, planMap := range root.SMPs { + prices[metro] = map[string]float64{} + for plan, v := range planMap { + prices[metro][plan] = v.Price + } + } + return prices, resp, err +} diff --git a/provider/packet/vendor/github.com/packethost/packngo/spotmarketrequest.go b/provider/packet/vendor/github.com/packethost/packngo/spotmarketrequest.go new file mode 100644 index 00000000..1ef1f352 --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/spotmarketrequest.go @@ -0,0 +1,120 @@ +package packngo + +import ( + "math" + "path" +) + +const spotMarketRequestBasePath = "/spot-market-requests" + +type SpotMarketRequestService interface { + List(string, *ListOptions) ([]SpotMarketRequest, *Response, error) + Create(*SpotMarketRequestCreateRequest, string) (*SpotMarketRequest, *Response, error) + Delete(string, bool) (*Response, error) + Get(string, *GetOptions) (*SpotMarketRequest, *Response, error) +} + +type SpotMarketRequestCreateRequest struct { + DevicesMax int `json:"devices_max"` + DevicesMin int `json:"devices_min"` + EndAt *Timestamp `json:"end_at,omitempty"` + FacilityIDs []string `json:"facilities,omitempty"` + Metro string `json:"metro,omitempty"` + MaxBidPrice float64 `json:"max_bid_price"` + + Parameters SpotMarketRequestInstanceParameters `json:"instance_parameters"` +} + +type SpotMarketRequest struct { + SpotMarketRequestCreateRequest + ID string `json:"id"` + Devices []Device `json:"devices"` + Facilities []Facility `json:"facilities,omitempty"` + Metro *Metro `json:"metro,omitempty"` + Project Project `json:"project"` + Href string `json:"href"` + Plan Plan `json:"plan"` +} + +type SpotMarketRequestInstanceParameters struct { + AlwaysPXE bool `json:"always_pxe,omitempty"` + IPXEScriptURL string `json:"ipxe_script_url,omitempty"` + BillingCycle string `json:"billing_cycle"` + CustomData string `json:"customdata,omitempty"` + Description string `json:"description,omitempty"` + Features []string `json:"features,omitempty"` + Hostname string `json:"hostname,omitempty"` + Hostnames []string `json:"hostnames,omitempty"` + Locked bool `json:"locked,omitempty"` + OperatingSystem string `json:"operating_system"` + Plan string `json:"plan"` + ProjectSSHKeys []string `json:"project_ssh_keys,omitempty"` + Tags []string `json:"tags"` + TerminationTime *Timestamp `json:"termination_time,omitempty"` + UserSSHKeys []string `json:"user_ssh_keys,omitempty"` + UserData string `json:"userdata"` +} + +type SpotMarketRequestServiceOp struct { + client *Client +} + +func roundPlus(f float64, places int) float64 { + shift := math.Pow(10, float64(places)) + return math.Floor(f*shift+.5) / shift +} + +func (s *SpotMarketRequestServiceOp) Create(cr *SpotMarketRequestCreateRequest, pID string) (*SpotMarketRequest, *Response, error) { + opts := (&GetOptions{}).Including("devices", "project", "plan") + endpointPath := path.Join(projectBasePath, pID, spotMarketRequestBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + + cr.MaxBidPrice = roundPlus(cr.MaxBidPrice, 2) + smr := new(SpotMarketRequest) + + resp, err := s.client.DoRequest("POST", apiPathQuery, cr, smr) + if err != nil { + return nil, resp, err + } + + return smr, resp, err +} + +func (s *SpotMarketRequestServiceOp) List(pID string, opts *ListOptions) ([]SpotMarketRequest, *Response, error) { + type smrRoot struct { + SMRs []SpotMarketRequest `json:"spot_market_requests"` + } + + endpointPath := path.Join(projectBasePath, pID, spotMarketRequestBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + output := new(smrRoot) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, output) + if err != nil { + return nil, nil, err + } + + return output.SMRs, resp, nil +} + +func (s *SpotMarketRequestServiceOp) Get(id string, opts *GetOptions) (*SpotMarketRequest, *Response, error) { + endpointPath := path.Join(spotMarketRequestBasePath, id) + apiPathQuery := opts.WithQuery(endpointPath) + smr := new(SpotMarketRequest) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, &smr) + if err != nil { + return nil, resp, err + } + + return smr, resp, err +} + +func (s *SpotMarketRequestServiceOp) Delete(id string, forceDelete bool) (*Response, error) { + apiPath := path.Join(spotMarketRequestBasePath, id) + var params *map[string]bool + if forceDelete { + params = &map[string]bool{"force_termination": true} + } + return s.client.DoRequest("DELETE", apiPath, params, nil) +} diff --git a/provider/packet/vendor/github.com/packethost/packngo/sshkeys.go b/provider/packet/vendor/github.com/packethost/packngo/sshkeys.go index 260bf087..3f4e3b74 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/sshkeys.go +++ b/provider/packet/vendor/github.com/packethost/packngo/sshkeys.go @@ -1,6 +1,9 @@ package packngo -import "fmt" +import ( + "fmt" + "path" +) const ( sshKeyBasePath = "/ssh-keys" @@ -10,7 +13,7 @@ const ( type SSHKeyService interface { List() ([]SSHKey, *Response, error) ProjectList(string) ([]SSHKey, *Response, error) - Get(string) (*SSHKey, *Response, error) + Get(string, *GetOptions) (*SSHKey, *Response, error) Create(*SSHKeyCreateRequest) (*SSHKey, *Response, error) Update(string, *SSHKeyUpdateRequest) (*SSHKey, *Response, error) Delete(string) (*Response, error) @@ -28,7 +31,7 @@ type SSHKey struct { FingerPrint string `json:"fingerprint"` Created string `json:"created_at"` Updated string `json:"updated_at"` - User User `json:"user,omitempty"` + Owner Href URL string `json:"href,omitempty"` } @@ -74,8 +77,9 @@ func (s *SSHKeyServiceOp) list(url string) ([]SSHKey, *Response, error) { } // ProjectList lists ssh keys of a project +// Deprecated: Use ProjectServiceOp.ListSSHKeys func (s *SSHKeyServiceOp) ProjectList(projectID string) ([]SSHKey, *Response, error) { - return s.list(fmt.Sprintf("%s/%s%s", projectBasePath, projectID, sshKeyBasePath)) + return s.list(path.Join(projectBasePath, projectID, sshKeyBasePath)) } @@ -85,11 +89,12 @@ func (s *SSHKeyServiceOp) List() ([]SSHKey, *Response, error) { } // Get returns an ssh key by id -func (s *SSHKeyServiceOp) Get(sshKeyID string) (*SSHKey, *Response, error) { - path := fmt.Sprintf("%s/%s", sshKeyBasePath, sshKeyID) +func (s *SSHKeyServiceOp) Get(sshKeyID string, opts *GetOptions) (*SSHKey, *Response, error) { + endpointPath := path.Join(sshKeyBasePath, sshKeyID) + apiPathQuery := opts.WithQuery(endpointPath) sshKey := new(SSHKey) - resp, err := s.client.DoRequest("GET", path, nil, sshKey) + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, sshKey) if err != nil { return nil, resp, err } @@ -99,13 +104,13 @@ func (s *SSHKeyServiceOp) Get(sshKeyID string) (*SSHKey, *Response, error) { // Create creates a new ssh key func (s *SSHKeyServiceOp) Create(createRequest *SSHKeyCreateRequest) (*SSHKey, *Response, error) { - path := sshKeyBasePath + urlPath := sshKeyBasePath if createRequest.ProjectID != "" { - path = fmt.Sprintf("%s/%s%s", projectBasePath, createRequest.ProjectID, sshKeyBasePath) + urlPath = path.Join(projectBasePath, createRequest.ProjectID, sshKeyBasePath) } sshKey := new(SSHKey) - resp, err := s.client.DoRequest("POST", path, createRequest, sshKey) + resp, err := s.client.DoRequest("POST", urlPath, createRequest, sshKey) if err != nil { return nil, resp, err } @@ -118,11 +123,11 @@ func (s *SSHKeyServiceOp) Update(id string, updateRequest *SSHKeyUpdateRequest) if updateRequest.Label == nil && updateRequest.Key == nil { return nil, nil, fmt.Errorf("You must set either Label or Key string for SSH Key update") } - path := fmt.Sprintf("%s/%s", sshKeyBasePath, id) + apiPath := path.Join(sshKeyBasePath, id) sshKey := new(SSHKey) - resp, err := s.client.DoRequest("PATCH", path, updateRequest, sshKey) + resp, err := s.client.DoRequest("PATCH", apiPath, updateRequest, sshKey) if err != nil { return nil, resp, err } @@ -132,7 +137,7 @@ func (s *SSHKeyServiceOp) Update(id string, updateRequest *SSHKeyUpdateRequest) // Delete deletes an ssh key func (s *SSHKeyServiceOp) Delete(sshKeyID string) (*Response, error) { - path := fmt.Sprintf("%s/%s", sshKeyBasePath, sshKeyID) + apiPath := path.Join(sshKeyBasePath, sshKeyID) - return s.client.DoRequest("DELETE", path, nil, nil) + return s.client.DoRequest("DELETE", apiPath, nil, nil) } diff --git a/provider/packet/vendor/github.com/packethost/packngo/timestamp.go b/provider/packet/vendor/github.com/packethost/packngo/timestamp.go index c3320ed6..ee3d780d 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/timestamp.go +++ b/provider/packet/vendor/github.com/packethost/packngo/timestamp.go @@ -2,6 +2,7 @@ package packngo import ( "strconv" + "strings" "time" ) @@ -22,9 +23,11 @@ func (t *Timestamp) UnmarshalJSON(data []byte) (err error) { str := string(data) i, err := strconv.ParseInt(str, 10, 64) if err == nil { - t.Time = time.Unix(i, 0) + t.Time = time.Unix(i, 0).UTC() } else { - t.Time, err = time.Parse(`"`+time.RFC3339+`"`, str) + if t.Time, err = time.ParseInLocation(time.RFC3339, strings.Trim(str, `"`), time.UTC); err != nil { + return err + } } return } diff --git a/provider/packet/vendor/github.com/packethost/packngo/two_factor_auth.go b/provider/packet/vendor/github.com/packethost/packngo/two_factor_auth.go new file mode 100644 index 00000000..5064b09f --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/two_factor_auth.go @@ -0,0 +1,56 @@ +package packngo + +const twoFactorAuthAppPath = "/user/otp/app" +const twoFactorAuthSmsPath = "/user/otp/sms" + +// TwoFactorAuthService interface defines available two factor authentication functions +type TwoFactorAuthService interface { + EnableApp(string) (*Response, error) + DisableApp(string) (*Response, error) + EnableSms(string) (*Response, error) + DisableSms(string) (*Response, error) + ReceiveSms() (*Response, error) + SeedApp() (string, *Response, error) +} + +// TwoFactorAuthServiceOp implements TwoFactorAuthService +type TwoFactorAuthServiceOp struct { + client *Client +} + +// EnableApp function enables two factor auth using authenticatior app +func (s *TwoFactorAuthServiceOp) EnableApp(token string) (resp *Response, err error) { + headers := map[string]string{"x-otp-token": token} + return s.client.DoRequestWithHeader("POST", headers, twoFactorAuthAppPath, nil, nil) +} + +// EnableSms function enables two factor auth using sms +func (s *TwoFactorAuthServiceOp) EnableSms(token string) (resp *Response, err error) { + headers := map[string]string{"x-otp-token": token} + return s.client.DoRequestWithHeader("POST", headers, twoFactorAuthSmsPath, nil, nil) +} + +// ReceiveSms orders the auth service to issue an SMS token +func (s *TwoFactorAuthServiceOp) ReceiveSms() (resp *Response, err error) { + return s.client.DoRequest("POST", twoFactorAuthSmsPath+"/receive", nil, nil) +} + +// DisableApp function disables two factor auth using +func (s *TwoFactorAuthServiceOp) DisableApp(token string) (resp *Response, err error) { + headers := map[string]string{"x-otp-token": token} + return s.client.DoRequestWithHeader("DELETE", headers, twoFactorAuthAppPath, nil, nil) +} + +// DisableSms function disables two factor auth using +func (s *TwoFactorAuthServiceOp) DisableSms(token string) (resp *Response, err error) { + headers := map[string]string{"x-otp-token": token} + return s.client.DoRequestWithHeader("DELETE", headers, twoFactorAuthSmsPath, nil, nil) +} + +// SeedApp orders the auth service to issue a token via google authenticator +func (s *TwoFactorAuthServiceOp) SeedApp() (otpURI string, resp *Response, err error) { + ret := &map[string]string{} + resp, err = s.client.DoRequest("POST", twoFactorAuthAppPath+"/receive", nil, ret) + + return (*ret)["otp_uri"], resp, err +} diff --git a/provider/packet/vendor/github.com/packethost/packngo/user.go b/provider/packet/vendor/github.com/packethost/packngo/user.go index 412db905..6132e23c 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/user.go +++ b/provider/packet/vendor/github.com/packethost/packngo/user.go @@ -1,15 +1,23 @@ package packngo -const userBasePath = "/users" -const userPath = "/user" +import "path" + +const usersBasePath = "/users" +const userBasePath = "/user" // UserService interface defines available user methods type UserService interface { - Get(string) (*User, *Response, error) + List(*ListOptions) ([]User, *Response, error) + Get(string, *GetOptions) (*User, *Response, error) Current() (*User, *Response, error) } -// User represents a Packet user +type usersRoot struct { + Users []User `json:"users"` + Meta meta `json:"meta"` +} + +// User represents an Equinix Metal user type User struct { ID string `json:"id"` FirstName string `json:"first_name,omitempty"` @@ -28,6 +36,13 @@ type User struct { Emails []Email `json:"emails,omitempty"` PhoneNumber string `json:"phone_number,omitempty"` URL string `json:"href,omitempty"` + + // VPN indicates if Doorman VPN service is enabled for the user. + // + // Deprecated: As of March 31, 2021, Doorman service is no longer + // available. See https://metal.equinix.com/developers/docs/accounts/doorman/ + // for more details. + VPN bool `json:"vpn"` } func (u User) String() string { @@ -40,7 +55,28 @@ type UserServiceOp struct { } // Get method gets a user by userID -func (s *UserServiceOp) Get(userID string) (*User, *Response, error) { +func (s *UserServiceOp) List(opts *ListOptions) (users []User, resp *Response, err error) { + apiPathQuery := opts.WithQuery(usersBasePath) + + for { + subset := new(usersRoot) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + users = append(users, subset.Users...) + + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + return + } +} + +// Returns the user object for the currently logged-in user. +func (s *UserServiceOp) Current() (*User, *Response, error) { user := new(User) resp, err := s.client.DoRequest("GET", userBasePath, nil, user) @@ -51,11 +87,12 @@ func (s *UserServiceOp) Get(userID string) (*User, *Response, error) { return user, resp, err } -// Returns the user object for the currently logged-in user. -func (s *UserServiceOp) Current() (*User, *Response, error) { +func (s *UserServiceOp) Get(userID string, opts *GetOptions) (*User, *Response, error) { + endpointPath := path.Join(usersBasePath, userID) + apiPathQuery := opts.WithQuery(endpointPath) user := new(User) - resp, err := s.client.DoRequest("GET", userPath, nil, user) + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, user) if err != nil { return nil, resp, err } diff --git a/provider/packet/vendor/github.com/packethost/packngo/utils.go b/provider/packet/vendor/github.com/packethost/packngo/utils.go index 57e5ef16..42124ba4 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/utils.go +++ b/provider/packet/vendor/github.com/packethost/packngo/utils.go @@ -7,59 +7,110 @@ import ( "reflect" ) -var timestampType = reflect.TypeOf(Timestamp{}) +var ( + timestampType = reflect.TypeOf(Timestamp{}) + + // Facilities DEPRECATED Use Facilities.List + Facilities = []string{ + "yyz1", "nrt1", "atl1", "mrs1", "hkg1", "ams1", + "ewr1", "sin1", "dfw1", "lax1", "syd1", "sjc1", + "ord1", "iad1", "fra1", "sea1", "dfw2"} + + // FacilityFeatures DEPRECATED Use Facilities.List + FacilityFeatures = []string{ + "baremetal", "layer_2", "backend_transfer", "storage", "global_ipv4"} + + // UtilizationLevels DEPRECATED + UtilizationLevels = []string{"unavailable", "critical", "limited", "normal"} + + // DevicePlans DEPRECATED Use Plans.List + DevicePlans = []string{"c2.medium.x86", "g2.large.x86", + "m2.xlarge.x86", "x2.xlarge.x86", "baremetal_2a", "baremetal_2a2", + "baremetal_1", "baremetal_3", "baremetal_2", "baremetal_s", + "baremetal_0", "baremetal_1e", + } +) // Stringify creates a string representation of the provided message +// DEPRECATED This is used internally and should not be exported by packngo func Stringify(message interface{}) string { var buf bytes.Buffer v := reflect.ValueOf(message) - stringifyValue(&buf, v) + // TODO(displague) errors here are not reported + _ = stringifyValue(&buf, v) return buf.String() } // StreamToString converts a reader to a string -func StreamToString(stream io.Reader) string { +// DEPRECATED This is unused and should not be exported by packngo +func StreamToString(stream io.Reader) (string, error) { buf := new(bytes.Buffer) - buf.ReadFrom(stream) - return buf.String() + if _, err := buf.ReadFrom(stream); err != nil { + return "", err + } + return buf.String(), nil +} + +// contains tells whether a contains x. +func contains(a []string, x string) bool { + for _, n := range a { + if x == n { + return true + } + } + return false } // stringifyValue was graciously cargoculted from the goprotubuf library -func stringifyValue(w io.Writer, val reflect.Value) { +func stringifyValue(w io.Writer, val reflect.Value) error { if val.Kind() == reflect.Ptr && val.IsNil() { - w.Write([]byte("")) - return + _, err := w.Write([]byte("")) + return err } v := reflect.Indirect(val) switch v.Kind() { case reflect.String: - fmt.Fprintf(w, `"%s"`, v) + if _, err := fmt.Fprintf(w, `"%s"`, v); err != nil { + return err + } case reflect.Slice: - w.Write([]byte{'['}) + if _, err := w.Write([]byte{'['}); err != nil { + return err + } for i := 0; i < v.Len(); i++ { if i > 0 { - w.Write([]byte{' '}) + if _, err := w.Write([]byte{' '}); err != nil { + return err + } } - stringifyValue(w, v.Index(i)) + if err := stringifyValue(w, v.Index(i)); err != nil { + return err + } } - w.Write([]byte{']'}) - return + if _, err := w.Write([]byte{']'}); err != nil { + return err + } + return nil case reflect.Struct: if v.Type().Name() != "" { - w.Write([]byte(v.Type().String())) + if _, err := w.Write([]byte(v.Type().String())); err != nil { + return err + } } // special handling of Timestamp values if v.Type() == timestampType { - fmt.Fprintf(w, "{%s}", v.Interface()) - return + _, err := fmt.Fprintf(w, "{%s}", v.Interface()) + return err } - w.Write([]byte{'{'}) + if _, err := w.Write([]byte{'{'}); err != nil { + return err + } var sep bool for i := 0; i < v.NumField(); i++ { @@ -72,20 +123,34 @@ func stringifyValue(w io.Writer, val reflect.Value) { } if sep { - w.Write([]byte(", ")) + if _, err := w.Write([]byte(", ")); err != nil { + return err + } } else { sep = true } - w.Write([]byte(v.Type().Field(i).Name)) - w.Write([]byte{':'}) - stringifyValue(w, fv) + if _, err := w.Write([]byte(v.Type().Field(i).Name)); err != nil { + return err + } + if _, err := w.Write([]byte{':'}); err != nil { + return err + } + + if err := stringifyValue(w, fv); err != nil { + return err + } } - w.Write([]byte{'}'}) + if _, err := w.Write([]byte{'}'}); err != nil { + return err + } default: if v.CanInterface() { - fmt.Fprint(w, v.Interface()) + if _, err := fmt.Fprint(w, v.Interface()); err != nil { + return err + } } } + return nil } diff --git a/provider/packet/vendor/github.com/packethost/packngo/version.go b/provider/packet/vendor/github.com/packethost/packngo/version.go new file mode 100644 index 00000000..384479c7 --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/version.go @@ -0,0 +1,36 @@ +package packngo + +import "runtime/debug" + +var ( + // Version of the packngo package. Version will be updated at runtime. + Version = "(devel)" + + // UserAgent is the default HTTP User-Agent Header value that will be used by NewClient. + // init() will update the version to match the built version of packngo. + UserAgent = "packngo/(devel)" +) + +const packagePath = "github.com/packethost/packngo" + +// init finds packngo in the dependency so the package Version can be properly +// reflected in API UserAgent headers and client introspection +func init() { + bi, ok := debug.ReadBuildInfo() + if !ok { + return + } + for _, d := range bi.Deps { + if d.Path == packagePath { + Version = d.Version + if d.Replace != nil { + v := d.Replace.Version + if v != "" { + Version = v + } + } + UserAgent = "packngo/" + Version + break + } + } +} diff --git a/provider/packet/vendor/github.com/packethost/packngo/virtualcircuits.go b/provider/packet/vendor/github.com/packethost/packngo/virtualcircuits.go new file mode 100644 index 00000000..4a08ece7 --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/virtualcircuits.go @@ -0,0 +1,102 @@ +package packngo + +import "path" + +const ( + virtualCircuitBasePath = "/virtual-circuits" + + // VC is being create but not ready yet + VCStatusPending = "pending" + + // VC is ready with a VLAN + VCStatusActive = "active" + + // VC is ready without a VLAN + VCStatusWaiting = "waiting_on_customer_vlan" + + // VC is being deleted + VCStatusDeleting = "deleting" + + // not sure what the following states mean, or whether they exist + // someone from the API side could check + VCStatusActivating = "activating" + VCStatusDeactivating = "deactivating" + VCStatusActivationFailed = "activation_failed" + VCStatusDeactivationFailed = "dactivation_failed" +) + +type VirtualCircuitService interface { + Create(string, string, string, *VCCreateRequest, *GetOptions) (*VirtualCircuit, *Response, error) + Get(string, *GetOptions) (*VirtualCircuit, *Response, error) + Events(string, *GetOptions) ([]Event, *Response, error) + Delete(string) (*Response, error) + Update(string, *VCUpdateRequest, *GetOptions) (*VirtualCircuit, *Response, error) +} + +type VCUpdateRequest struct { + VirtualNetworkID *string `json:"vnid"` +} + +type VCCreateRequest struct { + VirtualNetworkID string `json:"vnid"` + NniVLAN int `json:"nni_vlan,omitempty"` + Name string `json:"name,omitempty"` +} + +type VirtualCircuitServiceOp struct { + client *Client +} + +type virtualCircuitsRoot struct { + VirtualCircuits []VirtualCircuit `json:"virtual_circuits"` + Meta meta `json:"meta"` +} + +type VirtualCircuit struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Status string `json:"status,omitempty"` + VNID int `json:"vnid,omitempty"` + NniVNID int `json:"nni_vnid,omitempty"` + NniVLAN int `json:"nni_vlan,omitempty"` + Project *Project `json:"project,omitempty"` + Port *ConnectionPort `json:"port,omitempty"` + VirtualNetwork *VirtualNetwork `json:"virtual_network,omitempty"` +} + +func (s *VirtualCircuitServiceOp) do(method, apiPathQuery string, req interface{}) (*VirtualCircuit, *Response, error) { + vc := new(VirtualCircuit) + resp, err := s.client.DoRequest(method, apiPathQuery, req, vc) + if err != nil { + return nil, resp, err + } + return vc, resp, err +} + +func (s *VirtualCircuitServiceOp) Update(vcID string, req *VCUpdateRequest, opts *GetOptions) (*VirtualCircuit, *Response, error) { + endpointPath := path.Join(virtualCircuitBasePath, vcID) + apiPathQuery := opts.WithQuery(endpointPath) + return s.do("PUT", apiPathQuery, req) +} + +func (s *VirtualCircuitServiceOp) Events(id string, opts *GetOptions) ([]Event, *Response, error) { + apiPath := path.Join(virtualCircuitBasePath, id, eventBasePath) + return listEvents(s.client, apiPath, opts) +} + +func (s *VirtualCircuitServiceOp) Get(id string, opts *GetOptions) (*VirtualCircuit, *Response, error) { + endpointPath := path.Join(virtualCircuitBasePath, id) + apiPathQuery := opts.WithQuery(endpointPath) + return s.do("GET", apiPathQuery, nil) +} + +func (s *VirtualCircuitServiceOp) Delete(id string) (*Response, error) { + apiPath := path.Join(virtualCircuitBasePath, id) + return s.client.DoRequest("DELETE", apiPath, nil, nil) +} + +func (s *VirtualCircuitServiceOp) Create(projectID, connID, portID string, request *VCCreateRequest, opts *GetOptions) (*VirtualCircuit, *Response, error) { + endpointPath := path.Join(projectBasePath, projectID, connectionBasePath, connID, portBasePath, portID, virtualCircuitBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + return s.do("POST", apiPathQuery, request) +} diff --git a/provider/packet/vendor/github.com/packethost/packngo/virtualnetworks.go b/provider/packet/vendor/github.com/packethost/packngo/virtualnetworks.go index ec0b9fc6..64299b8b 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/virtualnetworks.go +++ b/provider/packet/vendor/github.com/packethost/packngo/virtualnetworks.go @@ -1,25 +1,31 @@ package packngo import ( - "fmt" + "path" ) const virtualNetworkBasePath = "/virtual-networks" // DevicePortService handles operations on a port which belongs to a particular device type ProjectVirtualNetworkService interface { - List(projectID string) (*VirtualNetworkListResponse, *Response, error) + List(projectID string, opts *ListOptions) (*VirtualNetworkListResponse, *Response, error) Create(*VirtualNetworkCreateRequest) (*VirtualNetwork, *Response, error) + Get(string, *GetOptions) (*VirtualNetwork, *Response, error) Delete(virtualNetworkID string) (*Response, error) } type VirtualNetwork struct { - ID string `json:"id"` - Description string `json:"description,omitempty"` - VXLAN int `json:"vxlan,omitempty"` - FacilityCode string `json:"facility_code,omitempty"` - CreatedAt string `json:"created_at,omitempty"` - Href string `json:"href"` + ID string `json:"id"` + Description string `json:"description,omitempty"` // TODO: field can be null + VXLAN int `json:"vxlan,omitempty"` + FacilityCode string `json:"facility_code,omitempty"` + MetroCode string `json:"metro_code,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + Href string `json:"href"` + Project *Project `json:"assigned_to,omitempty"` + Facility *Facility `json:"facility,omitempty"` + Metro *Metro `json:"metro,omitempty"` + Instances []*Device `json:"instances,omitempty"` } type ProjectVirtualNetworkServiceOp struct { @@ -30,11 +36,12 @@ type VirtualNetworkListResponse struct { VirtualNetworks []VirtualNetwork `json:"virtual_networks"` } -func (i *ProjectVirtualNetworkServiceOp) List(projectID string) (*VirtualNetworkListResponse, *Response, error) { - path := fmt.Sprintf("%s/%s%s", projectBasePath, projectID, virtualNetworkBasePath) +func (i *ProjectVirtualNetworkServiceOp) List(projectID string, opts *ListOptions) (*VirtualNetworkListResponse, *Response, error) { + endpointPath := path.Join(projectBasePath, projectID, virtualNetworkBasePath) + apiPathQuery := opts.WithQuery(endpointPath) output := new(VirtualNetworkListResponse) - resp, err := i.client.DoRequest("GET", path, nil, output) + resp, err := i.client.DoRequest("GET", apiPathQuery, nil, output) if err != nil { return nil, nil, err } @@ -43,25 +50,45 @@ func (i *ProjectVirtualNetworkServiceOp) List(projectID string) (*VirtualNetwork } type VirtualNetworkCreateRequest struct { - ProjectID string `json:"project_id"` + // ProjectID of the project where the VLAN will be made available. + ProjectID string `json:"project_id"` + + // Description is a user supplied description of the VLAN. Description string `json:"description"` - Facility string `json:"facility"` - VXLAN int `json:"vxlan"` - VLAN int `json:"vlan"` + + // TODO: default Description is null when not specified. Permitting *string here would require changing VirtualNetwork.Description to *string too. + + // Facility in which to create the VLAN. Mutually exclusive with Metro. + Facility string `json:"facility,omitempty"` + + // Metro in which to create the VLAN. Mutually exclusive with Facility. + Metro string `json:"metro,omitempty"` + + // VXLAN is the VLAN ID. VXLAN may be specified when Metro is defined. It is remotely incremented otherwise. Must be unique per Metro. + VXLAN int `json:"vxlan,omitempty"` } -type VirtualNetworkCreateResponse struct { - VirtualNetwork VirtualNetwork `json:"virtual_networks"` +func (i *ProjectVirtualNetworkServiceOp) Get(vlanID string, opts *GetOptions) (*VirtualNetwork, *Response, error) { + endpointPath := path.Join(virtualNetworkBasePath, vlanID) + apiPathQuery := opts.WithQuery(endpointPath) + vlan := new(VirtualNetwork) + + resp, err := i.client.DoRequest("GET", apiPathQuery, nil, vlan) + if err != nil { + return nil, resp, err + } + + return vlan, resp, err } func (i *ProjectVirtualNetworkServiceOp) Create(input *VirtualNetworkCreateRequest) (*VirtualNetwork, *Response, error) { // TODO: May need to add timestamp to output from 'post' request // for the 'created_at' attribute of VirtualNetwork struct since // API response doesn't include it - path := fmt.Sprintf("%s/%s%s", projectBasePath, input.ProjectID, virtualNetworkBasePath) + apiPath := path.Join(projectBasePath, input.ProjectID, virtualNetworkBasePath) output := new(VirtualNetwork) - resp, err := i.client.DoRequest("POST", path, input, output) + resp, err := i.client.DoRequest("POST", apiPath, input, output) if err != nil { return nil, nil, err } @@ -70,9 +97,9 @@ func (i *ProjectVirtualNetworkServiceOp) Create(input *VirtualNetworkCreateReque } func (i *ProjectVirtualNetworkServiceOp) Delete(virtualNetworkID string) (*Response, error) { - path := fmt.Sprintf("%s/%s", virtualNetworkBasePath, virtualNetworkID) + apiPath := path.Join(virtualNetworkBasePath, virtualNetworkID) - resp, err := i.client.DoRequest("DELETE", path, nil, nil) + resp, err := i.client.DoRequest("DELETE", apiPath, nil, nil) if err != nil { return nil, err } diff --git a/provider/packet/vendor/github.com/packethost/packngo/volumes.go b/provider/packet/vendor/github.com/packethost/packngo/volumes.go index 772672a3..b6166787 100644 --- a/provider/packet/vendor/github.com/packethost/packngo/volumes.go +++ b/provider/packet/vendor/github.com/packethost/packngo/volumes.go @@ -1,6 +1,8 @@ package packngo -import "fmt" +import ( + "path" +) const ( volumeBasePath = "/storage" @@ -10,7 +12,7 @@ const ( // VolumeService interface defines available Volume methods type VolumeService interface { List(string, *ListOptions) ([]Volume, *Response, error) - Get(string) (*Volume, *Response, error) + Get(string, *GetOptions) (*Volume, *Response, error) Update(string, *VolumeUpdateRequest) (*Volume, *Response, error) Delete(string) (*Response, error) Create(*VolumeCreateRequest, string) (*Volume, *Response, error) @@ -20,7 +22,7 @@ type VolumeService interface { // VolumeAttachmentService defines attachment methdods type VolumeAttachmentService interface { - Get(string) (*VolumeAttachment, *Response, error) + Get(string, *GetOptions) (*VolumeAttachment, *Response, error) Create(string, string) (*VolumeAttachment, *Response, error) Delete(string) (*Response, error) } @@ -61,7 +63,7 @@ func (v Volume) String() string { return Stringify(v) } -// VolumeCreateRequest type used to create a Packet volume +// VolumeCreateRequest type used to create an Equinix Metal volume type VolumeCreateRequest struct { BillingCycle string `json:"billing_cycle"` Description string `json:"description,omitempty"` @@ -76,7 +78,7 @@ func (v VolumeCreateRequest) String() string { return Stringify(v) } -// VolumeUpdateRequest type used to update a Packet volume +// VolumeUpdateRequest type used to update an Equinix Metal volume type VolumeUpdateRequest struct { Description *string `json:"description,omitempty"` PlanID *string `json:"plan_id,omitempty"` @@ -84,7 +86,7 @@ type VolumeUpdateRequest struct { BillingCycle *string `json:"billing_cycle,omitempty"` } -// VolumeAttachment is a type from Packet API +// VolumeAttachment is a type from Equinix Metal API type VolumeAttachment struct { Href string `json:"href"` ID string `json:"id"` @@ -107,44 +109,33 @@ type VolumeServiceOp struct { } // List returns the volumes for a project -func (v *VolumeServiceOp) List(projectID string, listOpt *ListOptions) (volumes []Volume, resp *Response, err error) { - url := fmt.Sprintf("%s/%s%s", projectBasePath, projectID, volumeBasePath) - var params string - if listOpt != nil { - params = listOpt.createURL() - if params != "" { - url = fmt.Sprintf("%s?%s", url, params) - } - } - +func (v *VolumeServiceOp) List(projectID string, opts *ListOptions) (volumes []Volume, resp *Response, err error) { + endpointPath := path.Join(projectBasePath, projectID, volumeBasePath) + apiPathQuery := opts.WithQuery(endpointPath) for { subset := new(volumesRoot) - resp, err = v.client.DoRequest("GET", url, nil, subset) + resp, err = v.client.DoRequest("GET", apiPathQuery, nil, subset) if err != nil { return nil, resp, err } volumes = append(volumes, subset.Volumes...) - if subset.Meta.Next != nil && (listOpt == nil || listOpt.Page == 0) { - url = subset.Meta.Next.Href - if params != "" { - url = fmt.Sprintf("%s&%s", url, params) - } + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { continue } - return } } // Get returns a volume by id -func (v *VolumeServiceOp) Get(volumeID string) (*Volume, *Response, error) { - path := fmt.Sprintf("%s/%s?include=facility,snapshot_policies,attachments.device", volumeBasePath, volumeID) +func (v *VolumeServiceOp) Get(volumeID string, opts *GetOptions) (*Volume, *Response, error) { + endpointPath := path.Join(volumeBasePath, volumeID) + apiPathQuery := opts.WithQuery(endpointPath) volume := new(Volume) - resp, err := v.client.DoRequest("GET", path, nil, volume) + resp, err := v.client.DoRequest("GET", apiPathQuery, nil, volume) if err != nil { return nil, resp, err } @@ -154,10 +145,10 @@ func (v *VolumeServiceOp) Get(volumeID string) (*Volume, *Response, error) { // Update updates a volume func (v *VolumeServiceOp) Update(id string, updateRequest *VolumeUpdateRequest) (*Volume, *Response, error) { - path := fmt.Sprintf("%s/%s", volumeBasePath, id) + apiPath := path.Join(volumeBasePath, id) volume := new(Volume) - resp, err := v.client.DoRequest("PATCH", path, updateRequest, volume) + resp, err := v.client.DoRequest("PATCH", apiPath, updateRequest, volume) if err != nil { return nil, resp, err } @@ -167,14 +158,14 @@ func (v *VolumeServiceOp) Update(id string, updateRequest *VolumeUpdateRequest) // Delete deletes a volume func (v *VolumeServiceOp) Delete(volumeID string) (*Response, error) { - path := fmt.Sprintf("%s/%s", volumeBasePath, volumeID) + apiPath := path.Join(volumeBasePath, volumeID) - return v.client.DoRequest("DELETE", path, nil, nil) + return v.client.DoRequest("DELETE", apiPath, nil, nil) } // Create creates a new volume for a project func (v *VolumeServiceOp) Create(createRequest *VolumeCreateRequest, projectID string) (*Volume, *Response, error) { - url := fmt.Sprintf("%s/%s%s", projectBasePath, projectID, volumeBasePath) + url := path.Join(projectBasePath, projectID, volumeBasePath) volume := new(Volume) resp, err := v.client.DoRequest("POST", url, createRequest, volume) @@ -189,7 +180,7 @@ func (v *VolumeServiceOp) Create(createRequest *VolumeCreateRequest, projectID s // Create Attachment, i.e. attach volume to a device func (v *VolumeAttachmentServiceOp) Create(volumeID, deviceID string) (*VolumeAttachment, *Response, error) { - url := fmt.Sprintf("%s/%s%s", volumeBasePath, volumeID, attachmentsBasePath) + url := path.Join(volumeBasePath, volumeID, attachmentsBasePath) volAttachParam := map[string]string{ "device_id": deviceID, } @@ -203,11 +194,12 @@ func (v *VolumeAttachmentServiceOp) Create(volumeID, deviceID string) (*VolumeAt } // Get gets attachment by id -func (v *VolumeAttachmentServiceOp) Get(attachmentID string) (*VolumeAttachment, *Response, error) { - path := fmt.Sprintf("%s%s/%s", volumeBasePath, attachmentsBasePath, attachmentID) +func (v *VolumeAttachmentServiceOp) Get(attachmentID string, opts *GetOptions) (*VolumeAttachment, *Response, error) { + endpointPath := path.Join(volumeBasePath, attachmentsBasePath, attachmentID) + apiPathQuery := opts.WithQuery(endpointPath) volumeAttachment := new(VolumeAttachment) - resp, err := v.client.DoRequest("GET", path, nil, volumeAttachment) + resp, err := v.client.DoRequest("GET", apiPathQuery, nil, volumeAttachment) if err != nil { return nil, resp, err } @@ -217,23 +209,23 @@ func (v *VolumeAttachmentServiceOp) Get(attachmentID string) (*VolumeAttachment, // Delete deletes attachment by id func (v *VolumeAttachmentServiceOp) Delete(attachmentID string) (*Response, error) { - path := fmt.Sprintf("%s%s/%s", volumeBasePath, attachmentsBasePath, attachmentID) + apiPath := path.Join(volumeBasePath, attachmentsBasePath, attachmentID) - return v.client.DoRequest("DELETE", path, nil, nil) + return v.client.DoRequest("DELETE", apiPath, nil, nil) } // Lock sets a volume to "locked" -func (s *VolumeServiceOp) Lock(id string) (*Response, error) { - path := fmt.Sprintf("%s/%s", volumeBasePath, id) +func (v *VolumeServiceOp) Lock(id string) (*Response, error) { + apiPath := path.Join(volumeBasePath, id) action := lockType{Locked: true} - return s.client.DoRequest("PATCH", path, action, nil) + return v.client.DoRequest("PATCH", apiPath, action, nil) } // Unlock sets a volume to "unlocked" -func (s *VolumeServiceOp) Unlock(id string) (*Response, error) { - path := fmt.Sprintf("%s/%s", volumeBasePath, id) +func (v *VolumeServiceOp) Unlock(id string) (*Response, error) { + apiPath := path.Join(volumeBasePath, id) action := lockType{Locked: false} - return s.client.DoRequest("PATCH", path, action, nil) + return v.client.DoRequest("PATCH", apiPath, action, nil) } diff --git a/provider/packet/vendor/github.com/packethost/packngo/vpn.go b/provider/packet/vendor/github.com/packethost/packngo/vpn.go new file mode 100644 index 00000000..6ae09169 --- /dev/null +++ b/provider/packet/vendor/github.com/packethost/packngo/vpn.go @@ -0,0 +1,74 @@ +package packngo + +import "fmt" + +const vpnBasePath = "/user/vpn" + +// VPNConfig struct +// +// Deprecated: As of March 31, 2021, Doorman service is no longer +// available. See https://metal.equinix.com/developers/docs/accounts/doorman/ +// for more details. +type VPNConfig struct { + Config string `json:"config,omitempty"` +} + +// VPNService interface defines available VPN functions +// +// Deprecated: As of March 31, 2021, Doorman service is no longer +// available. See https://metal.equinix.com/developers/docs/accounts/doorman/ +// for more details. +type VPNService interface { + Enable() (*Response, error) + Disable() (*Response, error) + Get(code string, getOpt *GetOptions) (*VPNConfig, *Response, error) +} + +// VPNServiceOp implements VPNService +// +// Deprecated: As of March 31, 2021, Doorman service is no longer +// available. See https://metal.equinix.com/developers/docs/accounts/doorman/ +// for more details. +type VPNServiceOp struct { + client *Client +} + +// Enable VPN for current user +// +// Deprecated: As of March 31, 2021, Doorman service is no longer +// available. See https://metal.equinix.com/developers/docs/accounts/doorman/ +// for more details. +func (s *VPNServiceOp) Enable() (resp *Response, err error) { + return s.client.DoRequest("POST", vpnBasePath, nil, nil) +} + +// Disable VPN for current user +// +// Deprecated: As of March 31, 2021, Doorman service is no longer +// available. See https://metal.equinix.com/developers/docs/accounts/doorman/ +// for more details. +func (s *VPNServiceOp) Disable() (resp *Response, err error) { + return s.client.DoRequest("DELETE", vpnBasePath, nil, nil) + +} + +// Get returns the client vpn config for the currently logged-in user. +// +// Deprecated: As of March 31, 2021, Doorman service is no longer +// available. See https://metal.equinix.com/developers/docs/accounts/doorman/ +// for more details. +func (s *VPNServiceOp) Get(code string, opts *GetOptions) (config *VPNConfig, resp *Response, err error) { + params := urlQuery(opts) + config = &VPNConfig{} + apiPath := fmt.Sprintf("%s?code=%s", vpnBasePath, code) + if params != "" { + apiPath += params + } + + resp, err = s.client.DoRequest("GET", apiPath, nil, config) + if err != nil { + return nil, resp, err + } + + return config, resp, err +} diff --git a/provider/packet/vendor/vendor.json b/provider/packet/vendor/vendor.json index fca041e3..5f21283e 100644 --- a/provider/packet/vendor/vendor.json +++ b/provider/packet/vendor/vendor.json @@ -5,9 +5,9 @@ { "checksumSHA1": "kItpnJu5G9MVC56waIyHLoQZt/s=", "path": "github.com/packethost/packngo", - "revision": "b9cb5096f54c96687565b2f24be642483fb3fcf3", - "revisionTime": "2018-07-11T07:47:35Z" + "revision": "2e0421a3ecec0ce96df230128ee44669e8cd6e3a", + "revisionTime": "2021-05-20T00:32:48Z" } ], - "rootPath": "github.com/hashicorp/go-discover/provider/packet" + "rootPath": "github.com/hashicorp/go-discover/provider/equinixmetal" } From 98e7e5c1a0e2447cae2bae8b7bd2d0bb99606ee3 Mon Sep 17 00:00:00 2001 From: Marques Johansson Date: Fri, 18 Jun 2021 18:36:20 -0400 Subject: [PATCH 4/5] add test-infra acctest for equinixmetal Signed-off-by: Marques Johansson --- .circleci/config.yml | 11 +++++++- test/tf/equinixmetal/main.tf | 46 ++++++++++++++++++++++++++++++++ test/tf/equinixmetal/versions.tf | 10 +++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 test/tf/equinixmetal/main.tf create mode 100644 test/tf/equinixmetal/versions.tf diff --git a/.circleci/config.yml b/.circleci/config.yml index 99329304..6d051dcd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -207,7 +207,12 @@ jobs: - acctest: provider-test-infra-dir: triton provider-go-test-dir: triton - + equinixmetal-provider: + executor: go + steps: + - acctest: + provider-test-infra-dir: equinixmetal + provider-go-test-dir: equinixmetal workflows: version: 2 acceptance: @@ -253,3 +258,7 @@ workflows: requires: - go-test filters: *ignore_prs + - equinixmetal-provider: + requires: + - go-test + filters: *ignore_prs diff --git a/test/tf/equinixmetal/main.tf b/test/tf/equinixmetal/main.tf new file mode 100644 index 00000000..2dc1bcfe --- /dev/null +++ b/test/tf/equinixmetal/main.tf @@ -0,0 +1,46 @@ +provider "metal" { + version = "~> 2.8.1" +} + +provider "random" { + version = "~> 2.2.1" +} + +variable "metro" { + default = ["sv"] +} + +variable "tags" { + default = ["tag1", "tag1", "tag2", "tag3"] +} + +variable "metal_project" { + description = "Existing Equinix Metal project" +} + +resource "random_string" "vm_name_suffix" { + count = 4 + length = 8 + upper = false + special = false +} + +resource "metal_device" "discover-metal01" { + count = 4 + hostname = "go-discover.metal-device-${element(random_string.vm_name_suffix.*.result, count.index)}" + plan = "c3.small.x86" + metro = var.metro + # TF-UPGRADE-TODO: In Terraform v0.10 and earlier, it was sometimes necessary to + # force an interpolation expression to be interpreted as a list by wrapping it + # in an extra set of list brackets. That form was supported for compatibility in + # v0.11, but is no longer supported in Terraform v0.12. + # + # If the expression in the following list itself returns a list, remove the + # brackets to avoid interpretation as a list of lists. If the expression + # returns a single list item then leave it as-is and remove this TODO comment. + tags = [element(var.tags, count.index)] + operating_system = "ubuntu_18_04" + billing_cycle = "hourly" + project_id = var.metal_project +} + diff --git a/test/tf/equinixmetal/versions.tf b/test/tf/equinixmetal/versions.tf new file mode 100644 index 00000000..06f9c24a --- /dev/null +++ b/test/tf/equinixmetal/versions.tf @@ -0,0 +1,10 @@ + +terraform { + required_version = ">= 0.13" + required_providers { + metal = { + source = "equinix/metal" + version = "~> 2.1" + } + } +} From ad6477939ebc85790f1c9b7b90be3632e4fd8508 Mon Sep 17 00:00:00 2001 From: Marques Johansson Date: Wed, 7 Jul 2021 10:24:47 -0400 Subject: [PATCH 5/5] update go version to 1.16.5 Signed-off-by: Marques Johansson --- .circleci/config.yml | 2 +- go.mod | 2 +- go.sum | 6 ------ 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6d051dcd..195729f4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,7 +10,7 @@ locals: executors: go: docker: - - image: docker.mirror.hashicorp.services/circleci/golang:1.13.15 + - image: docker.mirror.hashicorp.services/circleci/golang:1.16.5 environment: - TEST_RESULTS: /tmp/test-results # path to where test results are saved diff --git a/go.mod b/go.mod index 7260e732..9d2607fe 100644 --- a/go.mod +++ b/go.mod @@ -41,4 +41,4 @@ require ( k8s.io/client-go v0.18.2 ) -go 1.12 +go 1.16 diff --git a/go.sum b/go.sum index 79195b36..15ef416d 100644 --- a/go.sum +++ b/go.sum @@ -64,7 +64,6 @@ github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= @@ -115,7 +114,6 @@ github.com/hashicorp/mdns v1.0.1 h1:XFSOubp8KWB+Jd2PDyaX5xUd5bhSP/+pTDZVDMzZJM8= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 h1:O/pT5C1Q3mVXMyuqg7yuAWUg/jMZR1/0QTzTRdNR6Uw= github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443/go.mod h1:bEpDU35nTu0ey1EXjwNwPjI9xErAsoOCmcMb9GKvyxo= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= @@ -156,10 +154,8 @@ github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/packethost/packngo v0.15.0 h1:nyiVHJAhQdt37Vf141vvm83niZHRKbBs9FmFB+QRgd4= github.com/packethost/packngo v0.15.0/go.mod h1:YrtUNN9IRjjqN6zK+cy2IYoi3EjHfoWTWxJkI1I1Vk0= @@ -264,7 +260,6 @@ gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= @@ -272,7 +267,6 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=