1package packngo 2 3import ( 4 "fmt" 5 "strings" 6) 7 8const deviceBasePath = "/devices" 9 10// DeviceService interface defines available device methods 11type DeviceService interface { 12 List(ProjectID string, listOpt *ListOptions) ([]Device, *Response, error) 13 Get(string) (*Device, *Response, error) 14 GetExtra(deviceID string, includes, excludes []string) (*Device, *Response, error) 15 Create(*DeviceCreateRequest) (*Device, *Response, error) 16 Update(string, *DeviceUpdateRequest) (*Device, *Response, error) 17 Delete(string) (*Response, error) 18 Reboot(string) (*Response, error) 19 PowerOff(string) (*Response, error) 20 PowerOn(string) (*Response, error) 21 Lock(string) (*Response, error) 22 Unlock(string) (*Response, error) 23} 24 25type devicesRoot struct { 26 Devices []Device `json:"devices"` 27 Meta meta `json:"meta"` 28} 29 30// Device represents a Packet device 31type Device struct { 32 ID string `json:"id"` 33 Href string `json:"href,omitempty"` 34 Hostname string `json:"hostname,omitempty"` 35 State string `json:"state,omitempty"` 36 Created string `json:"created_at,omitempty"` 37 Updated string `json:"updated_at,omitempty"` 38 Locked bool `json:"locked,omitempty"` 39 BillingCycle string `json:"billing_cycle,omitempty"` 40 Storage map[string]interface{} `json:"storage,omitempty"` 41 Tags []string `json:"tags,omitempty"` 42 Network []*IPAddressAssignment `json:"ip_addresses"` 43 Volumes []*Volume `json:"volumes"` 44 OS *OS `json:"operating_system,omitempty"` 45 Plan *Plan `json:"plan,omitempty"` 46 Facility *Facility `json:"facility,omitempty"` 47 Project *Project `json:"project,omitempty"` 48 ProvisionEvents []*ProvisionEvent `json:"provisioning_events,omitempty"` 49 ProvisionPer float32 `json:"provisioning_percentage,omitempty"` 50 UserData string `json:"userdata,omitempty"` 51 RootPassword string `json:"root_password,omitempty"` 52 IPXEScriptURL string `json:"ipxe_script_url,omitempty"` 53 AlwaysPXE bool `json:"always_pxe,omitempty"` 54 HardwareReservation Href `json:"hardware_reservation,omitempty"` 55 SpotInstance bool `json:"spot_instance,omitempty"` 56 SpotPriceMax float64 `json:"spot_price_max,omitempty"` 57 TerminationTime *Timestamp `json:"termination_time,omitempty"` 58 NetworkPorts []Port `json:"network_ports,omitempty"` 59 CustomData map[string]interface{} `json:"customdata,omitempty"` 60} 61 62type ProvisionEvent struct { 63 ID string `json:"id"` 64 Body string `json:"body"` 65 CreatedAt *Timestamp `json:"created_at,omitempty"` 66 Href string `json:"href"` 67 Interpolated string `json:"interpolated"` 68 Relationships []Href `json:"relationships"` 69 State string `json:"state"` 70 Type string `json:"type"` 71} 72 73func (d Device) String() string { 74 return Stringify(d) 75} 76 77// DeviceCreateRequest type used to create a Packet device 78type DeviceCreateRequest struct { 79 Hostname string `json:"hostname"` 80 Plan string `json:"plan"` 81 Facility string `json:"facility"` 82 OS string `json:"operating_system"` 83 BillingCycle string `json:"billing_cycle"` 84 ProjectID string `json:"project_id"` 85 UserData string `json:"userdata"` 86 Storage string `json:"storage,omitempty"` 87 Tags []string `json:"tags"` 88 IPXEScriptURL string `json:"ipxe_script_url,omitempty"` 89 PublicIPv4SubnetSize int `json:"public_ipv4_subnet_size,omitempty"` 90 AlwaysPXE bool `json:"always_pxe,omitempty"` 91 HardwareReservationID string `json:"hardware_reservation_id,omitempty"` 92 SpotInstance bool `json:"spot_instance,omitempty"` 93 SpotPriceMax float64 `json:"spot_price_max,omitempty,string"` 94 TerminationTime *Timestamp `json:"termination_time,omitempty"` 95 CustomData string `json:"customdata,omitempty"` 96} 97 98// DeviceUpdateRequest type used to update a Packet device 99type DeviceUpdateRequest struct { 100 Hostname *string `json:"hostname,omitempty"` 101 Description *string `json:"description,omitempty"` 102 UserData *string `json:"userdata,omitempty"` 103 Locked *bool `json:"locked,omitempty"` 104 Tags *[]string `json:"tags,omitempty"` 105 AlwaysPXE *bool `json:"always_pxe,omitempty"` 106 IPXEScriptURL *string `json:"ipxe_script_url,omitempty"` 107 CustomData *string `json:"customdata,omitempty"` 108} 109 110func (d DeviceCreateRequest) String() string { 111 return Stringify(d) 112} 113 114// DeviceActionRequest type used to execute actions on devices 115type DeviceActionRequest struct { 116 Type string `json:"type"` 117} 118 119func (d DeviceActionRequest) String() string { 120 return Stringify(d) 121} 122 123// DeviceServiceOp implements DeviceService 124type DeviceServiceOp struct { 125 client *Client 126} 127 128// List returns devices on a project 129func (s *DeviceServiceOp) List(projectID string, listOpt *ListOptions) (devices []Device, resp *Response, err error) { 130 params := "include=facility" 131 if listOpt != nil { 132 params = listOpt.createURL() 133 } 134 path := fmt.Sprintf("%s/%s%s?%s", projectBasePath, projectID, deviceBasePath, params) 135 136 for { 137 subset := new(devicesRoot) 138 139 resp, err = s.client.DoRequest("GET", path, nil, subset) 140 if err != nil { 141 return nil, resp, err 142 } 143 144 devices = append(devices, subset.Devices...) 145 146 if subset.Meta.Next != nil && (listOpt == nil || listOpt.Page == 0) { 147 path = subset.Meta.Next.Href 148 if params != "" { 149 path = fmt.Sprintf("%s&%s", path, params) 150 } 151 continue 152 } 153 154 return 155 } 156} 157 158// Get returns a device by id 159func (s *DeviceServiceOp) Get(deviceID string) (*Device, *Response, error) { 160 return s.GetExtra(deviceID, []string{"facility"}, nil) 161} 162 163// GetExtra returns a device by id. Specifying either includes/excludes provides more or less desired 164// detailed information about resources which would otherwise be represented with an href link 165func (s *DeviceServiceOp) GetExtra(deviceID string, includes, excludes []string) (*Device, *Response, error) { 166 path := fmt.Sprintf("%s/%s", deviceBasePath, deviceID) 167 if includes != nil { 168 path += fmt.Sprintf("?include=%s", strings.Join(includes, ",")) 169 } else if excludes != nil { 170 path += fmt.Sprintf("?exclude=%s", strings.Join(excludes, ",")) 171 } 172 device := new(Device) 173 174 resp, err := s.client.DoRequest("GET", path, nil, device) 175 if err != nil { 176 return nil, resp, err 177 } 178 179 return device, resp, err 180} 181 182// Create creates a new device 183func (s *DeviceServiceOp) Create(createRequest *DeviceCreateRequest) (*Device, *Response, error) { 184 path := fmt.Sprintf("%s/%s%s", projectBasePath, createRequest.ProjectID, deviceBasePath) 185 device := new(Device) 186 187 resp, err := s.client.DoRequest("POST", path, createRequest, device) 188 if err != nil { 189 return nil, resp, err 190 } 191 192 return device, resp, err 193} 194 195// Update updates an existing device 196func (s *DeviceServiceOp) Update(deviceID string, updateRequest *DeviceUpdateRequest) (*Device, *Response, error) { 197 path := fmt.Sprintf("%s/%s?include=facility", deviceBasePath, deviceID) 198 device := new(Device) 199 200 resp, err := s.client.DoRequest("PUT", path, updateRequest, device) 201 if err != nil { 202 return nil, resp, err 203 } 204 205 return device, resp, err 206} 207 208// Delete deletes a device 209func (s *DeviceServiceOp) Delete(deviceID string) (*Response, error) { 210 path := fmt.Sprintf("%s/%s", deviceBasePath, deviceID) 211 212 return s.client.DoRequest("DELETE", path, nil, nil) 213} 214 215// Reboot reboots on a device 216func (s *DeviceServiceOp) Reboot(deviceID string) (*Response, error) { 217 path := fmt.Sprintf("%s/%s/actions", deviceBasePath, deviceID) 218 action := &DeviceActionRequest{Type: "reboot"} 219 220 return s.client.DoRequest("POST", path, action, nil) 221} 222 223// PowerOff powers on a device 224func (s *DeviceServiceOp) PowerOff(deviceID string) (*Response, error) { 225 path := fmt.Sprintf("%s/%s/actions", deviceBasePath, deviceID) 226 action := &DeviceActionRequest{Type: "power_off"} 227 228 return s.client.DoRequest("POST", path, action, nil) 229} 230 231// PowerOn powers on a device 232func (s *DeviceServiceOp) PowerOn(deviceID string) (*Response, error) { 233 path := fmt.Sprintf("%s/%s/actions", deviceBasePath, deviceID) 234 action := &DeviceActionRequest{Type: "power_on"} 235 236 return s.client.DoRequest("POST", path, action, nil) 237} 238 239type lockType struct { 240 Locked bool `json:"locked"` 241} 242 243// Lock sets a device to "locked" 244func (s *DeviceServiceOp) Lock(deviceID string) (*Response, error) { 245 path := fmt.Sprintf("%s/%s", deviceBasePath, deviceID) 246 action := lockType{Locked: true} 247 248 return s.client.DoRequest("PATCH", path, action, nil) 249} 250 251// Unlock sets a device to "unlocked" 252func (s *DeviceServiceOp) Unlock(deviceID string) (*Response, error) { 253 path := fmt.Sprintf("%s/%s", deviceBasePath, deviceID) 254 action := lockType{Locked: false} 255 256 return s.client.DoRequest("PATCH", path, action, nil) 257} 258