2018-04-30 15:47:39 +00:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
// Server represents a server
|
|
|
|
type Server struct {
|
2018-04-30 15:47:39 +00:00
|
|
|
// Arch is the architecture target of the server
|
|
|
|
Arch string `json:"arch,omitempty"`
|
|
|
|
|
|
|
|
// Identifier is a unique identifier for the server
|
|
|
|
Identifier string `json:"id,omitempty"`
|
|
|
|
|
|
|
|
// Name is the user-defined name of the server
|
|
|
|
Name string `json:"name,omitempty"`
|
|
|
|
|
|
|
|
// CreationDate is the creation date of the server
|
|
|
|
CreationDate string `json:"creation_date,omitempty"`
|
|
|
|
|
|
|
|
// ModificationDate is the date of the last modification of the server
|
|
|
|
ModificationDate string `json:"modification_date,omitempty"`
|
|
|
|
|
|
|
|
// Image is the image used by the server
|
2019-12-02 20:37:43 +00:00
|
|
|
Image Image `json:"image,omitempty"`
|
2018-04-30 15:47:39 +00:00
|
|
|
|
|
|
|
// DynamicIPRequired is a flag that defines a server with a dynamic ip address attached
|
|
|
|
DynamicIPRequired *bool `json:"dynamic_ip_required,omitempty"`
|
|
|
|
|
|
|
|
// PublicIP is the public IP address bound to the server
|
2019-12-02 20:37:43 +00:00
|
|
|
PublicAddress IPAddress `json:"public_ip,omitempty"`
|
2018-04-30 15:47:39 +00:00
|
|
|
|
|
|
|
// State is the current status of the server
|
|
|
|
State string `json:"state,omitempty"`
|
|
|
|
|
|
|
|
// StateDetail is the detailed status of the server
|
|
|
|
StateDetail string `json:"state_detail,omitempty"`
|
|
|
|
|
|
|
|
// PrivateIP represents the private IPV4 attached to the server (changes on each boot)
|
|
|
|
PrivateIP string `json:"private_ip,omitempty"`
|
|
|
|
|
|
|
|
// Bootscript is the unique identifier of the selected bootscript
|
2019-12-02 20:37:43 +00:00
|
|
|
Bootscript *Bootscript `json:"bootscript,omitempty"`
|
|
|
|
|
|
|
|
// BootType defines the type of boot. Can be local or bootscript
|
|
|
|
BootType string `json:"boot_type,omitempty"`
|
2018-04-30 15:47:39 +00:00
|
|
|
|
|
|
|
// Hostname represents the ServerName in a format compatible with unix's hostname
|
|
|
|
Hostname string `json:"hostname,omitempty"`
|
|
|
|
|
|
|
|
// Tags represents user-defined tags
|
|
|
|
Tags []string `json:"tags,omitempty"`
|
|
|
|
|
|
|
|
// Volumes are the attached volumes
|
2019-12-02 20:37:43 +00:00
|
|
|
Volumes map[string]Volume `json:"volumes,omitempty"`
|
2018-04-30 15:47:39 +00:00
|
|
|
|
|
|
|
// SecurityGroup is the selected security group object
|
2019-12-02 20:37:43 +00:00
|
|
|
SecurityGroup SecurityGroupRef `json:"security_group,omitempty"`
|
2018-04-30 15:47:39 +00:00
|
|
|
|
|
|
|
// Organization is the owner of the server
|
|
|
|
Organization string `json:"organization,omitempty"`
|
|
|
|
|
|
|
|
// CommercialType is the commercial type of the server (i.e: C1, C2[SML], VC1S)
|
|
|
|
CommercialType string `json:"commercial_type,omitempty"`
|
|
|
|
|
|
|
|
// Location of the server
|
|
|
|
Location struct {
|
|
|
|
Platform string `json:"platform_id,omitempty"`
|
|
|
|
Chassis string `json:"chassis_id,omitempty"`
|
|
|
|
Cluster string `json:"cluster_id,omitempty"`
|
|
|
|
Hypervisor string `json:"hypervisor_id,omitempty"`
|
|
|
|
Blade string `json:"blade_id,omitempty"`
|
|
|
|
Node string `json:"node_id,omitempty"`
|
|
|
|
ZoneID string `json:"zone_id,omitempty"`
|
|
|
|
} `json:"location,omitempty"`
|
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
IPV6 *IPV6 `json:"ipv6,omitempty"`
|
2018-04-30 15:47:39 +00:00
|
|
|
|
|
|
|
EnableIPV6 bool `json:"enable_ipv6,omitempty"`
|
|
|
|
|
|
|
|
// This fields are not returned by the API, we generate it
|
|
|
|
DNSPublic string `json:"dns_public,omitempty"`
|
|
|
|
DNSPrivate string `json:"dns_private,omitempty"`
|
|
|
|
}
|
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
// ServerPatchDefinition represents a server with nullable fields (for PATCH)
|
|
|
|
type ServerPatchDefinition struct {
|
|
|
|
Arch *string `json:"arch,omitempty"`
|
|
|
|
Name *string `json:"name,omitempty"`
|
|
|
|
CreationDate *string `json:"creation_date,omitempty"`
|
|
|
|
ModificationDate *string `json:"modification_date,omitempty"`
|
|
|
|
Image *Image `json:"image,omitempty"`
|
|
|
|
DynamicIPRequired *bool `json:"dynamic_ip_required,omitempty"`
|
|
|
|
PublicAddress *IPAddress `json:"public_ip,omitempty"`
|
|
|
|
State *string `json:"state,omitempty"`
|
|
|
|
StateDetail *string `json:"state_detail,omitempty"`
|
|
|
|
PrivateIP *string `json:"private_ip,omitempty"`
|
|
|
|
Bootscript *string `json:"bootscript,omitempty"`
|
|
|
|
Hostname *string `json:"hostname,omitempty"`
|
|
|
|
Volumes *map[string]Volume `json:"volumes,omitempty"`
|
|
|
|
SecurityGroup *SecurityGroupRef `json:"security_group,omitempty"`
|
|
|
|
Organization *string `json:"organization,omitempty"`
|
|
|
|
Tags *[]string `json:"tags,omitempty"`
|
|
|
|
IPV6 *IPV6 `json:"ipv6,omitempty"`
|
|
|
|
EnableIPV6 *bool `json:"enable_ipv6,omitempty"`
|
2018-04-30 15:47:39 +00:00
|
|
|
}
|
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
// ServerDefinition represents a server with image definition
|
|
|
|
type ServerDefinition struct {
|
2018-04-30 15:47:39 +00:00
|
|
|
// Name is the user-defined name of the server
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
|
|
|
// Image is the image used by the server
|
|
|
|
Image *string `json:"image,omitempty"`
|
|
|
|
|
|
|
|
// Volumes are the attached volumes
|
|
|
|
Volumes map[string]string `json:"volumes,omitempty"`
|
|
|
|
|
|
|
|
// DynamicIPRequired is a flag that defines a server with a dynamic ip address attached
|
|
|
|
DynamicIPRequired *bool `json:"dynamic_ip_required,omitempty"`
|
|
|
|
|
|
|
|
// Bootscript is the bootscript used by the server
|
|
|
|
Bootscript *string `json:"bootscript"`
|
|
|
|
|
|
|
|
// Tags are the metadata tags attached to the server
|
|
|
|
Tags []string `json:"tags,omitempty"`
|
|
|
|
|
|
|
|
// Organization is the owner of the server
|
|
|
|
Organization string `json:"organization"`
|
|
|
|
|
|
|
|
// CommercialType is the commercial type of the server (i.e: C1, C2[SML], VC1S)
|
|
|
|
CommercialType string `json:"commercial_type"`
|
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
// BootType defines the type of boot. Can be local or bootscript
|
|
|
|
BootType string `json:"boot_type,omitempty"`
|
|
|
|
|
2018-04-30 15:47:39 +00:00
|
|
|
PublicIP string `json:"public_ip,omitempty"`
|
|
|
|
|
|
|
|
EnableIPV6 bool `json:"enable_ipv6,omitempty"`
|
|
|
|
|
|
|
|
SecurityGroup string `json:"security_group,omitempty"`
|
|
|
|
}
|
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
// Servers represents a group of servers
|
|
|
|
type Servers struct {
|
|
|
|
// Servers holds servers of the response
|
|
|
|
Servers []Server `json:"servers,omitempty"`
|
2018-04-30 15:47:39 +00:00
|
|
|
}
|
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
// ServerAction represents an action to perform on a server
|
|
|
|
type ServerAction struct {
|
2018-04-30 15:47:39 +00:00
|
|
|
// Action is the name of the action to trigger
|
|
|
|
Action string `json:"action,omitempty"`
|
|
|
|
}
|
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
// OneServer represents the response of a GET /servers/UUID API call
|
|
|
|
type OneServer struct {
|
|
|
|
Server Server `json:"server,omitempty"`
|
2018-04-30 15:47:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// PatchServer updates a server
|
2019-12-02 20:37:43 +00:00
|
|
|
func (s *API) PatchServer(serverID string, definition ServerPatchDefinition) error {
|
2018-04-30 15:47:39 +00:00
|
|
|
resp, err := s.PatchResponse(s.computeAPI, fmt.Sprintf("servers/%s", serverID), definition)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
if _, err := s.handleHTTPError([]int{http.StatusOK}, resp); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
// GetServers gets the list of servers from the API
|
|
|
|
func (s *API) GetServers(all bool, limit int) ([]Server, error) {
|
2018-04-30 15:47:39 +00:00
|
|
|
query := url.Values{}
|
|
|
|
if !all {
|
|
|
|
query.Set("state", "running")
|
|
|
|
}
|
2019-12-02 20:37:43 +00:00
|
|
|
// TODO per_page=20&page=2&state=running
|
2018-04-30 15:47:39 +00:00
|
|
|
if limit > 0 {
|
|
|
|
// FIXME: wait for the API to be ready
|
|
|
|
// query.Set("per_page", strconv.Itoa(limit))
|
|
|
|
panic("Not implemented yet")
|
|
|
|
}
|
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
servers, err := s.fetchServers(query)
|
|
|
|
if err != nil {
|
2018-04-30 15:47:39 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, server := range servers.Servers {
|
|
|
|
servers.Servers[i].DNSPublic = server.Identifier + URLPublicDNS
|
|
|
|
servers.Servers[i].DNSPrivate = server.Identifier + URLPrivateDNS
|
|
|
|
}
|
2019-12-02 20:37:43 +00:00
|
|
|
return servers.Servers, nil
|
2018-04-30 15:47:39 +00:00
|
|
|
}
|
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
// SortServers represents a wrapper to sort by CreationDate the servers
|
|
|
|
type SortServers []Server
|
2018-04-30 15:47:39 +00:00
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
func (s SortServers) Len() int {
|
2018-04-30 15:47:39 +00:00
|
|
|
return len(s)
|
|
|
|
}
|
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
func (s SortServers) Swap(i, j int) {
|
2018-04-30 15:47:39 +00:00
|
|
|
s[i], s[j] = s[j], s[i]
|
|
|
|
}
|
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
func (s SortServers) Less(i, j int) bool {
|
2018-04-30 15:47:39 +00:00
|
|
|
date1, _ := time.Parse("2006-01-02T15:04:05.000000+00:00", s[i].CreationDate)
|
|
|
|
date2, _ := time.Parse("2006-01-02T15:04:05.000000+00:00", s[j].CreationDate)
|
|
|
|
return date2.Before(date1)
|
|
|
|
}
|
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
// GetServer gets a server from the API
|
|
|
|
func (s *API) GetServer(serverID string) (*Server, error) {
|
2018-04-30 15:47:39 +00:00
|
|
|
if serverID == "" {
|
|
|
|
return nil, fmt.Errorf("cannot get server without serverID")
|
|
|
|
}
|
|
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, "servers/"+serverID, url.Values{})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
body, err := s.handleHTTPError([]int{http.StatusOK}, resp)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
var oneServer OneServer
|
2018-04-30 15:47:39 +00:00
|
|
|
|
|
|
|
if err = json.Unmarshal(body, &oneServer); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// FIXME arch, owner, title
|
|
|
|
oneServer.Server.DNSPublic = oneServer.Server.Identifier + URLPublicDNS
|
|
|
|
oneServer.Server.DNSPrivate = oneServer.Server.Identifier + URLPrivateDNS
|
|
|
|
return &oneServer.Server, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// PostServerAction posts an action on a server
|
2019-12-02 20:37:43 +00:00
|
|
|
func (s *API) PostServerAction(serverID, action string) (*Task, error) {
|
|
|
|
data := ServerAction{
|
2018-04-30 15:47:39 +00:00
|
|
|
Action: action,
|
|
|
|
}
|
|
|
|
resp, err := s.PostResponse(s.computeAPI, fmt.Sprintf("servers/%s/action", serverID), data)
|
|
|
|
if err != nil {
|
2019-12-02 20:37:43 +00:00
|
|
|
return nil, err
|
2018-04-30 15:47:39 +00:00
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
body, err := s.handleHTTPError([]int{http.StatusAccepted}, resp)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var t oneTask
|
|
|
|
if err = json.Unmarshal(body, &t); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &t.Task, err
|
2018-04-30 15:47:39 +00:00
|
|
|
}
|
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
func (s *API) fetchServers(query url.Values) (*Servers, error) {
|
|
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, "servers", query)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2018-04-30 15:47:39 +00:00
|
|
|
}
|
2019-12-02 20:37:43 +00:00
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
body, err := s.handleHTTPError([]int{http.StatusOK}, resp)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var servers Servers
|
|
|
|
|
|
|
|
if err = json.Unmarshal(body, &servers); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &servers, nil
|
2018-04-30 15:47:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteServer deletes a server
|
2019-12-02 20:37:43 +00:00
|
|
|
func (s *API) DeleteServer(serverID string) error {
|
2018-04-30 15:47:39 +00:00
|
|
|
resp, err := s.DeleteResponse(s.computeAPI, fmt.Sprintf("servers/%s", serverID))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
if _, err = s.handleHTTPError([]int{http.StatusNoContent}, resp); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
// CreateServer creates a new server
|
|
|
|
func (s *API) CreateServer(definition ServerDefinition) (*Server, error) {
|
2018-04-30 15:47:39 +00:00
|
|
|
definition.Organization = s.Organization
|
|
|
|
|
|
|
|
resp, err := s.PostResponse(s.computeAPI, "servers", definition)
|
|
|
|
if err != nil {
|
2019-12-02 20:37:43 +00:00
|
|
|
return nil, err
|
2018-04-30 15:47:39 +00:00
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
body, err := s.handleHTTPError([]int{http.StatusCreated}, resp)
|
|
|
|
if err != nil {
|
2019-12-02 20:37:43 +00:00
|
|
|
return nil, err
|
2018-04-30 15:47:39 +00:00
|
|
|
}
|
2019-12-02 20:37:43 +00:00
|
|
|
var data OneServer
|
2018-04-30 15:47:39 +00:00
|
|
|
|
2019-12-02 20:37:43 +00:00
|
|
|
if err = json.Unmarshal(body, &data); err != nil {
|
|
|
|
return nil, err
|
2018-04-30 15:47:39 +00:00
|
|
|
}
|
2019-12-02 20:37:43 +00:00
|
|
|
return &data.Server, nil
|
2018-04-30 15:47:39 +00:00
|
|
|
}
|