diff --git a/Gopkg.lock b/Gopkg.lock index d157e5d2f..69988fe9c 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -593,6 +593,13 @@ revision = "4dadeb3030eda0273a12382bb2348ffc7c9d1a39" version = "v1.0.0" +[[projects]] + digest = "1:acbfa5b756ccf1060157ab2d6d68111f0316cc77ecf372a6aedd3d0c3e6b21b4" + name = "github.com/packethost/packngo" + packages = ["."] + pruneopts = "NUT" + revision = "a02c426e4888c0bb39c3abcf249c8d048e2c6e40" + [[projects]] digest = "1:748946761cf99c8b73cef5a3c0ee3e040859dd713a20cece0d0e0dc04e6ceca7" name = "github.com/patrickmn/go-cache" @@ -1557,6 +1564,7 @@ "github.com/linode/linodego", "github.com/mattbaird/jsonpatch", "github.com/oklog/run", + "github.com/packethost/packngo", "github.com/patrickmn/go-cache", "github.com/pborman/uuid", "github.com/pmezard/go-difflib/difflib", diff --git a/Gopkg.toml b/Gopkg.toml index 7beb003f7..cefa3ce19 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -135,3 +135,8 @@ required = [ [[constraint]] name = "cloud.google.com/go" version = "0.36.0" + +[[constraint]] + name = "github.com/packethost/packngo" + revision = "a02c426e4888c0bb39c3abcf249c8d048e2c6e40" + diff --git a/examples/packet-machinedeployment.yaml b/examples/packet-machinedeployment.yaml new file mode 100644 index 000000000..172bf3eb3 --- /dev/null +++ b/examples/packet-machinedeployment.yaml @@ -0,0 +1,54 @@ +apiVersion: v1 +kind: Secret +metadata: + # If you change the namespace/name, you must also + # adjust the rbac rules + name: machine-controller-hetzner + namespace: kube-system +type: Opaque +stringData: + apiKey: << PACKET_API_KEY >> +--- +apiVersion: "cluster.k8s.io/v1alpha1" +kind: MachineDeployment +metadata: + name: packet-machinedeployment + namespace: kube-system +spec: + paused: false + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + minReadySeconds: 0 + selector: + matchLabels: + foo: bar + template: + metadata: + labels: + foo: bar + spec: + providerSpec: + value: + sshPublicKeys: + - "<< YOUR_PUBLIC_KEY >>" + cloudProvider: "packet" + cloudProviderSpec: + # If empty, can be set via PACKET_API_KEY env var + apiKey: + secretKeyRef: + namespace: kube-system + name: machine-controller-packet + key: apiKey + instanceType: "t1.small.x86" + projectID: "<< PROJECT_ID >>" + facilities: + - "ewr1" + operatingSystem: "ubuntu" + operatingSystemSpec: + distUpgradeOnBoot: false + versions: + kubelet: 1.13.1 diff --git a/pkg/cloudprovider/provider.go b/pkg/cloudprovider/provider.go index c43856de0..2fe0c6065 100644 --- a/pkg/cloudprovider/provider.go +++ b/pkg/cloudprovider/provider.go @@ -14,6 +14,7 @@ import ( "github.com/kubermatic/machine-controller/pkg/cloudprovider/provider/kubevirt" "github.com/kubermatic/machine-controller/pkg/cloudprovider/provider/linode" "github.com/kubermatic/machine-controller/pkg/cloudprovider/provider/openstack" + "github.com/kubermatic/machine-controller/pkg/cloudprovider/provider/packet" "github.com/kubermatic/machine-controller/pkg/cloudprovider/provider/vsphere" "github.com/kubermatic/machine-controller/pkg/providerconfig" ) @@ -49,6 +50,9 @@ var ( providerconfig.CloudProviderAzure: func(cvr *providerconfig.ConfigVarResolver) cloud.Provider { return azure.New(cvr) }, + providerconfig.CloudProviderPacket: func(cvr *providerconfig.ConfigVarResolver) cloud.Provider { + return packet.New(cvr) + }, providerconfig.CloudProviderFake: func(cvr *providerconfig.ConfigVarResolver) cloud.Provider { return fake.New(cvr) }, diff --git a/pkg/cloudprovider/provider/packet/provider.go b/pkg/cloudprovider/provider/packet/provider.go new file mode 100644 index 000000000..a86748d07 --- /dev/null +++ b/pkg/cloudprovider/provider/packet/provider.go @@ -0,0 +1,504 @@ +package packet + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + "strings" + + "github.com/golang/glog" + "github.com/packethost/packngo" + + "github.com/kubermatic/machine-controller/pkg/cloudprovider/cloud" + cloudprovidererrors "github.com/kubermatic/machine-controller/pkg/cloudprovider/errors" + "github.com/kubermatic/machine-controller/pkg/cloudprovider/instance" + "github.com/kubermatic/machine-controller/pkg/providerconfig" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + + "sigs.k8s.io/cluster-api/pkg/apis/cluster/common" + "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" +) + +const ( + machineUIDTag = "kubermatic-machine-controller:machine-uid" + defaultBillingCycle = "hourly" +) + +// New returns a Packet provider +func New(configVarResolver *providerconfig.ConfigVarResolver) cloud.Provider { + return &provider{configVarResolver: configVarResolver} +} + +type RawConfig struct { + APIKey providerconfig.ConfigVarString `json:"apiKey"` + ProjectID providerconfig.ConfigVarString `json:"projectID"` + BillingCycle providerconfig.ConfigVarString `json:"billingCycle"` + InstanceType providerconfig.ConfigVarString `json:"instanceType"` + Facilities []providerconfig.ConfigVarString `json:"facilities"` + OS providerconfig.ConfigVarString `json:"os"` + Tags []providerconfig.ConfigVarString `json:"tags"` +} + +type Config struct { + APIKey string + ProjectID string + BillingCycle string + InstanceType string + Facilities []string + OS string + Tags []string +} + +// because we have both Config and RawConfig, we need to have func for each +// ideally, these would be merged into one +func (c *Config) populateDefaults() { + if c.BillingCycle == "" { + c.BillingCycle = defaultBillingCycle + } +} + +func (c *RawConfig) populateDefaults() { + if c.BillingCycle.Value == "" { + c.BillingCycle.Value = defaultBillingCycle + } +} + +type provider struct { + configVarResolver *providerconfig.ConfigVarResolver +} + +func (p *provider) getConfig(s v1alpha1.ProviderSpec) (*Config, *RawConfig, *providerconfig.Config, error) { + if s.Value == nil { + return nil, nil, nil, fmt.Errorf("machine.spec.providerconfig.value is nil") + } + pconfig := providerconfig.Config{} + err := json.Unmarshal(s.Value.Raw, &pconfig) + if err != nil { + return nil, nil, nil, err + } + + rawConfig := RawConfig{} + if err = json.Unmarshal(pconfig.CloudProviderSpec.Raw, &rawConfig); err != nil { + return nil, nil, nil, err + } + + c := Config{} + c.APIKey, err = p.configVarResolver.GetConfigVarStringValueOrEnv(rawConfig.APIKey, "PACKET_API_KEY") + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get the value of \"apiKey\" field, error = %v", err) + } + c.ProjectID, err = p.configVarResolver.GetConfigVarStringValue(rawConfig.ProjectID) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get the value of \"projectID\" field, error = %v", err) + } + c.InstanceType, err = p.configVarResolver.GetConfigVarStringValue(rawConfig.InstanceType) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get the value of \"instanceType\" field, error = %v", err) + } + c.BillingCycle, err = p.configVarResolver.GetConfigVarStringValue(rawConfig.BillingCycle) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get the value of \"billingCycle\" field, error = %v", err) + } + for i, tag := range rawConfig.Tags { + tagValue, err := p.configVarResolver.GetConfigVarStringValue(tag) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to read the value for the Tag at index %d of the \"tags\" field, error = %v", i, err) + } + c.Tags = append(c.Tags, tagValue) + } + for i, facility := range rawConfig.Facilities { + facilityValue, err := p.configVarResolver.GetConfigVarStringValue(facility) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to read the value for the Tag at index %d of the \"facilities\" field, error = %v", i, err) + } + c.Facilities = append(c.Facilities, facilityValue) + } + + // ensure we have defaults + c.populateDefaults() + + return &c, &rawConfig, &pconfig, err +} + +func (p *provider) getPacketDevice(machine *v1alpha1.Machine) (*packngo.Device, *packngo.Client, error) { + c, _, _, err := p.getConfig(machine.Spec.ProviderSpec) + if err != nil { + return nil, nil, cloudprovidererrors.TerminalError{ + Reason: common.InvalidConfigurationMachineError, + Message: fmt.Sprintf("Failed to parse MachineSpec, due to %v", err), + } + } + + client := getClient(c.APIKey) + device, err := getDeviceByTag(client, c.ProjectID, generateTag(string(machine.UID))) + if err != nil { + return nil, nil, err + } + return device, client, nil +} + +func (p *provider) Validate(spec v1alpha1.MachineSpec) error { + c, _, pc, err := p.getConfig(spec.ProviderSpec) + if err != nil { + return fmt.Errorf("failed to parse config: %v", err) + } + + if c.APIKey == "" { + return errors.New("apiKey is missing") + } + if c.InstanceType == "" { + return errors.New("instanceType is missing") + } + if c.ProjectID == "" { + return errors.New("projectID is missing") + } + + _, err = getNameForOS(pc.OperatingSystem) + if err != nil { + return fmt.Errorf("invalid/not supported operating system specified %q: %v", pc.OperatingSystem, err) + } + + client := getClient(c.APIKey) + + if len(c.Facilities) == 0 || c.Facilities[0] == "" { + return fmt.Errorf("must have at least one non-blank facility") + } + + // get all valid facilities + facilities, _, err := client.Facilities.List(nil) + if err != nil { + return fmt.Errorf("failed to list facilities: %v", err) + } + // ensure our requested facilities are in those facilities + if missingFacilities := itemsNotInList(facilityProp(facilities, "Code"), c.Facilities); len(missingFacilities) > 0 { + return fmt.Errorf("unknown facilities: %s", strings.Join(missingFacilities, ",")) + } + + // get all valid plans a.k.a. instance types + plans, _, err := client.Plans.List(nil) + if err != nil { + return fmt.Errorf("failed to list instance types / plans: %v", err) + } + // ensure our requested plan is in those plans + validPlanNames := planProp(plans, "Name") + if missingPlans := itemsNotInList(validPlanNames, []string{c.InstanceType}); len(missingPlans) > 0 { + return fmt.Errorf("unknown instance type / plan: %s, acceptable plans: %s", strings.Join(missingPlans, ","), strings.Join(validPlanNames, ",")) + } + + return nil +} + +func (p *provider) Create(machine *v1alpha1.Machine, _ *cloud.MachineCreateDeleteData, userdata string) (instance.Instance, error) { + c, _, pc, err := p.getConfig(machine.Spec.ProviderSpec) + if err != nil { + return nil, cloudprovidererrors.TerminalError{ + Reason: common.InvalidConfigurationMachineError, + Message: fmt.Sprintf("Failed to parse MachineSpec, due to %v", err), + } + } + + client := getClient(c.APIKey) + + imageName, err := getNameForOS(pc.OperatingSystem) + if err != nil { + return nil, cloudprovidererrors.TerminalError{ + Reason: common.InvalidConfigurationMachineError, + Message: fmt.Sprintf("Invalid operating system specified %q, details = %v", pc.OperatingSystem, err), + } + } + + serverCreateOpts := &packngo.DeviceCreateRequest{ + Hostname: machine.Spec.Name, + UserData: userdata, + ProjectID: c.ProjectID, + Facility: c.Facilities, + BillingCycle: c.BillingCycle, + Plan: c.InstanceType, + OS: imageName, + Tags: []string{ + generateTag(string(machine.UID)), + }, + } + + device, res, err := client.Devices.Create(serverCreateOpts) + if err != nil { + return nil, packetErrorToTerminalError(err, res, "failed to create server") + } + + return &packetDevice{device: device}, nil +} + +func (p *provider) Cleanup(machine *v1alpha1.Machine, _ *cloud.MachineCreateDeleteData) (bool, error) { + instance, err := p.Get(machine) + if err != nil { + if err == cloudprovidererrors.ErrInstanceNotFound { + return true, nil + } + return false, err + } + + c, _, _, err := p.getConfig(machine.Spec.ProviderSpec) + if err != nil { + return false, cloudprovidererrors.TerminalError{ + Reason: common.InvalidConfigurationMachineError, + Message: fmt.Sprintf("Failed to parse MachineSpec, due to %v", err), + } + } + + client := getClient(c.APIKey) + + res, err := client.Devices.Delete(instance.(*packetDevice).device.ID) + if err != nil { + return false, packetErrorToTerminalError(err, res, "failed to delete the server") + } + + return false, nil +} + +func (p *provider) AddDefaults(spec v1alpha1.MachineSpec) (v1alpha1.MachineSpec, error) { + _, rawConfig, _, err := p.getConfig(spec.ProviderSpec) + if err != nil { + return spec, err + } + rawConfig.populateDefaults() + spec.ProviderSpec.Value, err = setProviderSpec(*rawConfig, spec.ProviderSpec) + if err != nil { + return spec, err + } + return spec, nil +} + +func (p *provider) Get(machine *v1alpha1.Machine) (instance.Instance, error) { + device, _, err := p.getPacketDevice(machine) + if err != nil { + return nil, err + } + if device != nil { + return &packetDevice{device: device}, nil + } + + return nil, cloudprovidererrors.ErrInstanceNotFound +} + +func (p *provider) MigrateUID(machine *v1alpha1.Machine, newID types.UID) error { + device, client, err := p.getPacketDevice(machine) + if err != nil { + return err + } + if device == nil { + glog.Infof("No instance exists for machine %s", machine.Name) + return nil + } + + // go through existing labels, make sure that no other UID label exists + tags := make([]string, 0) + for _, t := range device.Tags { + // filter out old UID tag(s) + if _, err := getTagUID(t); err != nil { + tags = append(tags, t) + } + } + + // create a new UID label + tags = append(tags, generateTag(string(newID))) + + glog.Infof("Setting UID label for machine %s", machine.Name) + dur := &packngo.DeviceUpdateRequest{ + Tags: &tags, + } + _, response, err := client.Devices.Update(device.ID, dur) + if err != nil { + return packetErrorToTerminalError(err, response, "failed to update UID label") + } + glog.Infof("Successfully set UID label for machine %s", machine.Name) + + return nil +} + +func (p *provider) GetCloudConfig(spec v1alpha1.MachineSpec) (config string, name string, err error) { + return "", "", nil +} + +func (p *provider) MachineMetricsLabels(machine *v1alpha1.Machine) (map[string]string, error) { + labels := make(map[string]string) + + c, _, _, err := p.getConfig(machine.Spec.ProviderSpec) + if err == nil { + labels["size"] = c.InstanceType + labels["facilities"] = strings.Join(c.Facilities, ",") + } + + return labels, err +} + +func (p *provider) SetMetricsForMachines(machines v1alpha1.MachineList) error { + return nil +} + +type packetDevice struct { + device *packngo.Device +} + +func (s *packetDevice) Name() string { + return s.device.Hostname +} + +func (s *packetDevice) ID() string { + return s.device.ID +} + +func (s *packetDevice) Addresses() []string { + // returns addresses in CIDR format + var addresses []string + for _, ip := range s.device.Network { + addresses = append(addresses, ip.Address) + } + + return addresses +} + +func (s *packetDevice) Status() instance.Status { + switch s.device.State { + case "provisioning": + return instance.StatusCreating + case "active": + return instance.StatusRunning + default: + return instance.StatusUnknown + } +} + +/****** +CONVENIENCE INTERNAL FUNCTIONS +******/ +func setProviderSpec(rawConfig RawConfig, s v1alpha1.ProviderSpec) (*runtime.RawExtension, error) { + if s.Value == nil { + return nil, fmt.Errorf("machine.spec.providerconfig.value is nil") + } + pconfig := providerconfig.Config{} + err := json.Unmarshal(s.Value.Raw, &pconfig) + if err != nil { + return nil, err + } + rawCloudProviderSpec, err := json.Marshal(rawConfig) + if err != nil { + return nil, err + } + pconfig.CloudProviderSpec = runtime.RawExtension{Raw: rawCloudProviderSpec} + rawPconfig, err := json.Marshal(pconfig) + if err != nil { + return nil, err + } + return &runtime.RawExtension{Raw: rawPconfig}, nil +} + +func getDeviceByTag(client *packngo.Client, projectID, tag string) (*packngo.Device, error) { + devices, response, err := client.Devices.List(projectID, nil) + if err != nil { + return nil, packetErrorToTerminalError(err, response, "failed to list devices") + } + + for _, device := range devices { + if itemInList(device.Tags, tag) { + return &device, nil + } + } + return nil, nil +} + +// given a defined Kubermatic constant for an operating system, return the canonical slug for Packet +func getNameForOS(os providerconfig.OperatingSystem) (string, error) { + switch os { + case providerconfig.OperatingSystemUbuntu: + return "ubuntu_18_04", nil + case providerconfig.OperatingSystemCentOS: + return "centos_7", nil + case providerconfig.OperatingSystemCoreos: + return "coreos_stable", nil + } + return "", providerconfig.ErrOSNotSupported +} + +func getClient(apiKey string) *packngo.Client { + return packngo.NewClientWithAuth("kubermatic", apiKey, nil) +} + +func generateTag(ID string) string { + return fmt.Sprintf("%s:%s", machineUIDTag, ID) +} +func getTagUID(tag string) (string, error) { + parts := strings.Split(tag, ":") + if len(parts) < 2 || parts[0] != machineUIDTag { + return "", fmt.Errorf("not a machine UID tag") + } + return parts[1], nil +} + +// packetErrorToTerminalError judges if the given error +// can be qualified as a "terminal" error, for more info see v1alpha1.MachineStatus +// +// if the given error doesn't qualify the error passed as an argument will be returned +func packetErrorToTerminalError(err error, response *packngo.Response, msg string) error { + prepareAndReturnError := func() error { + return fmt.Errorf("%s, due to %s", msg, err) + } + + if err != nil { + if response != nil && response.Response != nil && response.Response.StatusCode == 403 { + // authorization primitives come from MachineSpec + // thus we are setting InvalidConfigurationMachineError + return cloudprovidererrors.TerminalError{ + Reason: common.InvalidConfigurationMachineError, + Message: "A request has been rejected due to invalid credentials which were taken from the MachineSpec", + } + } + + return prepareAndReturnError() + } + + return err +} + +func itemInList(list []string, item string) bool { + for _, elm := range list { + if elm == item { + return true + } + } + return false +} + +func itemsNotInList(list, items []string) []string { + listMap := make(map[string]bool) + missing := make([]string, 0) + for _, item := range list { + listMap[item] = true + } + for _, item := range items { + if _, ok := listMap[item]; !ok { + missing = append(missing, item) + } + } + return missing +} + +func facilityProp(vs []packngo.Facility, field string) []string { + vsm := make([]string, len(vs)) + for i, v := range vs { + val := reflect.ValueOf(v) + vsm[i] = val.FieldByName(field).String() + } + return vsm +} + +func planProp(vs []packngo.Plan, field string) []string { + vsm := make([]string, len(vs)) + for i, v := range vs { + val := reflect.ValueOf(v) + vsm[i] = val.FieldByName(field).String() + } + return vsm +} diff --git a/pkg/providerconfig/types.go b/pkg/providerconfig/types.go index 7eae9366b..7387301d1 100644 --- a/pkg/providerconfig/types.go +++ b/pkg/providerconfig/types.go @@ -36,6 +36,7 @@ const ( CloudProviderDigitalocean CloudProvider = "digitalocean" CloudProviderOpenstack CloudProvider = "openstack" CloudProviderGoogle CloudProvider = "gce" + CloudProviderPacket CloudProvider = "packet" CloudProviderHetzner CloudProvider = "hetzner" CloudProviderLinode CloudProvider = "linode" CloudProviderVsphere CloudProvider = "vsphere" diff --git a/test/e2e/provisioning/all_e2e_test.go b/test/e2e/provisioning/all_e2e_test.go index 77e677711..eb7eca99b 100644 --- a/test/e2e/provisioning/all_e2e_test.go +++ b/test/e2e/provisioning/all_e2e_test.go @@ -26,6 +26,7 @@ const ( AzureManifest = "./testdata/machinedeployment-azure.yaml" GCEManifest = "./testdata/machinedeployment-gce.yaml" HZManifest = "./testdata/machinedeployment-hetzner.yaml" + PacketManifest = "./testdata/machinedeployment-packet.yaml" LinodeManifest = "./testdata/machinedeployment-linode.yaml" // vs_manifest = "./testdata/machinedeployment-vsphere.yaml" // vssip_manifest = "./testdata/machinedeployment-vsphere-static-ip.yaml" @@ -199,6 +200,33 @@ func TestHetznerProvisioningE2E(t *testing.T) { runScenarios(t, excludeSelector, params, HZManifest, fmt.Sprintf("hz-%s", *testRunIdentifier)) } +// TestPacketProvisioning - a test suite that exercises Packet provider +// by requesting nodes with different combination of container runtime type, container runtime version and the OS flavour. +func TestPacketProvisioningE2E(t *testing.T) { + t.Parallel() + + // test data + apiKey := os.Getenv("PACKET_API_KEY") + if len(apiKey) == 0 { + t.Fatal("unable to run the test suite, PACKET_API_KEY environment variable cannot be empty") + } + + projectID := os.Getenv("PACKET_PROJECT_ID") + if len(projectID) == 0 { + t.Fatal("unable to run the test suite, PACKET_PROJECT_ID environment variable cannot be empty") + } + + // Packet supports all + excludeSelector := &scenarioSelector{} + + // act + params := []string{ + fmt.Sprintf("<< PACKET_API_KEY >>=%s", apiKey), + fmt.Sprintf("<< PACKET_PROJECT_ID >>=%s", projectID), + } + runScenarios(t, excludeSelector, params, PacketManifest, fmt.Sprintf("packet-%s", *testRunIdentifier)) +} + // TestLinodeProvisioning - a test suite that exercises Linode provider // by requesting nodes with different combination of container runtime type, container runtime version and the OS flavour. // diff --git a/test/e2e/provisioning/migrateuidscenario.go b/test/e2e/provisioning/migrateuidscenario.go index 8a55246e3..74a2f8441 100644 --- a/test/e2e/provisioning/migrateuidscenario.go +++ b/test/e2e/provisioning/migrateuidscenario.go @@ -90,7 +90,7 @@ func verifyMigrateUID(kubeConfig, manifestPath string, parameters []string, time } return fmt.Errorf("failed to get machine %s before creating it: %v", machine.Name, err) } - _, err := prov.Create(machine, machineCreateDeleteData, "#cloud-config") + _, err := prov.Create(machine, machineCreateDeleteData, "#cloud-config\n") if err != nil { if i < maxTries-1 { time.Sleep(10 * time.Second) diff --git a/test/e2e/provisioning/testdata/machinedeployment-packet.yaml b/test/e2e/provisioning/testdata/machinedeployment-packet.yaml new file mode 100644 index 000000000..861a1f70d --- /dev/null +++ b/test/e2e/provisioning/testdata/machinedeployment-packet.yaml @@ -0,0 +1,37 @@ +apiVersion: "cluster.k8s.io/v1alpha1" +kind: MachineDeployment +metadata: + name: << MACHINE_NAME >> + namespace: kube-system +spec: + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + selector: + matchLabels: + name: << MACHINE_NAME >> + template: + metadata: + labels: + name: << MACHINE_NAME >> + spec: + providerSpec: + value: + sshPublicKeys: + - "<< YOUR_PUBLIC_KEY >>" + cloudProvider: "packet" + cloudProviderSpec: + apiKey: << PACKET_API_KEY >> + projectID: << PACKET_PROJECT_ID >> + instanceType: "t1.small.x86" + facilities: + - "ewr1" + operatingSystem: "<< OS_NAME >>" + operatingSystemSpec: + distUpgradeOnBoot: false + disableAutoUpdate: true + versions: + kubelet: "<< KUBERNETES_VERSION >>" diff --git a/vendor/github.com/packethost/packngo/LICENSE.txt b/vendor/github.com/packethost/packngo/LICENSE.txt new file mode 100644 index 000000000..57c50110c --- /dev/null +++ b/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/vendor/github.com/packethost/packngo/batches.go b/vendor/github.com/packethost/packngo/batches.go new file mode 100644 index 000000000..ca314b37f --- /dev/null +++ b/vendor/github.com/packethost/packngo/batches.go @@ -0,0 +1,97 @@ +package packngo + +import ( + "fmt" +) + +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"` + 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"` +} + +// BatchServiceOp implements BatchService +type BatchServiceOp struct { + client *Client +} + +// Get returns batch details +func (s *BatchServiceOp) Get(batchID string, getOpt *GetOptions) (*Batch, *Response, error) { + params := createGetOptionsURL(getOpt) + path := fmt.Sprintf("%s/%s?%s", batchBasePath, batchID, params) + batch := new(Batch) + + resp, err := s.client.DoRequest("GET", path, 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, listOpt *ListOptions) (batches []Batch, resp *Response, err error) { + params := createListOptionsURL(listOpt) + path := fmt.Sprintf("%s/%s%s?%s", projectBasePath, projectID, batchBasePath, params) + subset := new(batchesList) + resp, err = s.client.DoRequest("GET", path, 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) { + path := fmt.Sprintf("%s/%s/devices/batch", projectBasePath, projectID) + + batches := new(batchesList) + resp, err := s.client.DoRequest("POST", path, 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) { + path := fmt.Sprintf("%s/%s?remove_associated_instances=%t", batchBasePath, id, removeDevices) + + return s.client.DoRequest("DELETE", path, nil, nil) +} diff --git a/vendor/github.com/packethost/packngo/bgp_configs.go b/vendor/github.com/packethost/packngo/bgp_configs.go new file mode 100644 index 000000000..aa21c5561 --- /dev/null +++ b/vendor/github.com/packethost/packngo/bgp_configs.go @@ -0,0 +1,81 @@ +package packngo + +import "fmt" + +var bgpConfigBasePath = "/bgp-config" + +// BGPConfigService interface defines available BGP config methods +type BGPConfigService interface { + Get(projectID string, getOpt *GetOptions) (*BGPConfig, *Response, error) + Create(string, CreateBGPConfigRequest) (*Response, error) + // Delete(configID string) (resp *Response, err error) TODO: Not in Packet 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 a Packet 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) { + path := fmt.Sprintf("%s/%s%ss", projectBasePath, projectID, bgpConfigBasePath) + + resp, err := s.client.DoRequest("POST", path, request, nil) + if err != nil { + return resp, err + } + + return resp, err +} + +// Get function +func (s *BGPConfigServiceOp) Get(projectID string, getOpt *GetOptions) (bgpConfig *BGPConfig, resp *Response, err error) { + params := createGetOptionsURL(getOpt) + + path := fmt.Sprintf("%s/%s%s?%s", projectBasePath, projectID, bgpConfigBasePath, params) + + subset := new(BGPConfig) + + resp, err = s.client.DoRequest("GET", path, nil, subset) + if err != nil { + return nil, resp, err + } + + return subset, resp, err +} + +// Delete function TODO: this is not implemented in the Packet API +// func (s *BGPConfigServiceOp) Delete(configID string) (resp *Response, err error) { +// path := fmt.Sprintf("%ss/%s", bgpConfigBasePath, configID) + +// resp, err = s.client.DoRequest("DELETE", path, nil, nil) +// if err != nil { +// return resp, err +// } + +// return resp, err +// } diff --git a/vendor/github.com/packethost/packngo/bgp_sessions.go b/vendor/github.com/packethost/packngo/bgp_sessions.go new file mode 100644 index 000000000..5562488fe --- /dev/null +++ b/vendor/github.com/packethost/packngo/bgp_sessions.go @@ -0,0 +1,72 @@ +package packngo + +import "fmt" + +var bgpSessionBasePath = "/bgp/sessions" + +// 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 a Packet 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"` +} + +// 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) { + path := fmt.Sprintf("%s/%s%s", deviceBasePath, deviceID, bgpSessionBasePath) + session := new(BGPSession) + + resp, err := s.client.DoRequest("POST", path, request, session) + if err != nil { + return nil, resp, err + } + + return session, resp, err +} + +// Delete function +func (s *BGPSessionServiceOp) Delete(id string) (*Response, error) { + path := fmt.Sprintf("%s/%s", bgpSessionBasePath, id) + + return s.client.DoRequest("DELETE", path, nil, nil) +} + +// Get function +func (s *BGPSessionServiceOp) Get(id string, getOpt *GetOptions) (session *BGPSession, response *Response, err error) { + params := createGetOptionsURL(getOpt) + path := fmt.Sprintf("%s/%s?%s", bgpSessionBasePath, id, params) + session = new(BGPSession) + response, err = s.client.DoRequest("GET", path, nil, session) + if err != nil { + return nil, response, err + } + + return session, response, err +} diff --git a/vendor/github.com/packethost/packngo/billing_address.go b/vendor/github.com/packethost/packngo/billing_address.go new file mode 100644 index 000000000..93255b322 --- /dev/null +++ b/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/vendor/github.com/packethost/packngo/capacities.go b/vendor/github.com/packethost/packngo/capacities.go new file mode 100644 index 000000000..fa51413be --- /dev/null +++ b/vendor/github.com/packethost/packngo/capacities.go @@ -0,0 +1,79 @@ +package packngo + +const capacityBasePath = "/capacity" + +// CapacityService interface defines available capacity methods +type CapacityService interface { + List() (*CapacityReport, *Response, error) + Check(*CapacityInput) (*CapacityInput, *Response, error) +} + +// CapacityInput struct +type CapacityInput struct { + Servers []ServerInfo `json:"servers,omitempty"` +} + +// ServerInfo struct +type ServerInfo struct { + Facility string `json:"facility,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 +} + +// List returns a list of facilities and plans with their current capacity. +func (s *CapacityServiceOp) List() (*CapacityReport, *Response, error) { + root := new(capacityRoot) + + resp, err := s.client.DoRequest("GET", capacityBasePath, nil, root) + if err != nil { + return nil, resp, err + } + + return &root.Capacity, nil, nil +} + +// Check validates if a deploy can be fulfilled. +func (s *CapacityServiceOp) Check(input *CapacityInput) (cap *CapacityInput, resp *Response, err error) { + cap = new(CapacityInput) + resp, err = s.client.DoRequest("POST", capacityBasePath, input, cap) + return cap, resp, err +} diff --git a/vendor/github.com/packethost/packngo/connect.go b/vendor/github.com/packethost/packngo/connect.go new file mode 100644 index 000000000..28e72d1f4 --- /dev/null +++ b/vendor/github.com/packethost/packngo/connect.go @@ -0,0 +1,148 @@ +package packngo + +import "fmt" + +const ( + connectBasePath = "/packet-connect/connections" + AzureProviderID = "ed5de8e0-77a9-4d3b-9de0-65281d3aa831" +) + +type ConnectService interface { + List(string, *ListOptions) ([]Connect, *Response, error) + Get(string, string, *GetOptions) (*Connect, *Response, error) + Delete(string, string) (*Response, error) + Create(*ConnectCreateRequest) (*Connect, *Response, error) + Provision(string, string) (*Connect, *Response, error) + Deprovision(string, string, bool) (*Connect, *Response, error) +} + +type ConnectCreateRequest struct { + Name string `json:"name"` + ProjectID string `json:"project_id"` + ProviderID string `json:"provider_id"` + ProviderPayload string `json:"provider_payload"` + Facility string `json:"facility"` + PortSpeed int `json:"port_speed"` + VLAN int `json:"vlan"` + Tags []string `json:"tags,omitempty"` + Description string `json:"description,omitempty"` +} + +type Connect struct { + ID string `json:"id"` + Status string `json:"status"` + Name string `json:"name"` + ProjectID string `json:"project_id"` + ProviderID string `json:"provider_id"` + ProviderPayload string `json:"provider_payload"` + Facility string `json:"facility"` + PortSpeed int `json:"port_speed"` + VLAN int `json:"vlan"` + Description string `json:"description,omitempty"` +} + +type ConnectServiceOp struct { + client *Client +} + +type connectsRoot struct { + Connects []Connect `json:"connections"` + Meta meta `json:"meta"` +} + +func (c *ConnectServiceOp) List(projectID string, listOpt *ListOptions) (connects []Connect, resp *Response, err error) { + params := createListOptionsURL(listOpt) + + project_param := fmt.Sprintf("project_id=%s", projectID) + if params == "" { + params = project_param + } else { + params = fmt.Sprintf("%s&%s", params, project_param) + } + path := fmt.Sprintf("%s/?%s", connectBasePath, params) + + for { + subset := new(connectsRoot) + + resp, err = c.client.DoRequest("GET", path, nil, subset) + if err != nil { + return nil, resp, err + } + + connects = append(connects, subset.Connects...) + + if subset.Meta.Next != nil && (listOpt == nil || listOpt.Page == 0) { + path = subset.Meta.Next.Href + if params != "" { + path = fmt.Sprintf("%s&%s", path, params) + } + continue + } + + return + } +} + +func (c *ConnectServiceOp) Deprovision(connectID, projectID string, delete bool) (*Connect, *Response, error) { + params := fmt.Sprintf("project_id=%s&delete=%t", projectID, delete) + path := fmt.Sprintf("%s/%s/deprovision?%s", connectBasePath, connectID, params) + connect := new(Connect) + + resp, err := c.client.DoRequest("POST", path, nil, connect) + if err != nil { + return nil, resp, err + } + + return connect, resp, err +} + +func (c *ConnectServiceOp) Provision(connectID, projectID string) (*Connect, *Response, error) { + params := fmt.Sprintf("project_id=%s", projectID) + path := fmt.Sprintf("%s/%s/provision?%s", connectBasePath, connectID, params) + connect := new(Connect) + + resp, err := c.client.DoRequest("POST", path, nil, connect) + if err != nil { + return nil, resp, err + } + + return connect, resp, err +} + +func (c *ConnectServiceOp) Create(createRequest *ConnectCreateRequest) (*Connect, *Response, error) { + url := fmt.Sprintf("%s", connectBasePath) + connect := new(Connect) + + resp, err := c.client.DoRequest("POST", url, createRequest, connect) + if err != nil { + return nil, resp, err + } + + return connect, resp, err +} + +func (c *ConnectServiceOp) Get(connectID, projectID string, getOpt *GetOptions) (*Connect, *Response, error) { + params := createGetOptionsURL(getOpt) + project_param := fmt.Sprintf("project_id=%s", projectID) + if params == "" { + params = project_param + } else { + params = fmt.Sprintf("%s&%s", params, project_param) + } + path := fmt.Sprintf("%s/%s?%s", connectBasePath, connectID, params) + connect := new(Connect) + + resp, err := c.client.DoRequest("GET", path, nil, connect) + if err != nil { + return nil, resp, err + } + + return connect, resp, err +} + +func (c *ConnectServiceOp) Delete(connectID, projectID string) (*Response, error) { + path := fmt.Sprintf("%s/%s?project_id=%s", connectBasePath, connectID, + projectID) + + return c.client.DoRequest("DELETE", path, nil, nil) +} diff --git a/vendor/github.com/packethost/packngo/devices.go b/vendor/github.com/packethost/packngo/devices.go new file mode 100644 index 000000000..08251d60a --- /dev/null +++ b/vendor/github.com/packethost/packngo/devices.go @@ -0,0 +1,312 @@ +package packngo + +import ( + "encoding/json" + "fmt" +) + +const deviceBasePath = "/devices" + +// DeviceService interface defines available device methods +type DeviceService interface { + List(ProjectID string, listOpt *ListOptions) ([]Device, *Response, error) + Get(DeviceID string, getOpt *GetOptions) (*Device, *Response, error) + Create(*DeviceCreateRequest) (*Device, *Response, error) + Update(string, *DeviceUpdateRequest) (*Device, *Response, error) + Delete(string) (*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, listOpt *ListOptions) ([]BGPSession, *Response, error) + ListEvents(string, *ListOptions) ([]Event, *Response, error) +} + +type devicesRoot struct { + Devices []Device `json:"devices"` + Meta meta `json:"meta"` +} + +// DeviceRaw represents a Packet device from API +type DeviceRaw struct { + ID string `json:"id"` + Href string `json:"href,omitempty"` + Hostname string `json:"hostname,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"` + 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"` + Project *Project `json:"project,omitempty"` + ProvisionEvents []*Event `json:"provisioning_events,omitempty"` + ProvisionPer float32 `json:"provisioning_percentage,omitempty"` + UserData string `json:"userdata,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"` + 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"` +} + +type Device struct { + DeviceRaw + NetworkType string +} + +func (d *Device) UnmarshalJSON(b []byte) error { + dJSON := DeviceRaw{} + if err := json.Unmarshal(b, &dJSON); err != nil { + return err + } + d.DeviceRaw = dJSON + if len(dJSON.NetworkPorts) > 0 { + networkType, err := dJSON.GetNetworkType() + if err != nil { + return err + } + d.NetworkType = networkType + } + return nil +} + +func (d Device) String() string { + return Stringify(d) +} + +func (d DeviceRaw) GetNetworkType() (string, error) { + if len(d.NetworkPorts) == 0 { + return "", fmt.Errorf("Device has no network ports listed") + } + for _, p := range d.NetworkPorts { + if p.Name == "bond0" { + return p.NetworkType, nil + } + } + return "", fmt.Errorf("Bound port not found") +} + +// DeviceCreateRequest type used to create a Packet device +type DeviceCreateRequest struct { + Hostname string `json:"hostname"` + Plan string `json:"plan"` + Facility []string `json:"facility"` + OS string `json:"operating_system"` + BillingCycle string `json:"billing_cycle"` + ProjectID string `json:"project_id"` + UserData string `json:"userdata"` + Storage string `json:"storage,omitempty"` + Tags []string `json:"tags"` + 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 - essentialy 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"` +} + +// DeviceUpdateRequest type used to update a Packet 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"` +} + +func (d DeviceActionRequest) String() string { + return Stringify(d) +} + +// DeviceServiceOp implements DeviceService +type DeviceServiceOp struct { + client *Client +} + +// List returns devices on a project +func (s *DeviceServiceOp) List(projectID string, listOpt *ListOptions) (devices []Device, resp *Response, err error) { + listOpt = makeSureListOptionsInclude(listOpt, "facility") + params := createListOptionsURL(listOpt) + path := fmt.Sprintf("%s/%s%s?%s", projectBasePath, projectID, deviceBasePath, params) + + for { + subset := new(devicesRoot) + + resp, err = s.client.DoRequest("GET", path, 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) + } + continue + } + + return + } +} + +// Get returns a device by id +func (s *DeviceServiceOp) Get(deviceID string, getOpt *GetOptions) (*Device, *Response, error) { + getOpt = makeSureGetOptionsInclude(getOpt, "facility") + params := createGetOptionsURL(getOpt) + + path := fmt.Sprintf("%s/%s?%s", deviceBasePath, deviceID, params) + device := new(Device) + resp, err := s.client.DoRequest("GET", path, 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) + device := new(Device) + + resp, err := s.client.DoRequest("POST", path, 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) + device := new(Device) + + resp, err := s.client.DoRequest("PUT", path, updateRequest, device) + if err != nil { + return nil, resp, err + } + + return device, resp, err +} + +// Delete deletes a device +func (s *DeviceServiceOp) Delete(deviceID string) (*Response, error) { + path := fmt.Sprintf("%s/%s", deviceBasePath, deviceID) + + return s.client.DoRequest("DELETE", path, nil, nil) +} + +// Reboot reboots on a device +func (s *DeviceServiceOp) Reboot(deviceID string) (*Response, error) { + path := fmt.Sprintf("%s/%s/actions", deviceBasePath, deviceID) + action := &DeviceActionRequest{Type: "reboot"} + + return s.client.DoRequest("POST", path, action, nil) +} + +// PowerOff powers on a device +func (s *DeviceServiceOp) PowerOff(deviceID string) (*Response, error) { + path := fmt.Sprintf("%s/%s/actions", deviceBasePath, deviceID) + action := &DeviceActionRequest{Type: "power_off"} + + return s.client.DoRequest("POST", path, action, nil) +} + +// PowerOn powers on a device +func (s *DeviceServiceOp) PowerOn(deviceID string) (*Response, error) { + path := fmt.Sprintf("%s/%s/actions", deviceBasePath, deviceID) + action := &DeviceActionRequest{Type: "power_on"} + + return s.client.DoRequest("POST", path, action, nil) +} + +type lockType struct { + Locked bool `json:"locked"` +} + +// Lock sets a device to "locked" +func (s *DeviceServiceOp) Lock(deviceID string) (*Response, error) { + path := fmt.Sprintf("%s/%s", deviceBasePath, deviceID) + action := lockType{Locked: true} + + return s.client.DoRequest("PATCH", path, action, nil) +} + +// Unlock sets a device to "unlocked" +func (s *DeviceServiceOp) Unlock(deviceID string) (*Response, error) { + path := fmt.Sprintf("%s/%s", deviceBasePath, deviceID) + action := lockType{Locked: false} + + return s.client.DoRequest("PATCH", path, action, nil) +} + +// ListBGPSessions returns all BGP Sessions associated with the device +func (s *DeviceServiceOp) ListBGPSessions(deviceID string, listOpt *ListOptions) (bgpSessions []BGPSession, resp *Response, err error) { + params := createListOptionsURL(listOpt) + path := fmt.Sprintf("%s/%s%s?%s", deviceBasePath, deviceID, bgpSessionBasePath, params) + + for { + subset := new(bgpSessionsRoot) + + resp, err = s.client.DoRequest("GET", path, nil, subset) + if err != nil { + return nil, resp, err + } + + bgpSessions = append(bgpSessions, subset.Sessions...) + + if subset.Meta.Next != nil && (listOpt == nil || listOpt.Page == 0) { + path = subset.Meta.Next.Href + if params != "" { + path = fmt.Sprintf("%s&%s", path, params) + } + continue + } + return + } +} + +// ListEvents returns list of device events +func (s *DeviceServiceOp) ListEvents(deviceID string, listOpt *ListOptions) ([]Event, *Response, error) { + path := fmt.Sprintf("%s/%s%s", deviceBasePath, deviceID, eventBasePath) + + return listEvents(s.client, path, listOpt) +} diff --git a/vendor/github.com/packethost/packngo/email.go b/vendor/github.com/packethost/packngo/email.go new file mode 100644 index 000000000..acce8999b --- /dev/null +++ b/vendor/github.com/packethost/packngo/email.go @@ -0,0 +1,87 @@ +package packngo + +import "fmt" + +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, getOpt *GetOptions) (*Email, *Response, error) { + params := createGetOptionsURL(getOpt) + path := fmt.Sprintf("%s/%s?%s", emailBasePath, emailID, params) + email := new(Email) + + resp, err := s.client.DoRequest("GET", path, 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 addres from the current user account +func (s *EmailServiceOp) Delete(emailID string) (*Response, error) { + path := fmt.Sprintf("%s/%s", emailBasePath, emailID) + + resp, err := s.client.DoRequest("DELETE", path, 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) + path := fmt.Sprintf("%s/%s", emailBasePath, emailID) + + resp, err := s.client.DoRequest("PUT", path, request, email) + if err != nil { + return nil, resp, err + } + + return email, resp, err +} diff --git a/vendor/github.com/packethost/packngo/events.go b/vendor/github.com/packethost/packngo/events.go new file mode 100644 index 000000000..78ec9b7f5 --- /dev/null +++ b/vendor/github.com/packethost/packngo/events.go @@ -0,0 +1,104 @@ +package packngo + +import "fmt" + +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) { + path := fmt.Sprintf("%s/%s", eventBasePath, eventID) + return get(s.client, path, getOpt) +} + +// list helper function for all event functions +func listEvents(client *Client, path string, listOpt *ListOptions) (events []Event, resp *Response, err error) { + params := createListOptionsURL(listOpt) + path = fmt.Sprintf("%s?%s", path, params) + + for { + subset := new(eventsRoot) + + resp, err = client.DoRequest("GET", path, nil, subset) + if err != nil { + return nil, resp, err + } + + events = append(events, subset.Events...) + + if subset.Meta.Next != nil && (listOpt == nil || listOpt.Page == 0) { + path = subset.Meta.Next.Href + if params != "" { + path = fmt.Sprintf("%s&%s", path, params) + } + continue + } + + return + } + +} + +// list helper function for all event functions +/* +func listEvents(client *Client, path string, listOpt *ListOptions) ([]Event, *Response, error) { + params := createListOptionsURL(listOpt) + root := new(eventsRoot) + + path = fmt.Sprintf("%s?%s", path, params) + + resp, err := client.DoRequest("GET", path, nil, root) + if err != nil { + return nil, resp, err + } + + return root.Events, resp, err +} +*/ + +func get(client *Client, path string, getOpt *GetOptions) (*Event, *Response, error) { + params := createGetOptionsURL(getOpt) + + event := new(Event) + + path = fmt.Sprintf("%s?%s", path, params) + + resp, err := client.DoRequest("GET", path, nil, event) + if err != nil { + return nil, resp, err + } + + return event, resp, err +} diff --git a/vendor/github.com/packethost/packngo/facilities.go b/vendor/github.com/packethost/packngo/facilities.go new file mode 100644 index 000000000..fd4a7bf91 --- /dev/null +++ b/vendor/github.com/packethost/packngo/facilities.go @@ -0,0 +1,56 @@ +package packngo + +import "fmt" + +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 a Packet 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"` + 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(listOpt *ListOptions) ([]Facility, *Response, error) { + root := new(facilityRoot) + params := createListOptionsURL(listOpt) + path := fmt.Sprintf("%s?%s", facilityBasePath, params) + + resp, err := s.client.DoRequest("GET", path, nil, root) + if err != nil { + return nil, resp, err + } + + return root.Facilities, resp, err +} diff --git a/vendor/github.com/packethost/packngo/hardware_reservations.go b/vendor/github.com/packethost/packngo/hardware_reservations.go new file mode 100644 index 000000000..826a28257 --- /dev/null +++ b/vendor/github.com/packethost/packngo/hardware_reservations.go @@ -0,0 +1,99 @@ +package packngo + +import "fmt" + +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 *Client +} + +// 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, listOpt *ListOptions) (reservations []HardwareReservation, resp *Response, err error) { + root := new(hardwareReservationRoot) + params := createListOptionsURL(listOpt) + + path := fmt.Sprintf("%s/%s%s?%s", projectBasePath, projectID, hardwareReservationBasePath, params) + + for { + subset := new(hardwareReservationRoot) + + resp, err = s.client.DoRequest("GET", path, nil, root) + if err != nil { + return nil, resp, err + } + + reservations = append(reservations, root.HardwareReservations...) + + if subset.Meta.Next != nil && (listOpt == nil || listOpt.Page == 0) { + path = subset.Meta.Next.Href + if params != "" { + path = fmt.Sprintf("%s&%s", path, params) + } + continue + } + + return + } +} + +// Get returns a single hardware reservation +func (s *HardwareReservationServiceOp) Get(hardwareReservationdID string, getOpt *GetOptions) (*HardwareReservation, *Response, error) { + params := createGetOptionsURL(getOpt) + + hardwareReservation := new(HardwareReservation) + + path := fmt.Sprintf("%s/%s?%s", hardwareReservationBasePath, hardwareReservationdID, params) + + resp, err := s.client.DoRequest("GET", path, 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) + path := fmt.Sprintf("%s/%s/%s", hardwareReservationBasePath, hardwareReservationdID, "move") + body := map[string]string{} + body["project_id"] = projectID + + resp, err := s.client.DoRequest("POST", path, body, hardwareReservation) + if err != nil { + return nil, resp, err + } + + return hardwareReservation, resp, err +} diff --git a/vendor/github.com/packethost/packngo/ip.go b/vendor/github.com/packethost/packngo/ip.go new file mode 100644 index 000000000..062f16f05 --- /dev/null +++ b/vendor/github.com/packethost/packngo/ip.go @@ -0,0 +1,197 @@ +package packngo + +import ( + "fmt" +) + +const ipBasePath = "/ips" + +// 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) +} + +// ProjectIPService handles reservation of IP address blocks for a project. +type ProjectIPService interface { + Get(reservationID string, getOpt *GetOptions) (*IPAddressReservation, *Response, error) + List(projectID string) ([]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"` + Global *bool `json:"global_ip"` +} + +// 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"` +} + +// 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"` + Comments string `json:"comments"` + Facility *string `json:"facility,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) { + path := fmt.Sprintf("%s/%s", ipBasePath, resourceID) + + return client.DoRequest("DELETE", path, 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) { + path := fmt.Sprintf("%s/%s%s", deviceBasePath, deviceID, ipBasePath) + ipa := new(IPAddressAssignment) + + resp, err := i.client.DoRequest("POST", path, assignRequest, ipa) + if err != nil { + return nil, resp, err + } + + return ipa, resp, err +} + +// Get returns assignment by ID. +func (i *DeviceIPServiceOp) Get(assignmentID string, getOpt *GetOptions) (*IPAddressAssignment, *Response, error) { + params := createGetOptionsURL(getOpt) + path := fmt.Sprintf("%s/%s?%s", ipBasePath, assignmentID, params) + ipa := new(IPAddressAssignment) + + resp, err := i.client.DoRequest("GET", path, nil, ipa) + if err != nil { + return nil, resp, err + } + + return ipa, 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, getOpt *GetOptions) (*IPAddressReservation, *Response, error) { + params := createGetOptionsURL(getOpt) + path := fmt.Sprintf("%s/%s?%s", ipBasePath, reservationID, params) + ipr := new(IPAddressReservation) + + resp, err := i.client.DoRequest("GET", path, 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) ([]IPAddressReservation, *Response, error) { + path := fmt.Sprintf("%s/%s%s", projectBasePath, projectID, ipBasePath) + reservations := new(struct { + Reservations []IPAddressReservation `json:"ip_addresses"` + }) + + resp, err := i.client.DoRequest("GET", path, 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) { + path := fmt.Sprintf("%s/%s%s", projectBasePath, projectID, ipBasePath) + ipr := new(IPAddressReservation) + + resp, err := i.client.DoRequest("POST", path, 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) { + path := fmt.Sprintf("%s/%s/available?cidr=%d", ipBasePath, ipReservationID, r.CIDR) + ar := new(AvailableResponse) + + resp, err := i.client.DoRequest("GET", path, r, ar) + if err != nil { + return nil, resp, err + } + return ar.Available, resp, nil + +} diff --git a/vendor/github.com/packethost/packngo/notifications.go b/vendor/github.com/packethost/packngo/notifications.go new file mode 100644 index 000000000..051ca56f9 --- /dev/null +++ b/vendor/github.com/packethost/packngo/notifications.go @@ -0,0 +1,95 @@ +package packngo + +import "fmt" + +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, getOpt *GetOptions) (*Notification, *Response, error) { + params := createGetOptionsURL(getOpt) + + path := fmt.Sprintf("%s/%s?%s", notificationBasePath, notificationID, params) + return getNotifications(s.client, path) +} + +// Marks notification as read by ID +func (s *NotificationServiceOp) MarkAsRead(notificationID string) (*Notification, *Response, error) { + path := fmt.Sprintf("%s/%s", notificationBasePath, notificationID) + return markAsRead(s.client, path) +} + +// list helper function for all notification functions +func listNotifications(client *Client, path string, listOpt *ListOptions) ([]Notification, *Response, error) { + params := createListOptionsURL(listOpt) + + root := new(notificationsRoot) + + path = fmt.Sprintf("%s?%s", path, params) + + resp, err := client.DoRequest("GET", path, nil, root) + if err != nil { + return nil, resp, err + } + + return root.Notifications, resp, err +} + +func getNotifications(client *Client, path string) (*Notification, *Response, error) { + + notification := new(Notification) + + resp, err := client.DoRequest("GET", path, nil, notification) + if err != nil { + return nil, resp, err + } + + return notification, resp, err +} + +func markAsRead(client *Client, path string) (*Notification, *Response, error) { + + notification := new(Notification) + + resp, err := client.DoRequest("PUT", path, nil, notification) + if err != nil { + return nil, resp, err + } + + return notification, resp, err +} diff --git a/vendor/github.com/packethost/packngo/operatingsystems.go b/vendor/github.com/packethost/packngo/operatingsystems.go new file mode 100644 index 000000000..12aa1bb5b --- /dev/null +++ b/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 a Packet 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/vendor/github.com/packethost/packngo/organizations.go b/vendor/github.com/packethost/packngo/organizations.go new file mode 100644 index 000000000..03b59c601 --- /dev/null +++ b/vendor/github.com/packethost/packngo/organizations.go @@ -0,0 +1,171 @@ +package packngo + +import "fmt" + +// API documentation https://www.packet.net/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 a Packet 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 a Packet 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 a Packet 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(listOpt *ListOptions) (orgs []Organization, resp *Response, err error) { + params := createListOptionsURL(listOpt) + root := new(organizationsRoot) + + path := fmt.Sprintf("%s?%s", organizationBasePath, params) + + for { + resp, err = s.client.DoRequest("GET", path, nil, root) + if err != nil { + return nil, resp, err + } + + orgs = append(orgs, root.Organizations...) + + if root.Meta.Next != nil && (listOpt == nil || listOpt.Page == 0) { + path = root.Meta.Next.Href + if params != "" { + path = fmt.Sprintf("%s&%s", path, params) + } + continue + } + return + } +} + +// Get returns a organization by id +func (s *OrganizationServiceOp) Get(organizationID string, getOpt *GetOptions) (*Organization, *Response, error) { + params := createGetOptionsURL(getOpt) + path := fmt.Sprintf("%s/%s?%s", organizationBasePath, organizationID, params) + organization := new(Organization) + + resp, err := s.client.DoRequest("GET", path, 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) { + path := fmt.Sprintf("%s/%s", organizationBasePath, id) + organization := new(Organization) + + resp, err := s.client.DoRequest("PATCH", path, 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) { + path := fmt.Sprintf("%s/%s", organizationBasePath, organizationID) + + return s.client.DoRequest("DELETE", path, 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) + root := new(paymentMethodsRoot) + + resp, err := s.client.DoRequest("GET", url, 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) { + path := fmt.Sprintf("%s/%s%s", organizationBasePath, organizationID, eventBasePath) + + return listEvents(s.client, path, listOpt) +} diff --git a/vendor/github.com/packethost/packngo/packngo.go b/vendor/github.com/packethost/packngo/packngo.go new file mode 100644 index 000000000..47b89a29f --- /dev/null +++ b/vendor/github.com/packethost/packngo/packngo.go @@ -0,0 +1,394 @@ +package packngo + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "net/http/httputil" + "net/url" + "os" + "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" +) + +type GetOptions struct { + Includes []string + Excludes []string +} + +// 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"` + Includes []string + Excludes []string +} + +func makeSureGetOptionsInclude(g *GetOptions, s string) *GetOptions { + if g == nil { + return &GetOptions{Includes: []string{s}} + } + if !contains(g.Includes, s) { + g.Includes = append(g.Includes, s) + } + return g +} + +func makeSureListOptionsInclude(l *ListOptions, s string) *ListOptions { + if l == nil { + return &ListOptions{Includes: []string{s}} + } + if !contains(l.Includes, s) { + l.Includes = append(l.Includes, s) + } + return l +} + +func createGetOptionsURL(g *GetOptions) (url string) { + if g == nil { + return "" + } + if len(g.Includes) != 0 { + url += fmt.Sprintf("include=%s", strings.Join(g.Includes, ",")) + } + if len(g.Excludes) != 0 { + if url != "" { + url += "&" + } + url += fmt.Sprintf("exclude=%s", strings.Join(g.Excludes, ",")) + } + return + +} + +func createListOptionsURL(l *ListOptions) (url string) { + if l == nil { + return "" + } + if len(l.Includes) != 0 { + url += fmt.Sprintf("include=%s", strings.Join(l.Includes, ",")) + } + if len(l.Excludes) != 0 { + if url != "" { + url += "&" + } + url += fmt.Sprintf("exclude=%s", strings.Join(l.Excludes, ",")) + } + 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"` + 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 + + // Packet Api Objects + Plans PlanService + Users UserService + Emails EmailService + SSHKeys SSHKeyService + Devices DeviceService + Projects ProjectService + Facilities FacilityService + OperatingSystems OSService + DeviceIPs DeviceIPService + DevicePorts DevicePortService + ProjectIPs ProjectIPService + ProjectVirtualNetworks ProjectVirtualNetworkService + Volumes VolumeService + VolumeAttachments VolumeAttachmentService + SpotMarket SpotMarketService + SpotMarketRequests SpotMarketRequestService + Organizations OrganizationService + BGPSessions BGPSessionService + BGPConfig BGPConfigService + CapacityService CapacityService + Batches BatchService + TwoFactorAuth TwoFactorAuthService + VPN VPNService + HardwareReservations HardwareReservationService + Events EventService + Notifications NotificationService + Connects ConnectService +} + +// 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 + 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", 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 { + o, _ := httputil.DumpResponse(response.Response, true) + log.Printf("\n=======[RESPONSE]============\n%s\n\n", string(o)) + } + 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 { + io.Copy(w, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(v) + if err != nil { + return &response, err + } + } + } + + return &response, err +} + +// 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)) + } + 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 { + o, _ := httputil.DumpRequestOut(req, true) + log.Printf("\n=======[REQUEST]=============\n%s\n", string(o)) + } + if err != nil { + return nil, err + } + return c.Do(req, v) +} + +// NewClient initializes and returns a Client +func NewClient() (*Client, error) { + apiToken := os.Getenv(packetTokenEnvVar) + if apiToken == "" { + return nil, fmt.Errorf("you must export %s", packetTokenEnvVar) + } + 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.: Packet'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 { + // 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{} + } + + u, err := url.Parse(apiBaseURL) + if err != nil { + 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.Devices = &DeviceServiceOp{client: c} + c.Projects = &ProjectServiceOp{client: c} + c.Facilities = &FacilityServiceOp{client: c} + c.OperatingSystems = &OSServiceOp{client: c} + c.DeviceIPs = &DeviceIPServiceOp{client: c} + c.DevicePorts = &DevicePortServiceOp{client: c} + c.ProjectVirtualNetworks = &ProjectVirtualNetworkServiceOp{client: c} + c.ProjectIPs = &ProjectIPServiceOp{client: c} + c.Volumes = &VolumeServiceOp{client: c} + c.VolumeAttachments = &VolumeAttachmentServiceOp{client: c} + c.SpotMarket = &SpotMarketServiceOp{client: c} + c.BGPSessions = &BGPSessionServiceOp{client: c} + c.BGPConfig = &BGPConfigServiceOp{client: c} + c.CapacityService = &CapacityServiceOp{client: c} + c.Batches = &BatchServiceOp{client: c} + c.TwoFactorAuth = &TwoFactorAuthServiceOp{client: c} + c.VPN = &VPNServiceOp{client: c} + c.HardwareReservations = &HardwareReservationServiceOp{client: c} + c.SpotMarketRequests = &SpotMarketRequestServiceOp{client: c} + c.Events = &EventServiceOp{client: c} + c.Notifications = &NotificationServiceOp{client: c} + c.Connects = &ConnectServiceOp{client: c} + + 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 { + // 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 && len(data) > 0 { + json.Unmarshal(data, errorResponse) + } + + return errorResponse +} diff --git a/vendor/github.com/packethost/packngo/payment_methods.go b/vendor/github.com/packethost/packngo/payment_methods.go new file mode 100644 index 000000000..0dc98fa0c --- /dev/null +++ b/vendor/github.com/packethost/packngo/payment_methods.go @@ -0,0 +1,72 @@ +package packngo + +// API documentation https://www.packet.net/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 a Packet 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 a Packet 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 a Packet 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 { + client *Client +} diff --git a/vendor/github.com/packethost/packngo/plans.go b/vendor/github.com/packethost/packngo/plans.go new file mode 100644 index 000000000..36b0a2ff2 --- /dev/null +++ b/vendor/github.com/packethost/packngo/plans.go @@ -0,0 +1,126 @@ +package packngo + +import ( + "fmt" +) + +const planBasePath = "/plans" + +// PlanService interface defines available plan methods +type PlanService interface { + List(*ListOptions) ([]Plan, *Response, error) +} + +type planRoot struct { + Plans []Plan `json:"plans"` +} + +// Plan represents a Packet 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"` + DeploymentTypes []string `json:"deployment_types"` + Class string `json:"class"` + AvailableIn []Facility `json:"available_in"` +} + +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 +} + +// List method returns all available plans +func (s *PlanServiceOp) List(listOpt *ListOptions) ([]Plan, *Response, error) { + root := new(planRoot) + params := createListOptionsURL(listOpt) + path := fmt.Sprintf("%s?%s", planBasePath, params) + + resp, err := s.client.DoRequest("GET", path, nil, root) + if err != nil { + return nil, resp, err + } + + return root.Plans, resp, err +} diff --git a/vendor/github.com/packethost/packngo/ports.go b/vendor/github.com/packethost/packngo/ports.go new file mode 100644 index 000000000..6dc42eeb9 --- /dev/null +++ b/vendor/github.com/packethost/packngo/ports.go @@ -0,0 +1,301 @@ +package packngo + +import ( + "fmt" +) + +const portBasePath = "/ports" + +// 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) + DeviceToNetworkType(string, string) (*Device, error) + DeviceNetworkType(string) (string, error) + PortToLayerTwo(string) (*Port, *Response, error) + PortToLayerThree(string) (*Port, *Response, error) + DeviceToLayerTwo(string) (*Device, error) + DeviceToLayerThree(string) (*Device, error) + GetBondPort(string) (*Port, error) + GetPortByName(string, string) (*Port, error) +} + +type PortData struct { + MAC string `json:"mac"` + Bonded bool `json:"bonded"` +} + +type Port struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + Data PortData `json:"data"` + NetworkType string `json:"network_type,omitempty"` + AttachedVirtualNetworks []VirtualNetwork `json:"virtual_networks"` +} + +type AddressRequest struct { + AddressFamily int `json:"address_family"` + Public bool `json:"public"` +} + +type BackToL3Request struct { + RequestIPs []AddressRequest `json:"request_ips"` +} + +type DevicePortServiceOp struct { + client *Client +} + +type PortAssignRequest struct { + PortID string `json:"id"` + VirtualNetworkID string `json:"vnid"` +} + +type BondRequest struct { + PortID string `json:"id"` + BulkEnable bool `json:"bulk_enable"` +} + +type DisbondRequest struct { + PortID string `json:"id"` + BulkDisable bool `json:"bulk_disable"` +} + +func (i *DevicePortServiceOp) GetBondPort(deviceID string) (*Port, error) { + device, _, err := i.client.Devices.Get(deviceID, nil) + if err != nil { + return nil, err + } + for _, port := range device.NetworkPorts { + if port.Type == "NetworkBondPort" { + return &port, nil + } + } + + return nil, fmt.Errorf("No bonded port found in device %s", deviceID) +} + +func (i *DevicePortServiceOp) GetPortByName(deviceID, name string) (*Port, error) { + device, _, err := i.client.Devices.Get(deviceID, nil) + if err != nil { + return nil, err + } + for _, port := range device.NetworkPorts { + if port.Name == name { + return &port, nil + } + } + + return nil, fmt.Errorf("Port %s not found in device %s", name, deviceID) +} + +func (i *DevicePortServiceOp) Assign(par *PortAssignRequest) (*Port, *Response, error) { + path := fmt.Sprintf("%s/%s/assign", portBasePath, par.PortID) + return i.portAction(path, par) +} + +func (i *DevicePortServiceOp) Unassign(par *PortAssignRequest) (*Port, *Response, error) { + path := fmt.Sprintf("%s/%s/unassign", portBasePath, par.PortID) + return i.portAction(path, par) +} + +func (i *DevicePortServiceOp) Bond(br *BondRequest) (*Port, *Response, error) { + path := fmt.Sprintf("%s/%s/bond", portBasePath, br.PortID) + return i.portAction(path, br) +} + +func (i *DevicePortServiceOp) Disbond(dr *DisbondRequest) (*Port, *Response, error) { + path := fmt.Sprintf("%s/%s/disbond", portBasePath, dr.PortID) + return i.portAction(path, dr) +} + +func (i *DevicePortServiceOp) portAction(path string, req interface{}) (*Port, *Response, error) { + port := new(Port) + + resp, err := i.client.DoRequest("POST", path, req, port) + if err != nil { + return nil, resp, err + } + + return port, resp, err +} + +func (i *DevicePortServiceOp) PortToLayerTwo(portID string) (*Port, *Response, error) { + path := fmt.Sprintf("%s/%s/convert/layer-2", portBasePath, portID) + port := new(Port) + + resp, err := i.client.DoRequest("POST", path, nil, port) + if err != nil { + return nil, resp, err + } + + return port, resp, err +} + +func (i *DevicePortServiceOp) PortToLayerThree(portID string) (*Port, *Response, error) { + path := fmt.Sprintf("%s/%s/convert/layer-3", portBasePath, portID) + port := new(Port) + + req := BackToL3Request{ + RequestIPs: []AddressRequest{ + AddressRequest{AddressFamily: 4, Public: true}, + AddressRequest{AddressFamily: 4, Public: false}, + AddressRequest{AddressFamily: 6, Public: true}, + }, + } + + resp, err := i.client.DoRequest("POST", path, &req, port) + if err != nil { + return nil, resp, err + } + + return port, resp, err +} + +func (i *DevicePortServiceOp) DeviceNetworkType(deviceID string) (string, error) { + d, _, err := i.client.Devices.Get(deviceID, nil) + if err != nil { + return "", err + } + return d.NetworkType, nil +} + +func (i *DevicePortServiceOp) DeviceToNetworkType(deviceID string, nType string) (*Device, error) { + + d, _, err := i.client.Devices.Get(deviceID, nil) + if err != nil { + return nil, err + } + + curType := d.NetworkType + + if curType == nType { + return nil, fmt.Errorf("Device already is in state %s", nType) + } + bond0ID := "" + eth1ID := "" + for _, port := range d.NetworkPorts { + if port.Name == "bond0" { + bond0ID = port.ID + } + if port.Name == "eth1" { + eth1ID = port.ID + } + } + + if nType == "layer3" { + if curType == "layer2-individual" || curType == "layer2-bonded" { + if curType == "layer2-individual" { + _, _, err := i.client.DevicePorts.Bond( + &BondRequest{PortID: bond0ID, BulkEnable: false}) + if err != nil { + return nil, err + } + + } + _, _, err := i.client.DevicePorts.PortToLayerThree(bond0ID) + if err != nil { + return nil, err + } + } + _, _, err = i.client.DevicePorts.Bond( + &BondRequest{PortID: bond0ID, BulkEnable: true}) + if err != nil { + return nil, err + } + } + if nType == "hybrid" { + if curType == "layer2-individual" || curType == "layer2-bonded" { + if curType == "layer2-individual" { + _, _, err = i.client.DevicePorts.Bond( + &BondRequest{PortID: bond0ID, BulkEnable: false}) + if err != nil { + return nil, err + } + } + _, _, err = i.client.DevicePorts.PortToLayerThree(bond0ID) + if err != nil { + return nil, err + } + } + _, _, err := i.client.DevicePorts.Disbond( + &DisbondRequest{PortID: eth1ID, BulkDisable: false}) + if err != nil { + return nil, err + } + } + if nType == "layer2-individual" { + if curType == "hybrid" || curType == "layer3" { + _, _, err = i.client.DevicePorts.PortToLayerTwo(bond0ID) + if err != nil { + return nil, err + } + + } + _, _, err = i.client.DevicePorts.Disbond( + &DisbondRequest{PortID: bond0ID, BulkDisable: true}) + if err != nil { + return nil, err + } + } + if nType == "layer2-bonded" { + if curType == "hybrid" || curType == "layer3" { + _, _, err = i.client.DevicePorts.PortToLayerTwo(bond0ID) + if err != nil { + return nil, err + } + } + _, _, err = i.client.DevicePorts.Bond( + &BondRequest{PortID: bond0ID, BulkEnable: false}) + if err != nil { + return nil, err + } + } + + d, _, err = i.client.Devices.Get(deviceID, nil) + if err != nil { + return nil, err + } + + if d.NetworkType != nType { + return nil, fmt.Errorf( + "Failed to convert device %s from %s to %s. New type was %s", + deviceID, curType, nType, d.NetworkType) + + } + return d, err +} + +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, nil) + 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) + if err != nil { + return nil, err + } + d, _, err := i.client.Devices.Get(deviceID, nil) + return d, err + +} diff --git a/vendor/github.com/packethost/packngo/projects.go b/vendor/github.com/packethost/packngo/projects.go new file mode 100644 index 000000000..cb2649eef --- /dev/null +++ b/vendor/github.com/packethost/packngo/projects.go @@ -0,0 +1,174 @@ +package packngo + +import ( + "fmt" +) + +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) +} + +type projectsRoot struct { + Projects []Project `json:"projects"` + Meta meta `json:"meta"` +} + +// Project represents a Packet 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 a Packet 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 a Packet 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 *Client +} + +// List returns the user's projects +func (s *ProjectServiceOp) List(listOpt *ListOptions) (projects []Project, resp *Response, err error) { + params := createListOptionsURL(listOpt) + root := new(projectsRoot) + + path := fmt.Sprintf("%s?%s", projectBasePath, params) + + for { + resp, err = s.client.DoRequest("GET", path, nil, root) + if err != nil { + return nil, resp, err + } + + projects = append(projects, root.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) + } + continue + } + + return + } +} + +// Get returns a project by id +func (s *ProjectServiceOp) Get(projectID string, getOpt *GetOptions) (*Project, *Response, error) { + params := createGetOptionsURL(getOpt) + path := fmt.Sprintf("%s/%s?%s", projectBasePath, projectID, params) + project := new(Project) + resp, err := s.client.DoRequest("GET", path, 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) { + path := fmt.Sprintf("%s/%s", projectBasePath, id) + project := new(Project) + + resp, err := s.client.DoRequest("PATCH", path, 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) { + path := fmt.Sprintf("%s/%s", projectBasePath, projectID) + + return s.client.DoRequest("DELETE", path, nil, nil) +} + +// ListBGPSessions returns all BGP Sessions associated with the project +func (s *ProjectServiceOp) ListBGPSessions(projectID string, listOpt *ListOptions) (bgpSessions []BGPSession, resp *Response, err error) { + params := createListOptionsURL(listOpt) + path := fmt.Sprintf("%s/%s%s?%s", projectBasePath, projectID, bgpSessionBasePath, params) + + for { + subset := new(bgpSessionsRoot) + + resp, err = s.client.DoRequest("GET", path, nil, subset) + if err != nil { + return nil, resp, err + } + + bgpSessions = append(bgpSessions, subset.Sessions...) + + if subset.Meta.Next != nil && (listOpt == nil || listOpt.Page == 0) { + path = subset.Meta.Next.Href + if params != "" { + path = fmt.Sprintf("%s&%s", path, params) + } + continue + } + + return + } +} + +// ListEvents returns list of project events +func (s *ProjectServiceOp) ListEvents(projectID string, listOpt *ListOptions) ([]Event, *Response, error) { + path := fmt.Sprintf("%s/%s%s", projectBasePath, projectID, eventBasePath) + + return listEvents(s.client, path, listOpt) +} diff --git a/vendor/github.com/packethost/packngo/rate.go b/vendor/github.com/packethost/packngo/rate.go new file mode 100644 index 000000000..965967d45 --- /dev/null +++ b/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/vendor/github.com/packethost/packngo/spotmarket.go b/vendor/github.com/packethost/packngo/spotmarket.go new file mode 100644 index 000000000..5dfb7d559 --- /dev/null +++ b/vendor/github.com/packethost/packngo/spotmarket.go @@ -0,0 +1,39 @@ +package packngo + +const spotMarketBasePath = "/market/spot/prices" + +// SpotMarketService expooses Spot Market methods +type SpotMarketService interface { + Prices() (PriceMap, *Response, error) +} + +// SpotMarketServiceOp implements SpotMarketService +type SpotMarketServiceOp struct { + client *Client +} + +// PriceMap is a map of [facility][plan]-> float Price +type PriceMap map[string]map[string]float64 + +// Prices gets current PriceMap from the API +func (s *SpotMarketServiceOp) Prices() (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 +} diff --git a/vendor/github.com/packethost/packngo/spotmarketrequest.go b/vendor/github.com/packethost/packngo/spotmarketrequest.go new file mode 100644 index 000000000..3f5b25594 --- /dev/null +++ b/vendor/github.com/packethost/packngo/spotmarketrequest.go @@ -0,0 +1,114 @@ +package packngo + +import ( + "fmt" + "math" +) + +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"` + 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"` + Project Project `json:"project"` + Href string `json:"href"` + Plan Plan `json:"plan"` +} + +type SpotMarketRequestInstanceParameters struct { + AlwaysPXE bool `json:"always_pxe,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) { + path := fmt.Sprintf("%s/%s%s?include=devices,project,plan", projectBasePath, pID, spotMarketRequestBasePath) + cr.MaxBidPrice = roundPlus(cr.MaxBidPrice, 2) + smr := new(SpotMarketRequest) + + resp, err := s.client.DoRequest("POST", path, cr, smr) + if err != nil { + return nil, resp, err + } + + return smr, resp, err +} + +func (s *SpotMarketRequestServiceOp) List(pID string, listOpt *ListOptions) ([]SpotMarketRequest, *Response, error) { + type smrRoot struct { + SMRs []SpotMarketRequest `json:"spot_market_requests"` + } + + params := createListOptionsURL(listOpt) + path := fmt.Sprintf("%s/%s%s?%s", projectBasePath, pID, spotMarketRequestBasePath, params) + output := new(smrRoot) + + resp, err := s.client.DoRequest("GET", path, nil, output) + if err != nil { + return nil, nil, err + } + + return output.SMRs, resp, nil +} + +func (s *SpotMarketRequestServiceOp) Get(id string, getOpt *GetOptions) (*SpotMarketRequest, *Response, error) { + params := createGetOptionsURL(getOpt) + path := fmt.Sprintf("%s/%s?%s", spotMarketRequestBasePath, id, params) + smr := new(SpotMarketRequest) + + resp, err := s.client.DoRequest("GET", path, nil, &smr) + if err != nil { + return nil, resp, err + } + + return smr, resp, err +} + +func (s *SpotMarketRequestServiceOp) Delete(id string, forceDelete bool) (*Response, error) { + path := fmt.Sprintf("%s/%s", spotMarketRequestBasePath, id) + var params *map[string]bool + if forceDelete { + params = &map[string]bool{"force_termination": true} + } + return s.client.DoRequest("DELETE", path, params, nil) +} diff --git a/vendor/github.com/packethost/packngo/sshkeys.go b/vendor/github.com/packethost/packngo/sshkeys.go new file mode 100644 index 000000000..4b198f571 --- /dev/null +++ b/vendor/github.com/packethost/packngo/sshkeys.go @@ -0,0 +1,139 @@ +package packngo + +import "fmt" + +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"` + User User `json:"user,omitempty"` + 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 +func (s *SSHKeyServiceOp) ProjectList(projectID string) ([]SSHKey, *Response, error) { + return s.list(fmt.Sprintf("%s/%s%s", 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, getOpt *GetOptions) (*SSHKey, *Response, error) { + params := createGetOptionsURL(getOpt) + path := fmt.Sprintf("%s/%s?%s", sshKeyBasePath, sshKeyID, params) + sshKey := new(SSHKey) + + resp, err := s.client.DoRequest("GET", path, 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) { + path := sshKeyBasePath + if createRequest.ProjectID != "" { + path = fmt.Sprintf("%s/%s%s", projectBasePath, createRequest.ProjectID, sshKeyBasePath) + } + sshKey := new(SSHKey) + + resp, err := s.client.DoRequest("POST", path, 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") + } + path := fmt.Sprintf("%s/%s", sshKeyBasePath, id) + + sshKey := new(SSHKey) + + resp, err := s.client.DoRequest("PATCH", path, 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) { + path := fmt.Sprintf("%s/%s", sshKeyBasePath, sshKeyID) + + return s.client.DoRequest("DELETE", path, nil, nil) +} diff --git a/vendor/github.com/packethost/packngo/timestamp.go b/vendor/github.com/packethost/packngo/timestamp.go new file mode 100644 index 000000000..c3320ed62 --- /dev/null +++ b/vendor/github.com/packethost/packngo/timestamp.go @@ -0,0 +1,35 @@ +package packngo + +import ( + "strconv" + "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) + } else { + t.Time, err = time.Parse(`"`+time.RFC3339+`"`, str) + } + 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/vendor/github.com/packethost/packngo/two_factor_auth.go b/vendor/github.com/packethost/packngo/two_factor_auth.go new file mode 100644 index 000000000..5064b09fe --- /dev/null +++ b/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/vendor/github.com/packethost/packngo/user.go b/vendor/github.com/packethost/packngo/user.go new file mode 100644 index 000000000..ef4b25bb6 --- /dev/null +++ b/vendor/github.com/packethost/packngo/user.go @@ -0,0 +1,100 @@ +package packngo + +import "fmt" + +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 a Packet 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 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(listOpt *ListOptions) (users []User, resp *Response, err error) { + params := createListOptionsURL(listOpt) + path := fmt.Sprintf("%s?%s", usersBasePath, params) + + for { + subset := new(usersRoot) + + resp, err = s.client.DoRequest("GET", path, nil, subset) + if err != nil { + return nil, resp, err + } + + users = append(users, subset.Users...) + + if subset.Meta.Next != nil && (listOpt == nil || listOpt.Page == 0) { + path = subset.Meta.Next.Href + if params != "" { + path = fmt.Sprintf("%s&%s", path, params) + } + 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, getOpt *GetOptions) (*User, *Response, error) { + params := createGetOptionsURL(getOpt) + path := fmt.Sprintf("%s/%s?%s", usersBasePath, userID, params) + user := new(User) + + resp, err := s.client.DoRequest("GET", path, nil, user) + if err != nil { + return nil, resp, err + } + + return user, resp, err +} diff --git a/vendor/github.com/packethost/packngo/utils.go b/vendor/github.com/packethost/packngo/utils.go new file mode 100644 index 000000000..db67f1f40 --- /dev/null +++ b/vendor/github.com/packethost/packngo/utils.go @@ -0,0 +1,115 @@ +package packngo + +import ( + "bytes" + "fmt" + "io" + "reflect" +) + +var ( + timestampType = reflect.TypeOf(Timestamp{}) + Facilities = []string{ + "yyz1", "nrt1", "atl1", "mrs1", "hkg1", "ams1", + "ewr1", "sin1", "dfw1", "lax1", "syd1", "sjc1", + "ord1", "iad1", "fra1", "sea1", "dfw2"} + FacilityFeatures = []string{ + "baremetal", "layer_2", "backend_transfer", "storage", "global_ipv4"} + UtilizationLevels = []string{"unavailable", "critical", "limited", "normal"} + 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 +func Stringify(message interface{}) string { + var buf bytes.Buffer + v := reflect.ValueOf(message) + stringifyValue(&buf, v) + return buf.String() +} + +// StreamToString converts a reader to a string +func StreamToString(stream io.Reader) string { + buf := new(bytes.Buffer) + buf.ReadFrom(stream) + return buf.String() +} + +// 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) { + if val.Kind() == reflect.Ptr && val.IsNil() { + w.Write([]byte("")) + return + } + + v := reflect.Indirect(val) + + switch v.Kind() { + case reflect.String: + fmt.Fprintf(w, `"%s"`, v) + case reflect.Slice: + w.Write([]byte{'['}) + for i := 0; i < v.Len(); i++ { + if i > 0 { + w.Write([]byte{' '}) + } + + stringifyValue(w, v.Index(i)) + } + + w.Write([]byte{']'}) + return + case reflect.Struct: + if v.Type().Name() != "" { + w.Write([]byte(v.Type().String())) + } + + // special handling of Timestamp values + if v.Type() == timestampType { + fmt.Fprintf(w, "{%s}", v.Interface()) + return + } + + w.Write([]byte{'{'}) + + 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 { + w.Write([]byte(", ")) + } else { + sep = true + } + + w.Write([]byte(v.Type().Field(i).Name)) + w.Write([]byte{':'}) + stringifyValue(w, fv) + } + + w.Write([]byte{'}'}) + default: + if v.CanInterface() { + fmt.Fprint(w, v.Interface()) + } + } +} diff --git a/vendor/github.com/packethost/packngo/virtualnetworks.go b/vendor/github.com/packethost/packngo/virtualnetworks.go new file mode 100644 index 000000000..5f0f9d0f0 --- /dev/null +++ b/vendor/github.com/packethost/packngo/virtualnetworks.go @@ -0,0 +1,92 @@ +package packngo + +import ( + "fmt" +) + +const virtualNetworkBasePath = "/virtual-networks" + +// DevicePortService handles operations on a port which belongs to a particular device +type ProjectVirtualNetworkService interface { + List(projectID string, listOpt *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"` + Project Project `json:"assigned_to"` +} + +type ProjectVirtualNetworkServiceOp struct { + client *Client +} + +type VirtualNetworkListResponse struct { + VirtualNetworks []VirtualNetwork `json:"virtual_networks"` +} + +func (i *ProjectVirtualNetworkServiceOp) List(projectID string, listOpt *ListOptions) (*VirtualNetworkListResponse, *Response, error) { + + params := createListOptionsURL(listOpt) + path := fmt.Sprintf("%s/%s%s?%s", projectBasePath, projectID, virtualNetworkBasePath, params) + output := new(VirtualNetworkListResponse) + + resp, err := i.client.DoRequest("GET", path, nil, output) + if err != nil { + return nil, nil, err + } + + return output, resp, nil +} + +type VirtualNetworkCreateRequest struct { + ProjectID string `json:"project_id"` + Description string `json:"description"` + Facility string `json:"facility"` +} + +func (i *ProjectVirtualNetworkServiceOp) Get(vlanID string, getOpt *GetOptions) (*VirtualNetwork, *Response, error) { + params := createGetOptionsURL(getOpt) + path := fmt.Sprintf("%s/%s?%s", virtualNetworkBasePath, vlanID, params) + vlan := new(VirtualNetwork) + + resp, err := i.client.DoRequest("GET", path, 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) + output := new(VirtualNetwork) + + resp, err := i.client.DoRequest("POST", path, input, output) + if err != nil { + return nil, nil, err + } + + return output, resp, nil +} + +func (i *ProjectVirtualNetworkServiceOp) Delete(virtualNetworkID string) (*Response, error) { + path := fmt.Sprintf("%s/%s", virtualNetworkBasePath, virtualNetworkID) + + resp, err := i.client.DoRequest("DELETE", path, nil, nil) + if err != nil { + return nil, err + } + + return resp, nil +} diff --git a/vendor/github.com/packethost/packngo/volumes.go b/vendor/github.com/packethost/packngo/volumes.go new file mode 100644 index 000000000..ebaaddf1c --- /dev/null +++ b/vendor/github.com/packethost/packngo/volumes.go @@ -0,0 +1,238 @@ +package packngo + +import ( + "fmt" +) + +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 a Packet 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 a Packet 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 Packet 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, listOpt *ListOptions) (volumes []Volume, resp *Response, err error) { + params := createListOptionsURL(listOpt) + path := fmt.Sprintf("%s/%s%s?%s", projectBasePath, projectID, volumeBasePath, params) + + for { + subset := new(volumesRoot) + + resp, err = v.client.DoRequest("GET", path, nil, subset) + if err != nil { + return nil, resp, err + } + + volumes = append(volumes, subset.Volumes...) + + if subset.Meta.Next != nil && (listOpt == nil || listOpt.Page == 0) { + path = subset.Meta.Next.Href + if params != "" { + path = fmt.Sprintf("%s&%s", path, params) + } + continue + } + + return + } +} + +// Get returns a volume by id +func (v *VolumeServiceOp) Get(volumeID string, getOpt *GetOptions) (*Volume, *Response, error) { + params := createGetOptionsURL(getOpt) + path := fmt.Sprintf("%s/%s?%s", volumeBasePath, volumeID, params) + volume := new(Volume) + + resp, err := v.client.DoRequest("GET", path, 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) { + path := fmt.Sprintf("%s/%s", volumeBasePath, id) + volume := new(Volume) + + resp, err := v.client.DoRequest("PATCH", path, 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) { + path := fmt.Sprintf("%s/%s", volumeBasePath, volumeID) + + return v.client.DoRequest("DELETE", path, 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) + 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 := fmt.Sprintf("%s/%s%s", 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, getOpt *GetOptions) (*VolumeAttachment, *Response, error) { + params := createGetOptionsURL(getOpt) + + path := fmt.Sprintf("%s%s/%s?%s", volumeBasePath, attachmentsBasePath, attachmentID, params) + volumeAttachment := new(VolumeAttachment) + + resp, err := v.client.DoRequest("GET", path, 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) { + path := fmt.Sprintf("%s%s/%s", volumeBasePath, attachmentsBasePath, attachmentID) + + return v.client.DoRequest("DELETE", path, nil, nil) +} + +// Lock sets a volume to "locked" +func (s *VolumeServiceOp) Lock(id string) (*Response, error) { + path := fmt.Sprintf("%s/%s", volumeBasePath, id) + action := lockType{Locked: true} + + return s.client.DoRequest("PATCH", path, action, nil) +} + +// Unlock sets a volume to "unlocked" +func (s *VolumeServiceOp) Unlock(id string) (*Response, error) { + path := fmt.Sprintf("%s/%s", volumeBasePath, id) + action := lockType{Locked: false} + + return s.client.DoRequest("PATCH", path, action, nil) +} diff --git a/vendor/github.com/packethost/packngo/vpn.go b/vendor/github.com/packethost/packngo/vpn.go new file mode 100644 index 000000000..f228f7d43 --- /dev/null +++ b/vendor/github.com/packethost/packngo/vpn.go @@ -0,0 +1,50 @@ +package packngo + +import "fmt" + +const vpnBasePath = "/user/vpn" + +// VPNConfig struct +type VPNConfig struct { + Config string `json:"config,omitempty"` +} + +// VPNService interface defines available VPN functions +type VPNService interface { + Enable() (*Response, error) + Disable() (*Response, error) + Get(code string, getOpt *GetOptions) (*VPNConfig, *Response, error) +} + +// VPNServiceOp implements VPNService +type VPNServiceOp struct { + client *Client +} + +// Enable VPN for current user +func (s *VPNServiceOp) Enable() (resp *Response, err error) { + return s.client.DoRequest("POST", vpnBasePath, nil, nil) +} + +// Disable VPN for current user +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. +func (s *VPNServiceOp) Get(code string, getOpt *GetOptions) (config *VPNConfig, resp *Response, err error) { + params := createGetOptionsURL(getOpt) + config = &VPNConfig{} + path := fmt.Sprintf("%s?code=%s", vpnBasePath, code) + if params != "" { + path += params + } + + resp, err = s.client.DoRequest("GET", path, nil, config) + if err != nil { + return nil, resp, err + } + + return config, resp, err +}