diff --git a/examples/packet.yml b/examples/packet.yml new file mode 100644 index 000000000..1d218caa9 --- /dev/null +++ b/examples/packet.yml @@ -0,0 +1,50 @@ +kernel: + image: "mobylinux/kernel:4.9.x" + cmdline: "console=ttyS1 page_poison=1" +init: + - mobylinux/init:e10e2efc1b78ef41d196175cbc07e069391f406e + - mobylinux/runc:b0fb122e10dbb7e4e45115177a61a3f8d68c19a9 + - mobylinux/containerd:18eaf72f3f4f9a9f29ca1951f66df701f873060b + - mobylinux/ca-certificates:eabc5a6e59f05aa91529d80e9a595b85b046f935 +onboot: + - name: sysctl + image: "mobylinux/sysctl:2cf2f9d5b4d314ba1bfc22b2fe931924af666d8c" + net: host + pid: host + ipc: host + capabilities: + - CAP_SYS_ADMIN +services: + - name: rngd + image: "mobylinux/rngd:3dad6dd43270fa632ac031e99d1947f20b22eec9@sha256:1c93c1db7196f6f71f8e300bc1d15f0376dd18e8891c8789d77c8ff19f3a9a92" + capabilities: + - CAP_SYS_ADMIN + oomScoreAdj: -800 + - name: dhcpcd + image: "mobylinux/dhcpcd:0d4012269cb142972fed8542fbdc3ff5a7b695cd" + binds: + - /var:/var + - /tmp:/etc + capabilities: + - CAP_NET_ADMIN + - CAP_NET_BIND_SERVICE + - CAP_NET_RAW + net: host + oomScoreAdj: -800 + - name: sshd + image: "mobylinux/sshd:160631d59fffc13d523ff7f09b3b49538d34b9cd" + capabilities: + - all + net: host + pid: host + binds: + - /root/.ssh:/root/.ssh + - /etc/resolv.conf:/etc/resolv.conf +trust: + image: + - mobylinux/kernel +files: + - path: root/.ssh/authorized_keys + contents: '#your ssh key here' +outputs: + - format: kernel+initrd diff --git a/src/cmd/moby/run.go b/src/cmd/moby/run.go index dc5a48882..d0564b81a 100644 --- a/src/cmd/moby/run.go +++ b/src/cmd/moby/run.go @@ -18,6 +18,7 @@ func runUsage() { fmt.Printf(" hyperkit [macOS]\n") fmt.Printf(" qemu [linux]\n") fmt.Printf(" vmware\n") + fmt.Printf(" packet\n") fmt.Printf("\n") fmt.Printf("'options' are the backend specific options.\n") fmt.Printf("See 'moby run [backend] --help' for details.\n\n") @@ -43,6 +44,8 @@ func run(args []string) { runGcp(args[1:]) case "qemu": runQemu(args[1:]) + case "packet": + runPacket(args[1:]) default: switch runtime.GOOS { case "darwin": diff --git a/src/cmd/moby/run_packet.go b/src/cmd/moby/run_packet.go new file mode 100644 index 000000000..080b4821d --- /dev/null +++ b/src/cmd/moby/run_packet.go @@ -0,0 +1,113 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + log "github.com/Sirupsen/logrus" + "github.com/packethost/packngo" + "net/http" + "os" +) + +const ( + packetDefaultZone = "ams1" + packetDefaultMachine = "baremetal_0" + packetDefaultHostname = "moby" + packetBaseURL = "PACKET_BASE_URL" + packetZoneVar = "PACKET_ZONE" + packetMachineVar = "PACKET_MACHINE" + packetAPIKeyVar = "PACKET_API_KEY" + packetProjectIDVar = "PACKET_PROJECT_ID" + packetHostnameVar = "PACKET_HOSTNAME" + packetNameVar = "PACKET_NAME" +) + +// ValidateHTTPURL does a sanity check that a URL returns a 200 or 300 response +func ValidateHTTPURL(url string) { + log.Printf("Validating URL: %s", url) + resp, err := http.Head(url) + if err != nil { + log.Fatal(err) + } + if resp.StatusCode >= 400 { + log.Fatal("Got a non 200- or 300- HTTP response code: %s", resp) + } + log.Printf("OK: %d response code", resp.StatusCode) +} + +// Process the run arguments and execute run +func runPacket(args []string) { + packetCmd := flag.NewFlagSet("packet", flag.ExitOnError) + packetCmd.Usage = func() { + fmt.Printf("USAGE: %s run packet [options] [name]\n\n", os.Args[0]) + fmt.Printf("Options:\n\n") + packetCmd.PrintDefaults() + } + baseURLFlag := packetCmd.String("base-url", "", "Base URL that the kernel and initrd are served from.") + zoneFlag := packetCmd.String("zone", packetDefaultZone, "Packet Zone") + machineFlag := packetCmd.String("machine", packetDefaultMachine, "Packet Machine Type") + apiKeyFlag := packetCmd.String("api-key", "", "Packet API key") + projectFlag := packetCmd.String("project-id", "", "Packet Project ID") + hostNameFlag := packetCmd.String("hostname", packetDefaultHostname, "Hostname of new instance") + nameFlag := packetCmd.String("img-name", "", "Overrides the prefix used to identify the files. Defaults to [name]") + if err := packetCmd.Parse(args); err != nil { + log.Fatal("Unable to parse args") + } + + remArgs := packetCmd.Args() + prefix := "packet" + if len(remArgs) > 0 { + prefix = remArgs[0] + } + + url := getStringValue(packetBaseURL, *baseURLFlag, "") + if url == "" { + log.Fatal("Need to specify a value for --base-url where the images are hosted. This URL should contain /%s-bzImage and /%s-initrd.img") + } + facility := getStringValue(packetZoneVar, *zoneFlag, "") + plan := getStringValue(packetMachineVar, *machineFlag, defaultMachine) + apiKey := getStringValue(packetAPIKeyVar, *apiKeyFlag, "") + if apiKey == "" { + log.Fatal("Must specify a Packet.net API key with --api-key") + } + projectID := getStringValue(packetProjectIDVar, *projectFlag, "") + if projectID == "" { + log.Fatal("Must specify a Packet.net Project ID with --project-id") + } + hostname := getStringValue(packetHostnameVar, *hostNameFlag, "") + name := getStringValue(packetNameVar, *nameFlag, prefix) + osType := "custom_ipxe" + billing := "hourly" + userData := fmt.Sprintf("#!ipxe\n\ndhcp\nset base-url %s\nset kernel-params ip=dhcp nomodeset ro serial console=ttyS1,115200\nkernel ${base-url}/%s-bzImage ${kernel-params}\ninitrd ${base-url}/%s-initrd.img\nboot", url, name, name) + log.Debugf("Using userData of:\n%s\n", userData) + initrdURL := fmt.Sprintf("%s/%s-initrd.img", url, name) + kernelURL := fmt.Sprintf("%s/%s-bzImage", url, name) + ValidateHTTPURL(kernelURL) + ValidateHTTPURL(initrdURL) + client := packngo.NewClient("", apiKey, nil) + tags := []string{} + req := packngo.DeviceCreateRequest{ + HostName: hostname, + Plan: plan, + Facility: facility, + OS: osType, + BillingCycle: billing, + ProjectID: projectID, + UserData: userData, + Tags: tags, + } + d, _, err := client.Devices.Create(&req) + if err != nil { + log.Fatal(err) + } + b, err := json.MarshalIndent(d, "", " ") + if err != nil { + log.Fatal(err) + } + // log response json if in verbose mode + log.Debugf("%s\n", string(b)) + // TODO: poll events api for bringup (requires extpacknogo) + // TODO: connect to serial console (requires API extension to get SSH URI) + // TODO: add ssh keys via API registered keys +} diff --git a/vendor.conf b/vendor.conf index f0b7863f5..a8de5e1f5 100644 --- a/vendor.conf +++ b/vendor.conf @@ -23,6 +23,7 @@ github.com/mattn/go-colorable d228849 github.com/mitchellh/go-ps 4fdf99ab29366514c69ccccddab5dc58b8d84062 github.com/opencontainers/runtime-spec d094a5c9c1997ab086197b57e9378fabed394d92 github.com/pkg/errors ff09b135c25aae272398c51a07235b90a75aa4f0 +github.com/packethost/packngo 91d54000aa56874149d348a884ba083c41d38091 github.com/rneugeba/iso9660wrap 4606f848a055435cdef85305960b0e1bb788d506 github.com/satori/go.uuid b061729afc07e77a8aa4fad0a2fd840958f1942a github.com/spf13/afero 9be650865eab0c12963d8753212f4f9c66cdcf12 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/README.md b/vendor/github.com/packethost/packngo/README.md new file mode 100644 index 000000000..d359a10d6 --- /dev/null +++ b/vendor/github.com/packethost/packngo/README.md @@ -0,0 +1,9 @@ +# packngo +Packet Go Api Client + +![](https://www.packet.net/media/labs/images/1679091c5a880faf6fb5e6087eb1b2dc/ULY7-hero.png) + +Committing +---------- + +Before committing, it's a good idea to run `gofmt -w *.go`. ([gofmt](https://golang.org/cmd/gofmt/)) diff --git a/vendor/github.com/packethost/packngo/devices.go b/vendor/github.com/packethost/packngo/devices.go new file mode 100644 index 000000000..f411bf65c --- /dev/null +++ b/vendor/github.com/packethost/packngo/devices.go @@ -0,0 +1,223 @@ +package packngo + +import "fmt" + +const deviceBasePath = "/devices" + +// DeviceService interface defines available device methods +type DeviceService interface { + List(ProjectID string) ([]Device, *Response, error) + Get(string) (*Device, *Response, error) + Create(*DeviceCreateRequest) (*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) +} + +type devicesRoot struct { + Devices []Device `json:"devices"` +} + +// Device represents a Packet device +type Device 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"` + Tags []string `json:"tags,omitempty"` + Network []*IPAddress `json:"ip_addresses"` + OS *OS `json:"operating_system,omitempty"` + Plan *Plan `json:"plan,omitempty"` + Facility *Facility `json:"facility,omitempty"` + Project *Project `json:"project,omitempty"` + ProvisionPer float32 `json:"provisioning_percentage,omitempty"` +} + +func (d Device) String() string { + return Stringify(d) +} + +// 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"` + Tags []string `json:"tags"` +} + +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) ([]Device, *Response, error) { + path := fmt.Sprintf("%s/%s/devices?include=facility", projectBasePath, projectID) + + req, err := s.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + root := new(devicesRoot) + resp, err := s.client.Do(req, root) + if err != nil { + return nil, resp, err + } + + return root.Devices, resp, err +} + +// Get returns a device by id +func (s *DeviceServiceOp) Get(deviceID string) (*Device, *Response, error) { + path := fmt.Sprintf("%s/%s?include=facility", deviceBasePath, deviceID) + + req, err := s.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + device := new(Device) + resp, err := s.client.Do(req, 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/devices", projectBasePath, createRequest.ProjectID) + + req, err := s.client.NewRequest("POST", path, createRequest) + if err != nil { + return nil, nil, err + } + + device := new(Device) + resp, err := s.client.Do(req, 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) + + req, err := s.client.NewRequest("DELETE", path, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + + return resp, err +} + +// 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"} + req, err := s.client.NewRequest("POST", path, action) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + + return resp, err +} + +// 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"} + req, err := s.client.NewRequest("POST", path, action) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + + return resp, err +} + +// 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"} + req, err := s.client.NewRequest("POST", path, action) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + + return resp, err +} + +type lockDeviceType 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 := lockDeviceType{Locked: true} + req, err := s.client.NewRequest("PATCH", path, action) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + + return resp, err + +} + +// Unlock sets a device to "locked" +func (s *DeviceServiceOp) Unlock(deviceID string) (*Response, error) { + path := fmt.Sprintf("%s/%s", deviceBasePath, deviceID) + + action := lockDeviceType{Locked: false} + req, err := s.client.NewRequest("PATCH", path, action) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + + return resp, err +} diff --git a/vendor/github.com/packethost/packngo/email.go b/vendor/github.com/packethost/packngo/email.go new file mode 100644 index 000000000..ef53ee4bb --- /dev/null +++ b/vendor/github.com/packethost/packngo/email.go @@ -0,0 +1,41 @@ +package packngo + +const emailBasePath = "/emails" + +// EmailService interface defines available email methods +type EmailService interface { + Get(string) (*Email, *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) (*Email, *Response, error) { + req, err := s.client.NewRequest("GET", emailBasePath, nil) + if err != nil { + return nil, nil, err + } + + email := new(Email) + resp, err := s.client.Do(req, email) + if err != nil { + return nil, resp, err + } + + return email, 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..42a3c7cb1 --- /dev/null +++ b/vendor/github.com/packethost/packngo/facilities.go @@ -0,0 +1,56 @@ +package packngo + +const facilityBasePath = "/facilities" + +// FacilityService interface defines available facility methods +type FacilityService interface { + List() ([]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 available Packet facilities +func (s *FacilityServiceOp) List() ([]Facility, *Response, error) { + req, err := s.client.NewRequest("GET", facilityBasePath, nil) + if err != nil { + return nil, nil, err + } + + root := new(facilityRoot) + resp, err := s.client.Do(req, root) + if err != nil { + return nil, resp, err + } + + return root.Facilities, 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..312b23f09 --- /dev/null +++ b/vendor/github.com/packethost/packngo/ip.go @@ -0,0 +1,204 @@ +package packngo + +import "fmt" + +const ipBasePath = "/ips" + +// IPService interface defines available IP methods +type IPService interface { + Assign(deviceID string, assignRequest *IPAddressAssignRequest) (*IPAddress, *Response, error) + Unassign(ipAddressID string) (*Response, error) + Get(ipAddressID string) (*IPAddress, *Response, error) +} + +// IPAddress represents a ip address +type IPAddress 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"` + AssignedTo map[string]string `json:"assigned_to"` + Created string `json:"created_at,omitempty"` + Updated string `json:"updated_at,omitempty"` + Href string `json:"href"` + Facility Facility `json:"facility,omitempty"` +} + +// IPAddressAssignRequest represents the body if a ip assign request +type IPAddressAssignRequest struct { + Address string `json:"address"` +} + +func (i IPAddress) String() string { + return Stringify(i) +} + +// IPServiceOp implements IPService +type IPServiceOp struct { + client *Client +} + +// Get returns IpAddress by ID +func (i *IPServiceOp) Get(ipAddressID string) (*IPAddress, *Response, error) { + path := fmt.Sprintf("%s/%s", ipBasePath, ipAddressID) + + req, err := i.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + ip := new(IPAddress) + resp, err := i.client.Do(req, ip) + if err != nil { + return nil, resp, err + } + + return ip, resp, err +} + +// Unassign unassigns an IP address record. 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 *IPServiceOp) Unassign(ipAddressID string) (*Response, error) { + path := fmt.Sprintf("%s/%s", ipBasePath, ipAddressID) + + req, err := i.client.NewRequest("DELETE", path, nil) + if err != nil { + return nil, err + } + + resp, err := i.client.Do(req, nil) + return resp, err +} + +// 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 *IPServiceOp) Assign(deviceID string, assignRequest *IPAddressAssignRequest) (*IPAddress, *Response, error) { + path := fmt.Sprintf("%s/%s%s", deviceBasePath, deviceID, ipBasePath) + + req, err := i.client.NewRequest("POST", path, assignRequest) + + ip := new(IPAddress) + resp, err := i.client.Do(req, ip) + if err != nil { + return nil, resp, err + } + + return ip, resp, err +} + +// IP RESERVATIONS API + +// IPReservationService interface defines available IPReservation methods +type IPReservationService interface { + List(projectID string) ([]IPReservation, *Response, error) + RequestMore(projectID string, ipReservationReq *IPReservationRequest) (*IPReservation, *Response, error) + Get(ipReservationID string) (*IPReservation, *Response, error) + Remove(ipReservationID string) (*Response, error) +} + +// IPReservationServiceOp implements the IPReservationService interface +type IPReservationServiceOp struct { + client *Client +} + +// IPReservationRequest represents the body of a reservation request +type IPReservationRequest struct { + Type string `json:"type"` + Quantity int `json:"quantity"` + Comments string `json:"comments"` +} + +// IPReservation represent an IP reservation for a single project +type IPReservation struct { + ID string `json:"id"` + Network string `json:"network"` + Address string `json:"address"` + AddressFamily int `json:"address_family"` + Netmask string `json:"netmask"` + Public bool `json:"public"` + Cidr int `json:"cidr"` + Management bool `json:"management"` + Manageable bool `json:"manageable"` + Addon bool `json:"addon"` + Bill bool `json:"bill"` + Assignments []map[string]string `json:"assignments"` + Created string `json:"created_at,omitempty"` + Updated string `json:"updated_at,omitempty"` + Href string `json:"href"` +} + +type ipReservationRoot struct { + IPReservations []IPReservation `json:"ip_addresses"` +} + +// List provides a list of IP resevations for a single project. +func (i *IPReservationServiceOp) List(projectID string) ([]IPReservation, *Response, error) { + path := fmt.Sprintf("%s/%s%s", projectBasePath, projectID, ipBasePath) + + req, err := i.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + reservations := new(ipReservationRoot) + resp, err := i.client.Do(req, reservations) + if err != nil { + return nil, resp, err + } + return reservations.IPReservations, resp, err +} + +// RequestMore requests more IP space for a project in order to have additional IP addresses to assign to devices +func (i *IPReservationServiceOp) RequestMore(projectID string, ipReservationReq *IPReservationRequest) (*IPReservation, *Response, error) { + path := fmt.Sprintf("%s/%s%s", projectBasePath, projectID, ipBasePath) + + req, err := i.client.NewRequest("POST", path, &ipReservationReq) + if err != nil { + return nil, nil, err + } + + ip := new(IPReservation) + resp, err := i.client.Do(req, ip) + if err != nil { + return nil, resp, err + } + return ip, resp, err +} + +// Get returns a single IP reservation object +func (i *IPReservationServiceOp) Get(ipReservationID string) (*IPReservation, *Response, error) { + path := fmt.Sprintf("%s/%s", ipBasePath, ipReservationID) + + req, err := i.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + reservation := new(IPReservation) + resp, err := i.client.Do(req, reservation) + if err != nil { + return nil, nil, err + } + + return reservation, resp, err +} + +// Remove removes an IP reservation from the project. +func (i *IPReservationServiceOp) Remove(ipReservationID string) (*Response, error) { + path := fmt.Sprintf("%s/%s", ipBasePath, ipReservationID) + + req, err := i.client.NewRequest("DELETE", path, nil) + if err != nil { + return nil, err + } + + resp, err := i.client.Do(req, nil) + if err != nil { + return nil, err + } + + return 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..4200a40cf --- /dev/null +++ b/vendor/github.com/packethost/packngo/operatingsystems.go @@ -0,0 +1,45 @@ +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"` +} + +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) { + req, err := s.client.NewRequest("GET", osBasePath, nil) + if err != nil { + return nil, nil, err + } + + root := new(osRoot) + resp, err := s.client.Do(req, root) + if err != nil { + return nil, resp, err + } + + return root.OperatingSystems, resp, err +} diff --git a/vendor/github.com/packethost/packngo/packngo.go b/vendor/github.com/packethost/packngo/packngo.go new file mode 100644 index 000000000..21a027941 --- /dev/null +++ b/vendor/github.com/packethost/packngo/packngo.go @@ -0,0 +1,218 @@ +package packngo + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strconv" + "strings" + "time" +) + +const ( + libraryVersion = "0.1.0" + baseURL = "https://api.packet.net/" + userAgent = "packngo/" + libraryVersion + mediaType = "application/json" + + headerRateLimit = "X-RateLimit-Limit" + headerRateRemaining = "X-RateLimit-Remaining" + headerRateReset = "X-RateLimit-Reset" +) + +// ListOptions specifies optional global API parameters +type ListOptions struct { + // for paginated result sets, page of results to retrieve + Page int `url:"page,omitempty"` + + // for paginated result sets, the number of results to return per page + PerPage int `url:"per_page,omitempty"` + + // specify which resources you want to return as collections instead of references + Includes string +} + +// Response is the http response from api calls +type Response struct { + *http.Response + Rate +} + +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 errrors +type ErrorResponse struct { + Response *http.Response + Errors []string `json:"errors"` +} + +func (r *ErrorResponse) Error() string { + return fmt.Sprintf("%v %v: %d %v", + r.Response.Request.Method, r.Response.Request.URL, r.Response.StatusCode, strings.Join(r.Errors, ", ")) +} + +// Client is the base API Client +type Client struct { + client *http.Client + + 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 + Ips IPService + IpReservations IPReservationService + Volumes VolumeService +} + +// 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() + 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 +} + +// NewClient 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 NewClient(consumerToken string, apiKey string, httpClient *http.Client) *Client { + client, _ := NewClientWithBaseURL(consumerToken, apiKey, httpClient, baseURL) + return client +} +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.Plans = &PlanServiceOp{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.Ips = &IPServiceOp{client: c} + c.IpReservations = &IPReservationServiceOp{client: c} + c.Volumes = &VolumeServiceOp{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/plans.go b/vendor/github.com/packethost/packngo/plans.go new file mode 100644 index 000000000..5b3fa86fd --- /dev/null +++ b/vendor/github.com/packethost/packngo/plans.go @@ -0,0 +1,122 @@ +package packngo + +const planBasePath = "/plans" + +// PlanService interface defines available plan methods +type PlanService interface { + List() ([]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"` +} + +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 { + Hourly float32 `json:"hourly,omitempty"` + Monthly float32 `json:"monthly,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() ([]Plan, *Response, error) { + req, err := s.client.NewRequest("GET", planBasePath, nil) + + if err != nil { + return nil, nil, err + } + + root := new(planRoot) + resp, err := s.client.Do(req, root) + if err != nil { + return nil, resp, err + } + + return root.Plans, resp, 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..9c539a8ce --- /dev/null +++ b/vendor/github.com/packethost/packngo/projects.go @@ -0,0 +1,183 @@ +package packngo + +import "fmt" + +const projectBasePath = "/projects" + +// ProjectService interface defines available project methods +type ProjectService interface { + List() ([]Project, *Response, error) + Get(string) (*Project, *Response, error) + Create(*ProjectCreateRequest) (*Project, *Response, error) + Update(*ProjectUpdateRequest) (*Project, *Response, error) + Delete(string) (*Response, error) + ListIPAddresses(string) ([]IPAddress, *Response, error) + ListVolumes(string) ([]Volume, *Response, error) +} + +type ipsRoot struct { + IPAddresses []IPAddress `json:"ip_addresses"` +} + +type volumesRoot struct { + Volumes []Volume `json:"volumes"` +} + +type projectsRoot struct { + Projects []Project `json:"projects"` +} + +// Project represents a Packet project +type Project struct { + ID string `json:"id"` + Name string `json:"name,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"` +} + +func (p Project) String() string { + return Stringify(p) +} + +// ProjectCreateRequest type used to create a Packet project +type ProjectCreateRequest struct { + Name string `json:"name"` + PaymentMethod string `json:"payment_method,omitempty"` +} + +func (p ProjectCreateRequest) String() string { + return Stringify(p) +} + +// ProjectUpdateRequest type used to update a Packet project +type ProjectUpdateRequest struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + PaymentMethod string `json:"payment_method,omitempty"` +} + +func (p ProjectUpdateRequest) String() string { + return Stringify(p) +} + +// ProjectServiceOp implements ProjectService +type ProjectServiceOp struct { + client *Client +} + +func (s *ProjectServiceOp) ListIPAddresses(projectID string) ([]IPAddress, *Response, error) { + url := fmt.Sprintf("%s/%s/ips", projectBasePath, projectID) + req, err := s.client.NewRequest("GET", url, nil) + if err != nil { + return nil, nil, err + } + + root := new(ipsRoot) + resp, err := s.client.Do(req, root) + if err != nil { + return nil, resp, err + } + + return root.IPAddresses, resp, err +} + +// List returns the user's projects +func (s *ProjectServiceOp) List() ([]Project, *Response, error) { + req, err := s.client.NewRequest("GET", projectBasePath, nil) + if err != nil { + return nil, nil, err + } + + root := new(projectsRoot) + resp, err := s.client.Do(req, root) + if err != nil { + return nil, resp, err + } + + return root.Projects, resp, err +} + +// Get returns a project by id +func (s *ProjectServiceOp) Get(projectID string) (*Project, *Response, error) { + path := fmt.Sprintf("%s/%s", projectBasePath, projectID) + req, err := s.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + project := new(Project) + resp, err := s.client.Do(req, 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) { + req, err := s.client.NewRequest("POST", projectBasePath, createRequest) + if err != nil { + return nil, nil, err + } + + project := new(Project) + resp, err := s.client.Do(req, project) + if err != nil { + return nil, resp, err + } + + return project, resp, err +} + +// Update updates a project +func (s *ProjectServiceOp) Update(updateRequest *ProjectUpdateRequest) (*Project, *Response, error) { + path := fmt.Sprintf("%s/%s", projectBasePath, updateRequest.ID) + req, err := s.client.NewRequest("PATCH", path, updateRequest) + if err != nil { + return nil, nil, err + } + + project := new(Project) + resp, err := s.client.Do(req, 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) + + req, err := s.client.NewRequest("DELETE", path, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + + return resp, err +} + +// List returns Volumes for a project +func (s *ProjectServiceOp) ListVolumes(projectID string) ([]Volume, *Response, error) { + url := fmt.Sprintf("%s/%s%s", projectBasePath, projectID, volumeBasePath) + req, err := s.client.NewRequest("GET", url, nil) + if err != nil { + return nil, nil, err + } + + root := new(volumesRoot) + resp, err := s.client.Do(req, root) + if err != nil { + return nil, resp, err + } + + return root.Volumes, resp, err +} 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/sshkeys.go b/vendor/github.com/packethost/packngo/sshkeys.go new file mode 100644 index 000000000..474af7742 --- /dev/null +++ b/vendor/github.com/packethost/packngo/sshkeys.go @@ -0,0 +1,146 @@ +package packngo + +import "fmt" + +const sshKeyBasePath = "/ssh-keys" + +// SSHKeyService interface defines available device methods +type SSHKeyService interface { + List() ([]SSHKey, *Response, error) + Get(string) (*SSHKey, *Response, error) + Create(*SSHKeyCreateRequest) (*SSHKey, *Response, error) + Update(*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 { + ID string `json:"id"` + Label string `json:"label"` + Key string `json:"key"` +} + +func (s SSHKeyUpdateRequest) String() string { + return Stringify(s) +} + +// SSHKeyServiceOp implements SSHKeyService +type SSHKeyServiceOp struct { + client *Client +} + +// List returns a user's ssh keys +func (s *SSHKeyServiceOp) List() ([]SSHKey, *Response, error) { + req, err := s.client.NewRequest("GET", sshKeyBasePath, nil) + if err != nil { + return nil, nil, err + } + + root := new(sshKeyRoot) + resp, err := s.client.Do(req, root) + if err != nil { + return nil, resp, err + } + + return root.SSHKeys, resp, err +} + +// Get returns an ssh key by id +func (s *SSHKeyServiceOp) Get(sshKeyID string) (*SSHKey, *Response, error) { + path := fmt.Sprintf("%s/%s", sshKeyBasePath, sshKeyID) + + req, err := s.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + sshKey := new(SSHKey) + resp, err := s.client.Do(req, 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 = "/projects/" + createRequest.ProjectID + sshKeyBasePath + } + req, err := s.client.NewRequest("POST", path, createRequest) + if err != nil { + return nil, nil, err + } + + sshKey := new(SSHKey) + resp, err := s.client.Do(req, sshKey) + if err != nil { + return nil, resp, err + } + + return sshKey, resp, err +} + +// Update updates an ssh key +func (s *SSHKeyServiceOp) Update(updateRequest *SSHKeyUpdateRequest) (*SSHKey, *Response, error) { + path := fmt.Sprintf("%s/%s", sshKeyBasePath, updateRequest.ID) + req, err := s.client.NewRequest("PATCH", path, updateRequest) + if err != nil { + return nil, nil, err + } + + sshKey := new(SSHKey) + resp, err := s.client.Do(req, 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) + + req, err := s.client.NewRequest("DELETE", path, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + + return resp, err +} 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/user.go b/vendor/github.com/packethost/packngo/user.go new file mode 100644 index 000000000..36e03d909 --- /dev/null +++ b/vendor/github.com/packethost/packngo/user.go @@ -0,0 +1,53 @@ +package packngo + +const userBasePath = "/users" + +// UserService interface defines available user methods +type UserService interface { + Get(string) (*User, *Response, error) +} + +// 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"` + 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:"email,omitempty"` + PhoneNumber string `json:"phone_number,omitempty"` + URL string `json:"href,omitempty"` +} + +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) Get(userID string) (*User, *Response, error) { + req, err := s.client.NewRequest("GET", userBasePath, nil) + if err != nil { + return nil, nil, err + } + + user := new(User) + resp, err := s.client.Do(req, 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..a77d7b79e --- /dev/null +++ b/vendor/github.com/packethost/packngo/utils.go @@ -0,0 +1,112 @@ +package packngo + +import ( + "bytes" + "fmt" + "io" + "reflect" +) + +var timestampType = reflect.TypeOf(Timestamp{}) + +// 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() +} + +// String allocates a new string value to store v and returns a pointer to it +func String(v string) *string { + p := new(string) + *p = v + return p +} + +// Int allocates a new int32 value to store v and returns a pointer to it, but unlike Int32 its argument value is an int. +func Int(v int) *int { + p := new(int) + *p = v + return p +} + +// Bool allocates a new bool value to store v and returns a pointer to it. +func Bool(v bool) *bool { + p := new(bool) + *p = v + return p +} + +// StreamToString converts a reader to a string +func StreamToString(stream io.Reader) string { + buf := new(bytes.Buffer) + buf.ReadFrom(stream) + return buf.String() +} + +// 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/volumes.go b/vendor/github.com/packethost/packngo/volumes.go new file mode 100644 index 000000000..96ab3b6e8 --- /dev/null +++ b/vendor/github.com/packethost/packngo/volumes.go @@ -0,0 +1,146 @@ +package packngo + +import "fmt" + +const volumeBasePath = "/storage" + +// VolumeService interface defines available Volume methods +type VolumeService interface { + Get(string) (*Volume, *Response, error) + Update(*VolumeUpdateRequest) (*Volume, *Response, error) + Delete(string) (*Response, error) + Create(*VolumeCreateRequest) (*Volume, *Response, error) +} + +// Volume represents a volume +type Volume struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Size int `json:"size,omitempty"` + State string `json:"state,omitempty"` + Locked bool `json:"locked,omitempty"` + BillingCycle string `json:"billing_cycle,omitempty"` + Created string `json:"created_at,omitempty"` + Updated string `json:"updated_at,omitempty"` + Href string `json:"href,omitempty"` + SnapshotPolicies []*SnapshotPolicy `json:"snapshot_policies,omitempty"` + Attachments []*Attachment `json:"attachments,omitempty"` + Plan *Plan `json:"plan,omitempty"` + Facility *Facility `json:"facility,omitempty"` + Project *Project `json:"project,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"` +} + +// Attachment used to execute actions on volume +type Attachment struct { + ID string `json:"id"` + Href string `json:"href"` +} + +func (v Volume) String() string { + return Stringify(v) +} + +// VolumeCreateRequest type used to create a Packet volume +type VolumeCreateRequest struct { + Size int `json:"size"` + BillingCycle string `json:"billing_cycle"` + ProjectID string `json:"project_id"` + PlanID string `json:"plan_id"` + FacilityID string `json:"facility_id"` + Description string `json:"description,omitempty"` + 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 { + ID string `json:"id"` + Description string `json:"description,omitempty"` + Plan string `json:"plan,omitempty"` +} + +func (v VolumeUpdateRequest) String() string { + return Stringify(v) +} + +// VolumeServiceOp implements VolumeService +type VolumeServiceOp struct { + client *Client +} + +// Get returns a volume by id +func (v *VolumeServiceOp) Get(volumeID string) (*Volume, *Response, error) { + path := fmt.Sprintf("%s/%s?include=facility,snapshot_policies,attachments.device", volumeBasePath, volumeID) + req, err := v.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + volume := new(Volume) + resp, err := v.client.Do(req, volume) + if err != nil { + return nil, resp, err + } + + return volume, resp, err +} + +// Update updates a volume +func (v *VolumeServiceOp) Update(updateRequest *VolumeUpdateRequest) (*Volume, *Response, error) { + path := fmt.Sprintf("%s/%s", volumeBasePath, updateRequest.ID) + req, err := v.client.NewRequest("PATCH", path, updateRequest) + if err != nil { + return nil, nil, err + } + + volume := new(Volume) + resp, err := v.client.Do(req, 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) + + req, err := v.client.NewRequest("DELETE", path, nil) + if err != nil { + return nil, err + } + + resp, err := v.client.Do(req, nil) + + return resp, err +} + +// Create creates a new volume for a project +func (v *VolumeServiceOp) Create(createRequest *VolumeCreateRequest) (*Volume, *Response, error) { + url := fmt.Sprintf("%s/%s%s", projectBasePath, createRequest.ProjectID, volumeBasePath) + req, err := v.client.NewRequest("POST", url, createRequest) + if err != nil { + return nil, nil, err + } + + volume := new(Volume) + resp, err := v.client.Do(req, volume) + if err != nil { + return nil, resp, err + } + + return volume, resp, err +}