2755 lines
80 KiB
Go
2755 lines
80 KiB
Go
// Copyright (C) 2015 Scaleway. All rights reserved.
|
|
// Use of this source code is governed by a MIT-style
|
|
// license that can be found in the LICENSE.md file.
|
|
|
|
// Interact with Scaleway API
|
|
|
|
// Package api contains client and functions to interact with Scaleway API
|
|
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
"os"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"text/tabwriter"
|
|
"text/template"
|
|
"time"
|
|
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
// Default values
|
|
var (
|
|
AccountAPI = "https://account.scaleway.com/"
|
|
MetadataAPI = "http://169.254.42.42/"
|
|
MarketplaceAPI = "https://api-marketplace.scaleway.com"
|
|
ComputeAPIPar1 = "https://cp-par1.scaleway.com/"
|
|
ComputeAPIAms1 = "https://cp-ams1.scaleway.com"
|
|
|
|
URLPublicDNS = ".pub.cloud.scaleway.com"
|
|
URLPrivateDNS = ".priv.cloud.scaleway.com"
|
|
)
|
|
|
|
func init() {
|
|
if url := os.Getenv("SCW_ACCOUNT_API"); url != "" {
|
|
AccountAPI = url
|
|
}
|
|
if url := os.Getenv("SCW_METADATA_API"); url != "" {
|
|
MetadataAPI = url
|
|
}
|
|
if url := os.Getenv("SCW_MARKETPLACE_API"); url != "" {
|
|
MarketplaceAPI = url
|
|
}
|
|
}
|
|
|
|
const (
|
|
perPage = 50
|
|
)
|
|
|
|
// ScalewayAPI is the interface used to communicate with the Scaleway API
|
|
type ScalewayAPI struct {
|
|
// Organization is the identifier of the Scaleway organization
|
|
Organization string
|
|
|
|
// Token is the authentication token for the Scaleway organization
|
|
Token string
|
|
|
|
// Password is the authentication password
|
|
password string
|
|
|
|
userAgent string
|
|
|
|
// Cache is used to quickly resolve identifiers from names
|
|
Cache *ScalewayCache
|
|
|
|
client *http.Client
|
|
verbose bool
|
|
computeAPI string
|
|
|
|
Region string
|
|
//
|
|
Logger
|
|
}
|
|
|
|
// ScalewayAPIError represents a Scaleway API Error
|
|
type ScalewayAPIError struct {
|
|
// Message is a human-friendly error message
|
|
APIMessage string `json:"message,omitempty"`
|
|
|
|
// Type is a string code that defines the kind of error
|
|
Type string `json:"type,omitempty"`
|
|
|
|
// Fields contains detail about validation error
|
|
Fields map[string][]string `json:"fields,omitempty"`
|
|
|
|
// StatusCode is the HTTP status code received
|
|
StatusCode int `json:"-"`
|
|
|
|
// Message
|
|
Message string `json:"-"`
|
|
}
|
|
|
|
// Error returns a string representing the error
|
|
func (e ScalewayAPIError) Error() string {
|
|
var b bytes.Buffer
|
|
|
|
fmt.Fprintf(&b, "StatusCode: %v, ", e.StatusCode)
|
|
fmt.Fprintf(&b, "Type: %v, ", e.Type)
|
|
fmt.Fprintf(&b, "APIMessage: \x1b[31m%v\x1b[0m", e.APIMessage)
|
|
if len(e.Fields) > 0 {
|
|
fmt.Fprintf(&b, ", Details: %v", e.Fields)
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// HideAPICredentials removes API credentials from a string
|
|
func (s *ScalewayAPI) HideAPICredentials(input string) string {
|
|
output := input
|
|
if s.Token != "" {
|
|
output = strings.Replace(output, s.Token, "00000000-0000-4000-8000-000000000000", -1)
|
|
}
|
|
if s.Organization != "" {
|
|
output = strings.Replace(output, s.Organization, "00000000-0000-5000-9000-000000000000", -1)
|
|
}
|
|
if s.password != "" {
|
|
output = strings.Replace(output, s.password, "XX-XX-XX-XX", -1)
|
|
}
|
|
return output
|
|
}
|
|
|
|
// ScalewayIPAddress represents a Scaleway IP address
|
|
type ScalewayIPAddress struct {
|
|
// Identifier is a unique identifier for the IP address
|
|
Identifier string `json:"id,omitempty"`
|
|
|
|
// IP is an IPv4 address
|
|
IP string `json:"address,omitempty"`
|
|
|
|
// Dynamic is a flag that defines an IP that change on each reboot
|
|
Dynamic *bool `json:"dynamic,omitempty"`
|
|
}
|
|
|
|
// ScalewayVolume represents a Scaleway Volume
|
|
type ScalewayVolume struct {
|
|
// Identifier is a unique identifier for the volume
|
|
Identifier string `json:"id,omitempty"`
|
|
|
|
// Size is the allocated size of the volume
|
|
Size uint64 `json:"size,omitempty"`
|
|
|
|
// CreationDate is the creation date of the volume
|
|
CreationDate string `json:"creation_date,omitempty"`
|
|
|
|
// ModificationDate is the date of the last modification of the volume
|
|
ModificationDate string `json:"modification_date,omitempty"`
|
|
|
|
// Organization is the organization owning the volume
|
|
Organization string `json:"organization,omitempty"`
|
|
|
|
// Name is the name of the volume
|
|
Name string `json:"name,omitempty"`
|
|
|
|
// Server is the server using this image
|
|
Server *struct {
|
|
Identifier string `json:"id,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
} `json:"server,omitempty"`
|
|
|
|
// VolumeType is a Scaleway identifier for the kind of volume (default: l_ssd)
|
|
VolumeType string `json:"volume_type,omitempty"`
|
|
|
|
// ExportURI represents the url used by initrd/scripts to attach the volume
|
|
ExportURI string `json:"export_uri,omitempty"`
|
|
}
|
|
|
|
// ScalewayOneVolume represents the response of a GET /volumes/UUID API call
|
|
type ScalewayOneVolume struct {
|
|
Volume ScalewayVolume `json:"volume,omitempty"`
|
|
}
|
|
|
|
// ScalewayVolumes represents a group of Scaleway volumes
|
|
type ScalewayVolumes struct {
|
|
// Volumes holds scaleway volumes of the response
|
|
Volumes []ScalewayVolume `json:"volumes,omitempty"`
|
|
}
|
|
|
|
// ScalewayVolumeDefinition represents a Scaleway volume definition
|
|
type ScalewayVolumeDefinition struct {
|
|
// Name is the user-defined name of the volume
|
|
Name string `json:"name"`
|
|
|
|
// Image is the image used by the volume
|
|
Size uint64 `json:"size"`
|
|
|
|
// Bootscript is the bootscript used by the volume
|
|
Type string `json:"volume_type"`
|
|
|
|
// Organization is the owner of the volume
|
|
Organization string `json:"organization"`
|
|
}
|
|
|
|
// ScalewayVolumePutDefinition represents a Scaleway volume with nullable fields (for PUT)
|
|
type ScalewayVolumePutDefinition struct {
|
|
Identifier *string `json:"id,omitempty"`
|
|
Size *uint64 `json:"size,omitempty"`
|
|
CreationDate *string `json:"creation_date,omitempty"`
|
|
ModificationDate *string `json:"modification_date,omitempty"`
|
|
Organization *string `json:"organization,omitempty"`
|
|
Name *string `json:"name,omitempty"`
|
|
Server struct {
|
|
Identifier *string `json:"id,omitempty"`
|
|
Name *string `json:"name,omitempty"`
|
|
} `json:"server,omitempty"`
|
|
VolumeType *string `json:"volume_type,omitempty"`
|
|
ExportURI *string `json:"export_uri,omitempty"`
|
|
}
|
|
|
|
// ScalewayImage represents a Scaleway Image
|
|
type ScalewayImage struct {
|
|
// Identifier is a unique identifier for the image
|
|
Identifier string `json:"id,omitempty"`
|
|
|
|
// Name is a user-defined name for the image
|
|
Name string `json:"name,omitempty"`
|
|
|
|
// CreationDate is the creation date of the image
|
|
CreationDate string `json:"creation_date,omitempty"`
|
|
|
|
// ModificationDate is the date of the last modification of the image
|
|
ModificationDate string `json:"modification_date,omitempty"`
|
|
|
|
// RootVolume is the root volume bound to the image
|
|
RootVolume ScalewayVolume `json:"root_volume,omitempty"`
|
|
|
|
// Public is true for public images and false for user images
|
|
Public bool `json:"public,omitempty"`
|
|
|
|
// Bootscript is the bootscript bound to the image
|
|
DefaultBootscript *ScalewayBootscript `json:"default_bootscript,omitempty"`
|
|
|
|
// Organization is the owner of the image
|
|
Organization string `json:"organization,omitempty"`
|
|
|
|
// Arch is the architecture target of the image
|
|
Arch string `json:"arch,omitempty"`
|
|
|
|
// FIXME: extra_volumes
|
|
}
|
|
|
|
// ScalewayImageIdentifier represents a Scaleway Image Identifier
|
|
type ScalewayImageIdentifier struct {
|
|
Identifier string
|
|
Arch string
|
|
Region string
|
|
Owner string
|
|
}
|
|
|
|
// ScalewayOneImage represents the response of a GET /images/UUID API call
|
|
type ScalewayOneImage struct {
|
|
Image ScalewayImage `json:"image,omitempty"`
|
|
}
|
|
|
|
// ScalewayImages represents a group of Scaleway images
|
|
type ScalewayImages struct {
|
|
// Images holds scaleway images of the response
|
|
Images []ScalewayImage `json:"images,omitempty"`
|
|
}
|
|
|
|
// ScalewaySnapshot represents a Scaleway Snapshot
|
|
type ScalewaySnapshot struct {
|
|
// Identifier is a unique identifier for the snapshot
|
|
Identifier string `json:"id,omitempty"`
|
|
|
|
// Name is a user-defined name for the snapshot
|
|
Name string `json:"name,omitempty"`
|
|
|
|
// CreationDate is the creation date of the snapshot
|
|
CreationDate string `json:"creation_date,omitempty"`
|
|
|
|
// ModificationDate is the date of the last modification of the snapshot
|
|
ModificationDate string `json:"modification_date,omitempty"`
|
|
|
|
// Size is the allocated size of the volume
|
|
Size uint64 `json:"size,omitempty"`
|
|
|
|
// Organization is the owner of the snapshot
|
|
Organization string `json:"organization"`
|
|
|
|
// State is the current state of the snapshot
|
|
State string `json:"state"`
|
|
|
|
// VolumeType is the kind of volume behind the snapshot
|
|
VolumeType string `json:"volume_type"`
|
|
|
|
// BaseVolume is the volume from which the snapshot inherits
|
|
BaseVolume ScalewayVolume `json:"base_volume,omitempty"`
|
|
}
|
|
|
|
// ScalewayOneSnapshot represents the response of a GET /snapshots/UUID API call
|
|
type ScalewayOneSnapshot struct {
|
|
Snapshot ScalewaySnapshot `json:"snapshot,omitempty"`
|
|
}
|
|
|
|
// ScalewaySnapshots represents a group of Scaleway snapshots
|
|
type ScalewaySnapshots struct {
|
|
// Snapshots holds scaleway snapshots of the response
|
|
Snapshots []ScalewaySnapshot `json:"snapshots,omitempty"`
|
|
}
|
|
|
|
// ScalewayBootscript represents a Scaleway Bootscript
|
|
type ScalewayBootscript struct {
|
|
Bootcmdargs string `json:"bootcmdargs,omitempty"`
|
|
Dtb string `json:"dtb,omitempty"`
|
|
Initrd string `json:"initrd,omitempty"`
|
|
Kernel string `json:"kernel,omitempty"`
|
|
|
|
// Arch is the architecture target of the bootscript
|
|
Arch string `json:"architecture,omitempty"`
|
|
|
|
// Identifier is a unique identifier for the bootscript
|
|
Identifier string `json:"id,omitempty"`
|
|
|
|
// Organization is the owner of the bootscript
|
|
Organization string `json:"organization,omitempty"`
|
|
|
|
// Name is a user-defined name for the bootscript
|
|
Title string `json:"title,omitempty"`
|
|
|
|
// Public is true for public bootscripts and false for user bootscripts
|
|
Public bool `json:"public,omitempty"`
|
|
|
|
Default bool `json:"default,omitempty"`
|
|
}
|
|
|
|
// ScalewayOneBootscript represents the response of a GET /bootscripts/UUID API call
|
|
type ScalewayOneBootscript struct {
|
|
Bootscript ScalewayBootscript `json:"bootscript,omitempty"`
|
|
}
|
|
|
|
// ScalewayBootscripts represents a group of Scaleway bootscripts
|
|
type ScalewayBootscripts struct {
|
|
// Bootscripts holds Scaleway bootscripts of the response
|
|
Bootscripts []ScalewayBootscript `json:"bootscripts,omitempty"`
|
|
}
|
|
|
|
// ScalewayTask represents a Scaleway Task
|
|
type ScalewayTask struct {
|
|
// Identifier is a unique identifier for the task
|
|
Identifier string `json:"id,omitempty"`
|
|
|
|
// StartDate is the start date of the task
|
|
StartDate string `json:"started_at,omitempty"`
|
|
|
|
// TerminationDate is the termination date of the task
|
|
TerminationDate string `json:"terminated_at,omitempty"`
|
|
|
|
HrefFrom string `json:"href_from,omitempty"`
|
|
|
|
Description string `json:"description,omitempty"`
|
|
|
|
Status string `json:"status,omitempty"`
|
|
|
|
Progress int `json:"progress,omitempty"`
|
|
}
|
|
|
|
// ScalewayOneTask represents the response of a GET /tasks/UUID API call
|
|
type ScalewayOneTask struct {
|
|
Task ScalewayTask `json:"task,omitempty"`
|
|
}
|
|
|
|
// ScalewayTasks represents a group of Scaleway tasks
|
|
type ScalewayTasks struct {
|
|
// Tasks holds scaleway tasks of the response
|
|
Tasks []ScalewayTask `json:"tasks,omitempty"`
|
|
}
|
|
|
|
// ScalewaySecurityGroupRule definition
|
|
type ScalewaySecurityGroupRule struct {
|
|
Direction string `json:"direction"`
|
|
Protocol string `json:"protocol"`
|
|
IPRange string `json:"ip_range"`
|
|
DestPortFrom int `json:"dest_port_from,omitempty"`
|
|
Action string `json:"action"`
|
|
Position int `json:"position"`
|
|
DestPortTo string `json:"dest_port_to"`
|
|
Editable bool `json:"editable"`
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
// ScalewayGetSecurityGroupRules represents the response of a GET /security_group/{groupID}/rules
|
|
type ScalewayGetSecurityGroupRules struct {
|
|
Rules []ScalewaySecurityGroupRule `json:"rules"`
|
|
}
|
|
|
|
// ScalewayGetSecurityGroupRule represents the response of a GET /security_group/{groupID}/rules/{ruleID}
|
|
type ScalewayGetSecurityGroupRule struct {
|
|
Rules ScalewaySecurityGroupRule `json:"rule"`
|
|
}
|
|
|
|
// ScalewayNewSecurityGroupRule definition POST/PUT request /security_group/{groupID}
|
|
type ScalewayNewSecurityGroupRule struct {
|
|
Action string `json:"action"`
|
|
Direction string `json:"direction"`
|
|
IPRange string `json:"ip_range"`
|
|
Protocol string `json:"protocol"`
|
|
DestPortFrom int `json:"dest_port_from,omitempty"`
|
|
}
|
|
|
|
// ScalewaySecurityGroups definition
|
|
type ScalewaySecurityGroups struct {
|
|
Description string `json:"description"`
|
|
ID string `json:"id"`
|
|
Organization string `json:"organization"`
|
|
Name string `json:"name"`
|
|
Servers []ScalewaySecurityGroup `json:"servers"`
|
|
EnableDefaultSecurity bool `json:"enable_default_security"`
|
|
OrganizationDefault bool `json:"organization_default"`
|
|
}
|
|
|
|
// ScalewayGetSecurityGroups represents the response of a GET /security_groups/
|
|
type ScalewayGetSecurityGroups struct {
|
|
SecurityGroups []ScalewaySecurityGroups `json:"security_groups"`
|
|
}
|
|
|
|
// ScalewayGetSecurityGroup represents the response of a GET /security_groups/{groupID}
|
|
type ScalewayGetSecurityGroup struct {
|
|
SecurityGroups ScalewaySecurityGroups `json:"security_group"`
|
|
}
|
|
|
|
// ScalewayIPDefinition represents the IP's fields
|
|
type ScalewayIPDefinition struct {
|
|
Organization string `json:"organization"`
|
|
Reverse *string `json:"reverse"`
|
|
ID string `json:"id"`
|
|
Server *struct {
|
|
Identifier string `json:"id,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
} `json:"server"`
|
|
Address string `json:"address"`
|
|
}
|
|
|
|
// ScalewayGetIPS represents the response of a GET /ips/
|
|
type ScalewayGetIPS struct {
|
|
IPS []ScalewayIPDefinition `json:"ips"`
|
|
}
|
|
|
|
// ScalewayGetIP represents the response of a GET /ips/{id_ip}
|
|
type ScalewayGetIP struct {
|
|
IP ScalewayIPDefinition `json:"ip"`
|
|
}
|
|
|
|
// ScalewaySecurityGroup represents a Scaleway security group
|
|
type ScalewaySecurityGroup struct {
|
|
// Identifier is a unique identifier for the security group
|
|
Identifier string `json:"id,omitempty"`
|
|
|
|
// Name is the user-defined name of the security group
|
|
Name string `json:"name,omitempty"`
|
|
}
|
|
|
|
// ScalewayNewSecurityGroup definition POST request /security_groups
|
|
type ScalewayNewSecurityGroup struct {
|
|
Organization string `json:"organization"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
}
|
|
|
|
// ScalewayUpdateSecurityGroup definition PUT request /security_groups
|
|
type ScalewayUpdateSecurityGroup struct {
|
|
Organization string `json:"organization"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
OrganizationDefault bool `json:"organization_default"`
|
|
}
|
|
|
|
// ScalewayServer represents a Scaleway server
|
|
type ScalewayServer struct {
|
|
// 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
|
|
Image ScalewayImage `json:"image,omitempty"`
|
|
|
|
// 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
|
|
PublicAddress ScalewayIPAddress `json:"public_ip,omitempty"`
|
|
|
|
// 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
|
|
Bootscript *ScalewayBootscript `json:"bootscript,omitempty"`
|
|
|
|
// 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
|
|
Volumes map[string]ScalewayVolume `json:"volumes,omitempty"`
|
|
|
|
// SecurityGroup is the selected security group object
|
|
SecurityGroup ScalewaySecurityGroup `json:"security_group,omitempty"`
|
|
|
|
// 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"`
|
|
|
|
IPV6 *ScalewayIPV6Definition `json:"ipv6,omitempty"`
|
|
|
|
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"`
|
|
}
|
|
|
|
// ScalewayIPV6Definition represents a Scaleway ipv6
|
|
type ScalewayIPV6Definition struct {
|
|
Netmask string `json:"netmask"`
|
|
Gateway string `json:"gateway"`
|
|
Address string `json:"address"`
|
|
}
|
|
|
|
// ScalewayServerPatchDefinition represents a Scaleway server with nullable fields (for PATCH)
|
|
type ScalewayServerPatchDefinition struct {
|
|
Arch *string `json:"arch,omitempty"`
|
|
Name *string `json:"name,omitempty"`
|
|
CreationDate *string `json:"creation_date,omitempty"`
|
|
ModificationDate *string `json:"modification_date,omitempty"`
|
|
Image *ScalewayImage `json:"image,omitempty"`
|
|
DynamicIPRequired *bool `json:"dynamic_ip_required,omitempty"`
|
|
PublicAddress *ScalewayIPAddress `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]ScalewayVolume `json:"volumes,omitempty"`
|
|
SecurityGroup *ScalewaySecurityGroup `json:"security_group,omitempty"`
|
|
Organization *string `json:"organization,omitempty"`
|
|
Tags *[]string `json:"tags,omitempty"`
|
|
IPV6 *ScalewayIPV6Definition `json:"ipv6,omitempty"`
|
|
EnableIPV6 *bool `json:"enable_ipv6,omitempty"`
|
|
}
|
|
|
|
// ScalewayServerDefinition represents a Scaleway server with image definition
|
|
type ScalewayServerDefinition struct {
|
|
// 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"`
|
|
|
|
PublicIP string `json:"public_ip,omitempty"`
|
|
|
|
EnableIPV6 bool `json:"enable_ipv6,omitempty"`
|
|
|
|
SecurityGroup string `json:"security_group,omitempty"`
|
|
}
|
|
|
|
// ScalewayOneServer represents the response of a GET /servers/UUID API call
|
|
type ScalewayOneServer struct {
|
|
Server ScalewayServer `json:"server,omitempty"`
|
|
}
|
|
|
|
// ScalewayServers represents a group of Scaleway servers
|
|
type ScalewayServers struct {
|
|
// Servers holds scaleway servers of the response
|
|
Servers []ScalewayServer `json:"servers,omitempty"`
|
|
}
|
|
|
|
// ScalewayServerAction represents an action to perform on a Scaleway server
|
|
type ScalewayServerAction struct {
|
|
// Action is the name of the action to trigger
|
|
Action string `json:"action,omitempty"`
|
|
}
|
|
|
|
// ScalewaySnapshotDefinition represents a Scaleway snapshot definition
|
|
type ScalewaySnapshotDefinition struct {
|
|
VolumeIDentifier string `json:"volume_id"`
|
|
Name string `json:"name,omitempty"`
|
|
Organization string `json:"organization"`
|
|
}
|
|
|
|
// ScalewayImageDefinition represents a Scaleway image definition
|
|
type ScalewayImageDefinition struct {
|
|
SnapshotIDentifier string `json:"root_volume"`
|
|
Name string `json:"name,omitempty"`
|
|
Organization string `json:"organization"`
|
|
Arch string `json:"arch"`
|
|
DefaultBootscript *string `json:"default_bootscript,omitempty"`
|
|
}
|
|
|
|
// ScalewayRoleDefinition represents a Scaleway Token UserId Role
|
|
type ScalewayRoleDefinition struct {
|
|
Organization ScalewayOrganizationDefinition `json:"organization,omitempty"`
|
|
Role string `json:"role,omitempty"`
|
|
}
|
|
|
|
// ScalewayTokenDefinition represents a Scaleway Token
|
|
type ScalewayTokenDefinition struct {
|
|
UserID string `json:"user_id"`
|
|
Description string `json:"description,omitempty"`
|
|
Roles ScalewayRoleDefinition `json:"roles"`
|
|
Expires string `json:"expires"`
|
|
InheritsUsersPerms bool `json:"inherits_user_perms"`
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
// ScalewayTokensDefinition represents a Scaleway Tokens
|
|
type ScalewayTokensDefinition struct {
|
|
Token ScalewayTokenDefinition `json:"token"`
|
|
}
|
|
|
|
// ScalewayGetTokens represents a list of Scaleway Tokens
|
|
type ScalewayGetTokens struct {
|
|
Tokens []ScalewayTokenDefinition `json:"tokens"`
|
|
}
|
|
|
|
// ScalewayContainerData represents a Scaleway container data (S3)
|
|
type ScalewayContainerData struct {
|
|
LastModified string `json:"last_modified"`
|
|
Name string `json:"name"`
|
|
Size string `json:"size"`
|
|
}
|
|
|
|
// ScalewayGetContainerDatas represents a list of Scaleway containers data (S3)
|
|
type ScalewayGetContainerDatas struct {
|
|
Container []ScalewayContainerData `json:"container"`
|
|
}
|
|
|
|
// ScalewayContainer represents a Scaleway container (S3)
|
|
type ScalewayContainer struct {
|
|
ScalewayOrganizationDefinition `json:"organization"`
|
|
Name string `json:"name"`
|
|
Size string `json:"size"`
|
|
}
|
|
|
|
// ScalewayGetContainers represents a list of Scaleway containers (S3)
|
|
type ScalewayGetContainers struct {
|
|
Containers []ScalewayContainer `json:"containers"`
|
|
}
|
|
|
|
// ScalewayConnectResponse represents the answer from POST /tokens
|
|
type ScalewayConnectResponse struct {
|
|
Token ScalewayTokenDefinition `json:"token"`
|
|
}
|
|
|
|
// ScalewayConnect represents the data to connect
|
|
type ScalewayConnect struct {
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
Description string `json:"description"`
|
|
Expires bool `json:"expires"`
|
|
}
|
|
|
|
// ScalewayOrganizationDefinition represents a Scaleway Organization
|
|
type ScalewayOrganizationDefinition struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Users []ScalewayUserDefinition `json:"users"`
|
|
}
|
|
|
|
// ScalewayOrganizationsDefinition represents a Scaleway Organizations
|
|
type ScalewayOrganizationsDefinition struct {
|
|
Organizations []ScalewayOrganizationDefinition `json:"organizations"`
|
|
}
|
|
|
|
// ScalewayUserDefinition represents a Scaleway User
|
|
type ScalewayUserDefinition struct {
|
|
Email string `json:"email"`
|
|
Firstname string `json:"firstname"`
|
|
Fullname string `json:"fullname"`
|
|
ID string `json:"id"`
|
|
Lastname string `json:"lastname"`
|
|
Organizations []ScalewayOrganizationDefinition `json:"organizations"`
|
|
Roles []ScalewayRoleDefinition `json:"roles"`
|
|
SSHPublicKeys []ScalewayKeyDefinition `json:"ssh_public_keys"`
|
|
}
|
|
|
|
// ScalewayUsersDefinition represents the response of a GET /user
|
|
type ScalewayUsersDefinition struct {
|
|
User ScalewayUserDefinition `json:"user"`
|
|
}
|
|
|
|
// ScalewayKeyDefinition represents a key
|
|
type ScalewayKeyDefinition struct {
|
|
Key string `json:"key"`
|
|
Fingerprint string `json:"fingerprint,omitempty"`
|
|
}
|
|
|
|
// ScalewayUserPatchSSHKeyDefinition represents a User Patch
|
|
type ScalewayUserPatchSSHKeyDefinition struct {
|
|
SSHPublicKeys []ScalewayKeyDefinition `json:"ssh_public_keys"`
|
|
}
|
|
|
|
// ScalewayDashboardResp represents a dashboard received from the API
|
|
type ScalewayDashboardResp struct {
|
|
Dashboard ScalewayDashboard
|
|
}
|
|
|
|
// ScalewayDashboard represents a dashboard
|
|
type ScalewayDashboard struct {
|
|
VolumesCount int `json:"volumes_count"`
|
|
RunningServersCount int `json:"running_servers_count"`
|
|
ImagesCount int `json:"images_count"`
|
|
SnapshotsCount int `json:"snapshots_count"`
|
|
ServersCount int `json:"servers_count"`
|
|
IPsCount int `json:"ips_count"`
|
|
}
|
|
|
|
// ScalewayPermissions represents the response of GET /permissions
|
|
type ScalewayPermissions map[string]ScalewayPermCategory
|
|
|
|
// ScalewayPermCategory represents ScalewayPermissions's fields
|
|
type ScalewayPermCategory map[string][]string
|
|
|
|
// ScalewayPermissionDefinition represents the permissions
|
|
type ScalewayPermissionDefinition struct {
|
|
Permissions ScalewayPermissions `json:"permissions"`
|
|
}
|
|
|
|
// ScalewayUserdatas represents the response of a GET /user_data
|
|
type ScalewayUserdatas struct {
|
|
UserData []string `json:"user_data"`
|
|
}
|
|
|
|
// ScalewayQuota represents a map of quota (name, value)
|
|
type ScalewayQuota map[string]int
|
|
|
|
// ScalewayGetQuotas represents the response of GET /organizations/{orga_id}/quotas
|
|
type ScalewayGetQuotas struct {
|
|
Quotas ScalewayQuota `json:"quotas"`
|
|
}
|
|
|
|
// ScalewayUserdata represents []byte
|
|
type ScalewayUserdata []byte
|
|
|
|
// FuncMap used for json inspection
|
|
var FuncMap = template.FuncMap{
|
|
"json": func(v interface{}) string {
|
|
a, _ := json.Marshal(v)
|
|
return string(a)
|
|
},
|
|
}
|
|
|
|
// MarketLocalImageDefinition represents localImage of marketplace version
|
|
type MarketLocalImageDefinition struct {
|
|
Arch string `json:"arch"`
|
|
ID string `json:"id"`
|
|
Zone string `json:"zone"`
|
|
}
|
|
|
|
// MarketLocalImages represents an array of local images
|
|
type MarketLocalImages struct {
|
|
LocalImages []MarketLocalImageDefinition `json:"local_images"`
|
|
}
|
|
|
|
// MarketLocalImage represents local image
|
|
type MarketLocalImage struct {
|
|
LocalImages MarketLocalImageDefinition `json:"local_image"`
|
|
}
|
|
|
|
// MarketVersionDefinition represents version of marketplace image
|
|
type MarketVersionDefinition struct {
|
|
CreationDate string `json:"creation_date"`
|
|
ID string `json:"id"`
|
|
Image struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
} `json:"image"`
|
|
ModificationDate string `json:"modification_date"`
|
|
Name string `json:"name"`
|
|
MarketLocalImages
|
|
}
|
|
|
|
// MarketVersions represents an array of marketplace image versions
|
|
type MarketVersions struct {
|
|
Versions []MarketVersionDefinition `json:"versions"`
|
|
}
|
|
|
|
// MarketVersion represents version of marketplace image
|
|
type MarketVersion struct {
|
|
Version MarketVersionDefinition `json:"version"`
|
|
}
|
|
|
|
// MarketImage represents MarketPlace image
|
|
type MarketImage struct {
|
|
Categories []string `json:"categories"`
|
|
CreationDate string `json:"creation_date"`
|
|
CurrentPublicVersion string `json:"current_public_version"`
|
|
Description string `json:"description"`
|
|
ID string `json:"id"`
|
|
Logo string `json:"logo"`
|
|
ModificationDate string `json:"modification_date"`
|
|
Name string `json:"name"`
|
|
Organization struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
} `json:"organization"`
|
|
Public bool `json:"-"`
|
|
MarketVersions
|
|
}
|
|
|
|
// MarketImages represents MarketPlace images
|
|
type MarketImages struct {
|
|
Images []MarketImage `json:"images"`
|
|
}
|
|
|
|
// NewScalewayAPI creates a ready-to-use ScalewayAPI client
|
|
func NewScalewayAPI(organization, token, userAgent, region string, options ...func(*ScalewayAPI)) (*ScalewayAPI, error) {
|
|
s := &ScalewayAPI{
|
|
// exposed
|
|
Organization: organization,
|
|
Token: token,
|
|
Logger: NewDefaultLogger(),
|
|
|
|
// internal
|
|
client: &http.Client{},
|
|
verbose: os.Getenv("SCW_VERBOSE_API") != "",
|
|
password: "",
|
|
userAgent: userAgent,
|
|
}
|
|
for _, option := range options {
|
|
option(s)
|
|
}
|
|
cache, err := NewScalewayCache(func() { s.Logger.Debugf("Writing cache file to disk") })
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.Cache = cache
|
|
if os.Getenv("SCW_TLSVERIFY") == "0" {
|
|
s.client.Transport = &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
}
|
|
}
|
|
switch region {
|
|
case "par1", "":
|
|
s.computeAPI = ComputeAPIPar1
|
|
case "ams1":
|
|
s.computeAPI = ComputeAPIAms1
|
|
default:
|
|
return nil, fmt.Errorf("%s isn't a valid region", region)
|
|
}
|
|
s.Region = region
|
|
if url := os.Getenv("SCW_COMPUTE_API"); url != "" {
|
|
s.computeAPI = url
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// ClearCache clears the cache
|
|
func (s *ScalewayAPI) ClearCache() {
|
|
s.Cache.Clear()
|
|
}
|
|
|
|
// Sync flushes out the cache to the disk
|
|
func (s *ScalewayAPI) Sync() {
|
|
s.Cache.Save()
|
|
}
|
|
|
|
func (s *ScalewayAPI) response(method, uri string, content io.Reader) (resp *http.Response, err error) {
|
|
var (
|
|
req *http.Request
|
|
)
|
|
|
|
req, err = http.NewRequest(method, uri, content)
|
|
if err != nil {
|
|
err = fmt.Errorf("response %s %s", method, uri)
|
|
return
|
|
}
|
|
req.Header.Set("X-Auth-Token", s.Token)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("User-Agent", s.userAgent)
|
|
s.LogHTTP(req)
|
|
if s.verbose {
|
|
dump, _ := httputil.DumpRequest(req, true)
|
|
s.Debugf("%v", string(dump))
|
|
} else {
|
|
s.Debugf("[%s]: %v", method, uri)
|
|
}
|
|
resp, err = s.client.Do(req)
|
|
return
|
|
}
|
|
|
|
// GetResponsePaginate fetchs all resources and returns an http.Response object for the requested resource
|
|
func (s *ScalewayAPI) GetResponsePaginate(apiURL, resource string, values url.Values) (*http.Response, error) {
|
|
resp, err := s.response("HEAD", fmt.Sprintf("%s/%s?%s", strings.TrimRight(apiURL, "/"), resource, values.Encode()), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
count := resp.Header.Get("X-Total-Count")
|
|
var maxElem int
|
|
if count == "" {
|
|
maxElem = 0
|
|
} else {
|
|
maxElem, err = strconv.Atoi(count)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
get := maxElem / perPage
|
|
if (float32(maxElem) / perPage) > float32(get) {
|
|
get++
|
|
}
|
|
|
|
if get <= 1 { // If there is 0 or 1 page of result, the response is not paginated
|
|
if len(values) == 0 {
|
|
return s.response("GET", fmt.Sprintf("%s/%s", strings.TrimRight(apiURL, "/"), resource), nil)
|
|
}
|
|
return s.response("GET", fmt.Sprintf("%s/%s?%s", strings.TrimRight(apiURL, "/"), resource, values.Encode()), nil)
|
|
}
|
|
|
|
fetchAll := !(values.Get("per_page") != "" || values.Get("page") != "")
|
|
if fetchAll {
|
|
var g errgroup.Group
|
|
|
|
ch := make(chan *http.Response, get)
|
|
for i := 1; i <= get; i++ {
|
|
i := i // closure tricks
|
|
g.Go(func() (err error) {
|
|
var resp *http.Response
|
|
|
|
val := url.Values{}
|
|
val.Set("per_page", fmt.Sprintf("%v", perPage))
|
|
val.Set("page", fmt.Sprintf("%v", i))
|
|
resp, err = s.response("GET", fmt.Sprintf("%s/%s?%s", strings.TrimRight(apiURL, "/"), resource, val.Encode()), nil)
|
|
ch <- resp
|
|
return
|
|
})
|
|
}
|
|
if err = g.Wait(); err != nil {
|
|
return nil, err
|
|
}
|
|
newBody := make(map[string][]json.RawMessage)
|
|
body := make(map[string][]json.RawMessage)
|
|
key := ""
|
|
for i := 0; i < get; i++ {
|
|
res := <-ch
|
|
if res.StatusCode != http.StatusOK {
|
|
return res, nil
|
|
}
|
|
content, err := ioutil.ReadAll(res.Body)
|
|
res.Body.Close()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := json.Unmarshal(content, &body); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if i == 0 {
|
|
resp = res
|
|
for k := range body {
|
|
key = k
|
|
break
|
|
}
|
|
}
|
|
newBody[key] = append(newBody[key], body[key]...)
|
|
}
|
|
payload := new(bytes.Buffer)
|
|
if err := json.NewEncoder(payload).Encode(newBody); err != nil {
|
|
return nil, err
|
|
}
|
|
resp.Body = ioutil.NopCloser(payload)
|
|
} else {
|
|
resp, err = s.response("GET", fmt.Sprintf("%s/%s?%s", strings.TrimRight(apiURL, "/"), resource, values.Encode()), nil)
|
|
}
|
|
return resp, err
|
|
}
|
|
|
|
// PostResponse returns an http.Response object for the updated resource
|
|
func (s *ScalewayAPI) PostResponse(apiURL, resource string, data interface{}) (*http.Response, error) {
|
|
payload := new(bytes.Buffer)
|
|
if err := json.NewEncoder(payload).Encode(data); err != nil {
|
|
return nil, err
|
|
}
|
|
return s.response("POST", fmt.Sprintf("%s/%s", strings.TrimRight(apiURL, "/"), resource), payload)
|
|
}
|
|
|
|
// PatchResponse returns an http.Response object for the updated resource
|
|
func (s *ScalewayAPI) PatchResponse(apiURL, resource string, data interface{}) (*http.Response, error) {
|
|
payload := new(bytes.Buffer)
|
|
if err := json.NewEncoder(payload).Encode(data); err != nil {
|
|
return nil, err
|
|
}
|
|
return s.response("PATCH", fmt.Sprintf("%s/%s", strings.TrimRight(apiURL, "/"), resource), payload)
|
|
}
|
|
|
|
// PutResponse returns an http.Response object for the updated resource
|
|
func (s *ScalewayAPI) PutResponse(apiURL, resource string, data interface{}) (*http.Response, error) {
|
|
payload := new(bytes.Buffer)
|
|
if err := json.NewEncoder(payload).Encode(data); err != nil {
|
|
return nil, err
|
|
}
|
|
return s.response("PUT", fmt.Sprintf("%s/%s", strings.TrimRight(apiURL, "/"), resource), payload)
|
|
}
|
|
|
|
// DeleteResponse returns an http.Response object for the deleted resource
|
|
func (s *ScalewayAPI) DeleteResponse(apiURL, resource string) (*http.Response, error) {
|
|
return s.response("DELETE", fmt.Sprintf("%s/%s", strings.TrimRight(apiURL, "/"), resource), nil)
|
|
}
|
|
|
|
// handleHTTPError checks the statusCode and displays the error
|
|
func (s *ScalewayAPI) handleHTTPError(goodStatusCode []int, resp *http.Response) ([]byte, error) {
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if s.verbose {
|
|
resp.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
|
dump, err := httputil.DumpResponse(resp, true)
|
|
if err == nil {
|
|
var js bytes.Buffer
|
|
|
|
err = json.Indent(&js, body, "", " ")
|
|
if err != nil {
|
|
s.Debugf("[Response]: [%v]\n%v", resp.StatusCode, string(dump))
|
|
} else {
|
|
s.Debugf("[Response]: [%v]\n%v", resp.StatusCode, js.String())
|
|
}
|
|
}
|
|
} else {
|
|
s.Debugf("[Response]: [%v]\n%v", resp.StatusCode, string(body))
|
|
}
|
|
|
|
if resp.StatusCode >= http.StatusInternalServerError {
|
|
return nil, errors.New(string(body))
|
|
}
|
|
good := false
|
|
for _, code := range goodStatusCode {
|
|
if code == resp.StatusCode {
|
|
good = true
|
|
}
|
|
}
|
|
if !good {
|
|
var scwError ScalewayAPIError
|
|
|
|
if err := json.Unmarshal(body, &scwError); err != nil {
|
|
return nil, err
|
|
}
|
|
scwError.StatusCode = resp.StatusCode
|
|
s.Debugf("%s", scwError.Error())
|
|
return nil, scwError
|
|
}
|
|
return body, nil
|
|
}
|
|
|
|
func (s *ScalewayAPI) fetchServers(api string, query url.Values, out chan<- ScalewayServers) func() error {
|
|
return func() error {
|
|
resp, err := s.GetResponsePaginate(api, "servers", query)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := s.handleHTTPError([]int{http.StatusOK}, resp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var servers ScalewayServers
|
|
|
|
if err = json.Unmarshal(body, &servers); err != nil {
|
|
return err
|
|
}
|
|
out <- servers
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// GetServers gets the list of servers from the ScalewayAPI
|
|
func (s *ScalewayAPI) GetServers(all bool, limit int) (*[]ScalewayServer, error) {
|
|
query := url.Values{}
|
|
if !all {
|
|
query.Set("state", "running")
|
|
}
|
|
if limit > 0 {
|
|
// FIXME: wait for the API to be ready
|
|
// query.Set("per_page", strconv.Itoa(limit))
|
|
panic("Not implemented yet")
|
|
}
|
|
if all && limit == 0 {
|
|
s.Cache.ClearServers()
|
|
}
|
|
var (
|
|
g errgroup.Group
|
|
apis = []string{
|
|
ComputeAPIPar1,
|
|
ComputeAPIAms1,
|
|
}
|
|
)
|
|
|
|
serverChan := make(chan ScalewayServers, 2)
|
|
for _, api := range apis {
|
|
g.Go(s.fetchServers(api, query, serverChan))
|
|
}
|
|
|
|
if err := g.Wait(); err != nil {
|
|
return nil, err
|
|
}
|
|
close(serverChan)
|
|
var servers ScalewayServers
|
|
|
|
for server := range serverChan {
|
|
servers.Servers = append(servers.Servers, server.Servers...)
|
|
}
|
|
|
|
for i, server := range servers.Servers {
|
|
servers.Servers[i].DNSPublic = server.Identifier + URLPublicDNS
|
|
servers.Servers[i].DNSPrivate = server.Identifier + URLPrivateDNS
|
|
s.Cache.InsertServer(server.Identifier, server.Location.ZoneID, server.Arch, server.Organization, server.Name)
|
|
}
|
|
return &servers.Servers, nil
|
|
}
|
|
|
|
// ScalewaySortServers represents a wrapper to sort by CreationDate the servers
|
|
type ScalewaySortServers []ScalewayServer
|
|
|
|
func (s ScalewaySortServers) Len() int {
|
|
return len(s)
|
|
}
|
|
|
|
func (s ScalewaySortServers) Swap(i, j int) {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|
|
|
|
func (s ScalewaySortServers) Less(i, j int) bool {
|
|
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)
|
|
}
|
|
|
|
// GetServer gets a server from the ScalewayAPI
|
|
func (s *ScalewayAPI) GetServer(serverID string) (*ScalewayServer, error) {
|
|
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
|
|
}
|
|
|
|
var oneServer ScalewayOneServer
|
|
|
|
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
|
|
s.Cache.InsertServer(oneServer.Server.Identifier, oneServer.Server.Location.ZoneID, oneServer.Server.Arch, oneServer.Server.Organization, oneServer.Server.Name)
|
|
return &oneServer.Server, nil
|
|
}
|
|
|
|
// PostServerAction posts an action on a server
|
|
func (s *ScalewayAPI) PostServerAction(serverID, action string) error {
|
|
data := ScalewayServerAction{
|
|
Action: action,
|
|
}
|
|
resp, err := s.PostResponse(s.computeAPI, fmt.Sprintf("servers/%s/action", serverID), data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
_, err = s.handleHTTPError([]int{http.StatusAccepted}, resp)
|
|
return err
|
|
}
|
|
|
|
// DeleteServer deletes a server
|
|
func (s *ScalewayAPI) DeleteServer(serverID string) error {
|
|
defer s.Cache.RemoveServer(serverID)
|
|
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
|
|
}
|
|
|
|
// PostServer creates a new server
|
|
func (s *ScalewayAPI) PostServer(definition ScalewayServerDefinition) (string, error) {
|
|
definition.Organization = s.Organization
|
|
|
|
resp, err := s.PostResponse(s.computeAPI, "servers", definition)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := s.handleHTTPError([]int{http.StatusCreated}, resp)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
var server ScalewayOneServer
|
|
|
|
if err = json.Unmarshal(body, &server); err != nil {
|
|
return "", err
|
|
}
|
|
// FIXME arch, owner, title
|
|
s.Cache.InsertServer(server.Server.Identifier, server.Server.Location.ZoneID, server.Server.Arch, server.Server.Organization, server.Server.Name)
|
|
return server.Server.Identifier, nil
|
|
}
|
|
|
|
// PatchUserSSHKey updates a user
|
|
func (s *ScalewayAPI) PatchUserSSHKey(UserID string, definition ScalewayUserPatchSSHKeyDefinition) error {
|
|
resp, err := s.PatchResponse(AccountAPI, fmt.Sprintf("users/%s", UserID), definition)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
if _, err := s.handleHTTPError([]int{http.StatusOK}, resp); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PatchServer updates a server
|
|
func (s *ScalewayAPI) PatchServer(serverID string, definition ScalewayServerPatchDefinition) error {
|
|
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
|
|
}
|
|
|
|
// PostSnapshot creates a new snapshot
|
|
func (s *ScalewayAPI) PostSnapshot(volumeID string, name string) (string, error) {
|
|
definition := ScalewaySnapshotDefinition{
|
|
VolumeIDentifier: volumeID,
|
|
Name: name,
|
|
Organization: s.Organization,
|
|
}
|
|
resp, err := s.PostResponse(s.computeAPI, "snapshots", definition)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := s.handleHTTPError([]int{http.StatusCreated}, resp)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
var snapshot ScalewayOneSnapshot
|
|
|
|
if err = json.Unmarshal(body, &snapshot); err != nil {
|
|
return "", err
|
|
}
|
|
// FIXME arch, owner, title
|
|
s.Cache.InsertSnapshot(snapshot.Snapshot.Identifier, "", "", snapshot.Snapshot.Organization, snapshot.Snapshot.Name)
|
|
return snapshot.Snapshot.Identifier, nil
|
|
}
|
|
|
|
// PostImage creates a new image
|
|
func (s *ScalewayAPI) PostImage(volumeID string, name string, bootscript string, arch string) (string, error) {
|
|
definition := ScalewayImageDefinition{
|
|
SnapshotIDentifier: volumeID,
|
|
Name: name,
|
|
Organization: s.Organization,
|
|
Arch: arch,
|
|
}
|
|
if bootscript != "" {
|
|
definition.DefaultBootscript = &bootscript
|
|
}
|
|
|
|
resp, err := s.PostResponse(s.computeAPI, "images", definition)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := s.handleHTTPError([]int{http.StatusCreated}, resp)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
var image ScalewayOneImage
|
|
|
|
if err = json.Unmarshal(body, &image); err != nil {
|
|
return "", err
|
|
}
|
|
// FIXME region, arch, owner, title
|
|
s.Cache.InsertImage(image.Image.Identifier, "", image.Image.Arch, image.Image.Organization, image.Image.Name, "")
|
|
return image.Image.Identifier, nil
|
|
}
|
|
|
|
// PostVolume creates a new volume
|
|
func (s *ScalewayAPI) PostVolume(definition ScalewayVolumeDefinition) (string, error) {
|
|
definition.Organization = s.Organization
|
|
if definition.Type == "" {
|
|
definition.Type = "l_ssd"
|
|
}
|
|
|
|
resp, err := s.PostResponse(s.computeAPI, "volumes", definition)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := s.handleHTTPError([]int{http.StatusCreated}, resp)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
var volume ScalewayOneVolume
|
|
|
|
if err = json.Unmarshal(body, &volume); err != nil {
|
|
return "", err
|
|
}
|
|
// FIXME: s.Cache.InsertVolume(volume.Volume.Identifier, volume.Volume.Name)
|
|
return volume.Volume.Identifier, nil
|
|
}
|
|
|
|
// PutVolume updates a volume
|
|
func (s *ScalewayAPI) PutVolume(volumeID string, definition ScalewayVolumePutDefinition) error {
|
|
resp, err := s.PutResponse(s.computeAPI, fmt.Sprintf("volumes/%s", volumeID), definition)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
_, err = s.handleHTTPError([]int{http.StatusOK}, resp)
|
|
return err
|
|
}
|
|
|
|
// ResolveServer attempts to find a matching Identifier for the input string
|
|
func (s *ScalewayAPI) ResolveServer(needle string) (ScalewayResolverResults, error) {
|
|
servers, err := s.Cache.LookUpServers(needle, true)
|
|
if err != nil {
|
|
return servers, err
|
|
}
|
|
if len(servers) == 0 {
|
|
if _, err = s.GetServers(true, 0); err != nil {
|
|
return nil, err
|
|
}
|
|
servers, err = s.Cache.LookUpServers(needle, true)
|
|
}
|
|
return servers, err
|
|
}
|
|
|
|
// ResolveVolume attempts to find a matching Identifier for the input string
|
|
func (s *ScalewayAPI) ResolveVolume(needle string) (ScalewayResolverResults, error) {
|
|
volumes, err := s.Cache.LookUpVolumes(needle, true)
|
|
if err != nil {
|
|
return volumes, err
|
|
}
|
|
if len(volumes) == 0 {
|
|
if _, err = s.GetVolumes(); err != nil {
|
|
return nil, err
|
|
}
|
|
volumes, err = s.Cache.LookUpVolumes(needle, true)
|
|
}
|
|
return volumes, err
|
|
}
|
|
|
|
// ResolveSnapshot attempts to find a matching Identifier for the input string
|
|
func (s *ScalewayAPI) ResolveSnapshot(needle string) (ScalewayResolverResults, error) {
|
|
snapshots, err := s.Cache.LookUpSnapshots(needle, true)
|
|
if err != nil {
|
|
return snapshots, err
|
|
}
|
|
if len(snapshots) == 0 {
|
|
if _, err = s.GetSnapshots(); err != nil {
|
|
return nil, err
|
|
}
|
|
snapshots, err = s.Cache.LookUpSnapshots(needle, true)
|
|
}
|
|
return snapshots, err
|
|
}
|
|
|
|
// ResolveImage attempts to find a matching Identifier for the input string
|
|
func (s *ScalewayAPI) ResolveImage(needle string) (ScalewayResolverResults, error) {
|
|
images, err := s.Cache.LookUpImages(needle, true)
|
|
if err != nil {
|
|
return images, err
|
|
}
|
|
if len(images) == 0 {
|
|
if _, err = s.GetImages(); err != nil {
|
|
return nil, err
|
|
}
|
|
images, err = s.Cache.LookUpImages(needle, true)
|
|
}
|
|
return images, err
|
|
}
|
|
|
|
// ResolveBootscript attempts to find a matching Identifier for the input string
|
|
func (s *ScalewayAPI) ResolveBootscript(needle string) (ScalewayResolverResults, error) {
|
|
bootscripts, err := s.Cache.LookUpBootscripts(needle, true)
|
|
if err != nil {
|
|
return bootscripts, err
|
|
}
|
|
if len(bootscripts) == 0 {
|
|
if _, err = s.GetBootscripts(); err != nil {
|
|
return nil, err
|
|
}
|
|
bootscripts, err = s.Cache.LookUpBootscripts(needle, true)
|
|
}
|
|
return bootscripts, err
|
|
}
|
|
|
|
// GetImages gets the list of images from the ScalewayAPI
|
|
func (s *ScalewayAPI) GetImages() (*[]MarketImage, error) {
|
|
images, err := s.GetMarketPlaceImages("")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.Cache.ClearImages()
|
|
for i, image := range images.Images {
|
|
if image.CurrentPublicVersion != "" {
|
|
for _, version := range image.Versions {
|
|
if version.ID == image.CurrentPublicVersion {
|
|
for _, localImage := range version.LocalImages {
|
|
images.Images[i].Public = true
|
|
s.Cache.InsertImage(localImage.ID, localImage.Zone, localImage.Arch, image.Organization.ID, image.Name, image.CurrentPublicVersion)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
values := url.Values{}
|
|
values.Set("organization", s.Organization)
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, "images", 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
|
|
}
|
|
var OrgaImages ScalewayImages
|
|
|
|
if err = json.Unmarshal(body, &OrgaImages); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, orgaImage := range OrgaImages.Images {
|
|
images.Images = append(images.Images, MarketImage{
|
|
Categories: []string{"MyImages"},
|
|
CreationDate: orgaImage.CreationDate,
|
|
CurrentPublicVersion: orgaImage.Identifier,
|
|
ModificationDate: orgaImage.ModificationDate,
|
|
Name: orgaImage.Name,
|
|
Public: false,
|
|
MarketVersions: MarketVersions{
|
|
Versions: []MarketVersionDefinition{
|
|
{
|
|
CreationDate: orgaImage.CreationDate,
|
|
ID: orgaImage.Identifier,
|
|
ModificationDate: orgaImage.ModificationDate,
|
|
MarketLocalImages: MarketLocalImages{
|
|
LocalImages: []MarketLocalImageDefinition{
|
|
{
|
|
Arch: orgaImage.Arch,
|
|
ID: orgaImage.Identifier,
|
|
// TODO: fecth images from ams1 and par1
|
|
Zone: s.Region,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
s.Cache.InsertImage(orgaImage.Identifier, s.Region, orgaImage.Arch, orgaImage.Organization, orgaImage.Name, "")
|
|
}
|
|
return &images.Images, nil
|
|
}
|
|
|
|
// GetImage gets an image from the ScalewayAPI
|
|
func (s *ScalewayAPI) GetImage(imageID string) (*ScalewayImage, error) {
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, "images/"+imageID, 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
|
|
}
|
|
var oneImage ScalewayOneImage
|
|
|
|
if err = json.Unmarshal(body, &oneImage); err != nil {
|
|
return nil, err
|
|
}
|
|
// FIXME owner, title
|
|
s.Cache.InsertImage(oneImage.Image.Identifier, s.Region, oneImage.Image.Arch, oneImage.Image.Organization, oneImage.Image.Name, "")
|
|
return &oneImage.Image, nil
|
|
}
|
|
|
|
// DeleteImage deletes a image
|
|
func (s *ScalewayAPI) DeleteImage(imageID string) error {
|
|
defer s.Cache.RemoveImage(imageID)
|
|
resp, err := s.DeleteResponse(s.computeAPI, fmt.Sprintf("images/%s", imageID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if _, err := s.handleHTTPError([]int{http.StatusNoContent}, resp); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DeleteSnapshot deletes a snapshot
|
|
func (s *ScalewayAPI) DeleteSnapshot(snapshotID string) error {
|
|
defer s.Cache.RemoveSnapshot(snapshotID)
|
|
resp, err := s.DeleteResponse(s.computeAPI, fmt.Sprintf("snapshots/%s", snapshotID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if _, err := s.handleHTTPError([]int{http.StatusNoContent}, resp); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DeleteVolume deletes a volume
|
|
func (s *ScalewayAPI) DeleteVolume(volumeID string) error {
|
|
defer s.Cache.RemoveVolume(volumeID)
|
|
resp, err := s.DeleteResponse(s.computeAPI, fmt.Sprintf("volumes/%s", volumeID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if _, err := s.handleHTTPError([]int{http.StatusNoContent}, resp); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetSnapshots gets the list of snapshots from the ScalewayAPI
|
|
func (s *ScalewayAPI) GetSnapshots() (*[]ScalewaySnapshot, error) {
|
|
query := url.Values{}
|
|
s.Cache.ClearSnapshots()
|
|
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, "snapshots", query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := s.handleHTTPError([]int{http.StatusOK}, resp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var snapshots ScalewaySnapshots
|
|
|
|
if err = json.Unmarshal(body, &snapshots); err != nil {
|
|
return nil, err
|
|
}
|
|
for _, snapshot := range snapshots.Snapshots {
|
|
// FIXME region, arch, owner, title
|
|
s.Cache.InsertSnapshot(snapshot.Identifier, "", "", snapshot.Organization, snapshot.Name)
|
|
}
|
|
return &snapshots.Snapshots, nil
|
|
}
|
|
|
|
// GetSnapshot gets a snapshot from the ScalewayAPI
|
|
func (s *ScalewayAPI) GetSnapshot(snapshotID string) (*ScalewaySnapshot, error) {
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, "snapshots/"+snapshotID, 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
|
|
}
|
|
var oneSnapshot ScalewayOneSnapshot
|
|
|
|
if err = json.Unmarshal(body, &oneSnapshot); err != nil {
|
|
return nil, err
|
|
}
|
|
// FIXME region, arch, owner, title
|
|
s.Cache.InsertSnapshot(oneSnapshot.Snapshot.Identifier, "", "", oneSnapshot.Snapshot.Organization, oneSnapshot.Snapshot.Name)
|
|
return &oneSnapshot.Snapshot, nil
|
|
}
|
|
|
|
// GetVolumes gets the list of volumes from the ScalewayAPI
|
|
func (s *ScalewayAPI) GetVolumes() (*[]ScalewayVolume, error) {
|
|
query := url.Values{}
|
|
s.Cache.ClearVolumes()
|
|
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, "volumes", query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := s.handleHTTPError([]int{http.StatusOK}, resp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var volumes ScalewayVolumes
|
|
|
|
if err = json.Unmarshal(body, &volumes); err != nil {
|
|
return nil, err
|
|
}
|
|
for _, volume := range volumes.Volumes {
|
|
// FIXME region, arch, owner, title
|
|
s.Cache.InsertVolume(volume.Identifier, "", "", volume.Organization, volume.Name)
|
|
}
|
|
return &volumes.Volumes, nil
|
|
}
|
|
|
|
// GetVolume gets a volume from the ScalewayAPI
|
|
func (s *ScalewayAPI) GetVolume(volumeID string) (*ScalewayVolume, error) {
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, "volumes/"+volumeID, 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
|
|
}
|
|
var oneVolume ScalewayOneVolume
|
|
|
|
if err = json.Unmarshal(body, &oneVolume); err != nil {
|
|
return nil, err
|
|
}
|
|
// FIXME region, arch, owner, title
|
|
s.Cache.InsertVolume(oneVolume.Volume.Identifier, "", "", oneVolume.Volume.Organization, oneVolume.Volume.Name)
|
|
return &oneVolume.Volume, nil
|
|
}
|
|
|
|
// GetBootscripts gets the list of bootscripts from the ScalewayAPI
|
|
func (s *ScalewayAPI) GetBootscripts() (*[]ScalewayBootscript, error) {
|
|
query := url.Values{}
|
|
|
|
s.Cache.ClearBootscripts()
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, "bootscripts", query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := s.handleHTTPError([]int{http.StatusOK}, resp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var bootscripts ScalewayBootscripts
|
|
|
|
if err = json.Unmarshal(body, &bootscripts); err != nil {
|
|
return nil, err
|
|
}
|
|
for _, bootscript := range bootscripts.Bootscripts {
|
|
// FIXME region, arch, owner, title
|
|
s.Cache.InsertBootscript(bootscript.Identifier, "", bootscript.Arch, bootscript.Organization, bootscript.Title)
|
|
}
|
|
return &bootscripts.Bootscripts, nil
|
|
}
|
|
|
|
// GetBootscript gets a bootscript from the ScalewayAPI
|
|
func (s *ScalewayAPI) GetBootscript(bootscriptID string) (*ScalewayBootscript, error) {
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, "bootscripts/"+bootscriptID, 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
|
|
}
|
|
var oneBootscript ScalewayOneBootscript
|
|
|
|
if err = json.Unmarshal(body, &oneBootscript); err != nil {
|
|
return nil, err
|
|
}
|
|
// FIXME region, arch, owner, title
|
|
s.Cache.InsertBootscript(oneBootscript.Bootscript.Identifier, "", oneBootscript.Bootscript.Arch, oneBootscript.Bootscript.Organization, oneBootscript.Bootscript.Title)
|
|
return &oneBootscript.Bootscript, nil
|
|
}
|
|
|
|
// GetUserdatas gets list of userdata for a server
|
|
func (s *ScalewayAPI) GetUserdatas(serverID string, metadata bool) (*ScalewayUserdatas, error) {
|
|
var uri, endpoint string
|
|
|
|
endpoint = s.computeAPI
|
|
if metadata {
|
|
uri = "/user_data"
|
|
endpoint = MetadataAPI
|
|
} else {
|
|
uri = fmt.Sprintf("servers/%s/user_data", serverID)
|
|
}
|
|
|
|
resp, err := s.GetResponsePaginate(endpoint, uri, 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
|
|
}
|
|
var userdatas ScalewayUserdatas
|
|
|
|
if err = json.Unmarshal(body, &userdatas); err != nil {
|
|
return nil, err
|
|
}
|
|
return &userdatas, nil
|
|
}
|
|
|
|
func (s *ScalewayUserdata) String() string {
|
|
return string(*s)
|
|
}
|
|
|
|
// GetUserdata gets a specific userdata for a server
|
|
func (s *ScalewayAPI) GetUserdata(serverID, key string, metadata bool) (*ScalewayUserdata, error) {
|
|
var uri, endpoint string
|
|
|
|
endpoint = s.computeAPI
|
|
if metadata {
|
|
uri = fmt.Sprintf("/user_data/%s", key)
|
|
endpoint = MetadataAPI
|
|
} else {
|
|
uri = fmt.Sprintf("servers/%s/user_data/%s", serverID, key)
|
|
}
|
|
|
|
var err error
|
|
resp, err := s.GetResponsePaginate(endpoint, uri, url.Values{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("no such user_data %q (%d)", key, resp.StatusCode)
|
|
}
|
|
var data ScalewayUserdata
|
|
data, err = ioutil.ReadAll(resp.Body)
|
|
return &data, err
|
|
}
|
|
|
|
// PatchUserdata sets a user data
|
|
func (s *ScalewayAPI) PatchUserdata(serverID, key string, value []byte, metadata bool) error {
|
|
var resource, endpoint string
|
|
|
|
endpoint = s.computeAPI
|
|
if metadata {
|
|
resource = fmt.Sprintf("/user_data/%s", key)
|
|
endpoint = MetadataAPI
|
|
} else {
|
|
resource = fmt.Sprintf("servers/%s/user_data/%s", serverID, key)
|
|
}
|
|
|
|
uri := fmt.Sprintf("%s/%s", strings.TrimRight(endpoint, "/"), resource)
|
|
payload := new(bytes.Buffer)
|
|
payload.Write(value)
|
|
|
|
req, err := http.NewRequest("PATCH", uri, payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
req.Header.Set("X-Auth-Token", s.Token)
|
|
req.Header.Set("Content-Type", "text/plain")
|
|
req.Header.Set("User-Agent", s.userAgent)
|
|
|
|
s.LogHTTP(req)
|
|
|
|
resp, err := s.client.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusNoContent {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("cannot set user_data (%d)", resp.StatusCode)
|
|
}
|
|
|
|
// DeleteUserdata deletes a server user_data
|
|
func (s *ScalewayAPI) DeleteUserdata(serverID, key string, metadata bool) error {
|
|
var url, endpoint string
|
|
|
|
endpoint = s.computeAPI
|
|
if metadata {
|
|
url = fmt.Sprintf("/user_data/%s", key)
|
|
endpoint = MetadataAPI
|
|
} else {
|
|
url = fmt.Sprintf("servers/%s/user_data/%s", serverID, key)
|
|
}
|
|
|
|
resp, err := s.DeleteResponse(endpoint, url)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
_, err = s.handleHTTPError([]int{http.StatusNoContent}, resp)
|
|
return err
|
|
}
|
|
|
|
// GetTasks get the list of tasks from the ScalewayAPI
|
|
func (s *ScalewayAPI) GetTasks() (*[]ScalewayTask, error) {
|
|
query := url.Values{}
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, "tasks", query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := s.handleHTTPError([]int{http.StatusOK}, resp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var tasks ScalewayTasks
|
|
|
|
if err = json.Unmarshal(body, &tasks); err != nil {
|
|
return nil, err
|
|
}
|
|
return &tasks.Tasks, nil
|
|
}
|
|
|
|
// CheckCredentials performs a dummy check to ensure we can contact the API
|
|
func (s *ScalewayAPI) CheckCredentials() error {
|
|
query := url.Values{}
|
|
|
|
resp, err := s.GetResponsePaginate(AccountAPI, "tokens", query)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
body, err := s.handleHTTPError([]int{http.StatusOK}, resp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
found := false
|
|
var tokens ScalewayGetTokens
|
|
|
|
if err = json.Unmarshal(body, &tokens); err != nil {
|
|
return err
|
|
}
|
|
for _, token := range tokens.Tokens {
|
|
if token.ID == s.Token {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return fmt.Errorf("Invalid token %v", s.Token)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetUserID returns the userID
|
|
func (s *ScalewayAPI) GetUserID() (string, error) {
|
|
resp, err := s.GetResponsePaginate(AccountAPI, fmt.Sprintf("tokens/%s", s.Token), url.Values{})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := s.handleHTTPError([]int{http.StatusOK}, resp)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
var token ScalewayTokensDefinition
|
|
|
|
if err = json.Unmarshal(body, &token); err != nil {
|
|
return "", err
|
|
}
|
|
return token.Token.UserID, nil
|
|
}
|
|
|
|
// GetOrganization returns Organization
|
|
func (s *ScalewayAPI) GetOrganization() (*ScalewayOrganizationsDefinition, error) {
|
|
resp, err := s.GetResponsePaginate(AccountAPI, "organizations", 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
|
|
}
|
|
var data ScalewayOrganizationsDefinition
|
|
|
|
if err = json.Unmarshal(body, &data); err != nil {
|
|
return nil, err
|
|
}
|
|
return &data, nil
|
|
}
|
|
|
|
// GetUser returns the user
|
|
func (s *ScalewayAPI) GetUser() (*ScalewayUserDefinition, error) {
|
|
userID, err := s.GetUserID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := s.GetResponsePaginate(AccountAPI, fmt.Sprintf("users/%s", userID), 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
|
|
}
|
|
var user ScalewayUsersDefinition
|
|
|
|
if err = json.Unmarshal(body, &user); err != nil {
|
|
return nil, err
|
|
}
|
|
return &user.User, nil
|
|
}
|
|
|
|
// GetPermissions returns the permissions
|
|
func (s *ScalewayAPI) GetPermissions() (*ScalewayPermissionDefinition, error) {
|
|
resp, err := s.GetResponsePaginate(AccountAPI, fmt.Sprintf("tokens/%s/permissions", s.Token), 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
|
|
}
|
|
var permissions ScalewayPermissionDefinition
|
|
|
|
if err = json.Unmarshal(body, &permissions); err != nil {
|
|
return nil, err
|
|
}
|
|
return &permissions, nil
|
|
}
|
|
|
|
// GetDashboard returns the dashboard
|
|
func (s *ScalewayAPI) GetDashboard() (*ScalewayDashboard, error) {
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, "dashboard", 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
|
|
}
|
|
var dashboard ScalewayDashboardResp
|
|
|
|
if err = json.Unmarshal(body, &dashboard); err != nil {
|
|
return nil, err
|
|
}
|
|
return &dashboard.Dashboard, nil
|
|
}
|
|
|
|
// GetServerID returns exactly one server matching
|
|
func (s *ScalewayAPI) GetServerID(needle string) (string, error) {
|
|
// Parses optional type prefix, i.e: "server:name" -> "name"
|
|
_, needle = parseNeedle(needle)
|
|
|
|
servers, err := s.ResolveServer(needle)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Unable to resolve server %s: %s", needle, err)
|
|
}
|
|
if len(servers) == 1 {
|
|
return servers[0].Identifier, nil
|
|
}
|
|
if len(servers) == 0 {
|
|
return "", fmt.Errorf("No such server: %s", needle)
|
|
}
|
|
return "", showResolverResults(needle, servers)
|
|
}
|
|
|
|
func showResolverResults(needle string, results ScalewayResolverResults) error {
|
|
w := tabwriter.NewWriter(os.Stderr, 20, 1, 3, ' ', 0)
|
|
defer w.Flush()
|
|
sort.Sort(results)
|
|
fmt.Fprintf(w, " IMAGEID\tFROM\tNAME\tZONE\tARCH\n")
|
|
for _, result := range results {
|
|
if result.Arch == "" {
|
|
result.Arch = "n/a"
|
|
}
|
|
fmt.Fprintf(w, "- %s\t%s\t%s\t%s\t%s\n", result.TruncIdentifier(), result.CodeName(), result.Name, result.Region, result.Arch)
|
|
}
|
|
return fmt.Errorf("Too many candidates for %s (%d)", needle, len(results))
|
|
}
|
|
|
|
// GetVolumeID returns exactly one volume matching
|
|
func (s *ScalewayAPI) GetVolumeID(needle string) (string, error) {
|
|
// Parses optional type prefix, i.e: "volume:name" -> "name"
|
|
_, needle = parseNeedle(needle)
|
|
|
|
volumes, err := s.ResolveVolume(needle)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Unable to resolve volume %s: %s", needle, err)
|
|
}
|
|
if len(volumes) == 1 {
|
|
return volumes[0].Identifier, nil
|
|
}
|
|
if len(volumes) == 0 {
|
|
return "", fmt.Errorf("No such volume: %s", needle)
|
|
}
|
|
return "", showResolverResults(needle, volumes)
|
|
}
|
|
|
|
// GetSnapshotID returns exactly one snapshot matching
|
|
func (s *ScalewayAPI) GetSnapshotID(needle string) (string, error) {
|
|
// Parses optional type prefix, i.e: "snapshot:name" -> "name"
|
|
_, needle = parseNeedle(needle)
|
|
|
|
snapshots, err := s.ResolveSnapshot(needle)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Unable to resolve snapshot %s: %s", needle, err)
|
|
}
|
|
if len(snapshots) == 1 {
|
|
return snapshots[0].Identifier, nil
|
|
}
|
|
if len(snapshots) == 0 {
|
|
return "", fmt.Errorf("No such snapshot: %s", needle)
|
|
}
|
|
return "", showResolverResults(needle, snapshots)
|
|
}
|
|
|
|
// FilterImagesByArch removes entry that doesn't match with architecture
|
|
func FilterImagesByArch(res ScalewayResolverResults, arch string) (ret ScalewayResolverResults) {
|
|
if arch == "*" {
|
|
return res
|
|
}
|
|
for _, result := range res {
|
|
if result.Arch == arch {
|
|
ret = append(ret, result)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// FilterImagesByRegion removes entry that doesn't match with region
|
|
func FilterImagesByRegion(res ScalewayResolverResults, region string) (ret ScalewayResolverResults) {
|
|
if region == "*" {
|
|
return res
|
|
}
|
|
for _, result := range res {
|
|
if result.Region == region {
|
|
ret = append(ret, result)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// GetImageID returns exactly one image matching
|
|
func (s *ScalewayAPI) GetImageID(needle, arch string) (*ScalewayImageIdentifier, error) {
|
|
// Parses optional type prefix, i.e: "image:name" -> "name"
|
|
_, needle = parseNeedle(needle)
|
|
|
|
images, err := s.ResolveImage(needle)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Unable to resolve image %s: %s", needle, err)
|
|
}
|
|
images = FilterImagesByArch(images, arch)
|
|
images = FilterImagesByRegion(images, s.Region)
|
|
if len(images) == 1 {
|
|
return &ScalewayImageIdentifier{
|
|
Identifier: images[0].Identifier,
|
|
Arch: images[0].Arch,
|
|
// FIXME region, owner hardcoded
|
|
Region: images[0].Region,
|
|
Owner: "",
|
|
}, nil
|
|
}
|
|
if len(images) == 0 {
|
|
return nil, fmt.Errorf("No such image (zone %s, arch %s) : %s", s.Region, arch, needle)
|
|
}
|
|
return nil, showResolverResults(needle, images)
|
|
}
|
|
|
|
// GetSecurityGroups returns a ScalewaySecurityGroups
|
|
func (s *ScalewayAPI) GetSecurityGroups() (*ScalewayGetSecurityGroups, error) {
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, "security_groups", 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
|
|
}
|
|
var securityGroups ScalewayGetSecurityGroups
|
|
|
|
if err = json.Unmarshal(body, &securityGroups); err != nil {
|
|
return nil, err
|
|
}
|
|
return &securityGroups, nil
|
|
}
|
|
|
|
// GetSecurityGroupRules returns a ScalewaySecurityGroupRules
|
|
func (s *ScalewayAPI) GetSecurityGroupRules(groupID string) (*ScalewayGetSecurityGroupRules, error) {
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, fmt.Sprintf("security_groups/%s/rules", groupID), 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
|
|
}
|
|
var securityGroupRules ScalewayGetSecurityGroupRules
|
|
|
|
if err = json.Unmarshal(body, &securityGroupRules); err != nil {
|
|
return nil, err
|
|
}
|
|
return &securityGroupRules, nil
|
|
}
|
|
|
|
// GetASecurityGroupRule returns a ScalewaySecurityGroupRule
|
|
func (s *ScalewayAPI) GetASecurityGroupRule(groupID string, rulesID string) (*ScalewayGetSecurityGroupRule, error) {
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, fmt.Sprintf("security_groups/%s/rules/%s", groupID, rulesID), 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
|
|
}
|
|
var securityGroupRules ScalewayGetSecurityGroupRule
|
|
|
|
if err = json.Unmarshal(body, &securityGroupRules); err != nil {
|
|
return nil, err
|
|
}
|
|
return &securityGroupRules, nil
|
|
}
|
|
|
|
// GetASecurityGroup returns a ScalewaySecurityGroup
|
|
func (s *ScalewayAPI) GetASecurityGroup(groupsID string) (*ScalewayGetSecurityGroup, error) {
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, fmt.Sprintf("security_groups/%s", groupsID), 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
|
|
}
|
|
var securityGroups ScalewayGetSecurityGroup
|
|
|
|
if err = json.Unmarshal(body, &securityGroups); err != nil {
|
|
return nil, err
|
|
}
|
|
return &securityGroups, nil
|
|
}
|
|
|
|
// PostSecurityGroup posts a group on a server
|
|
func (s *ScalewayAPI) PostSecurityGroup(group ScalewayNewSecurityGroup) error {
|
|
resp, err := s.PostResponse(s.computeAPI, "security_groups", group)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
_, err = s.handleHTTPError([]int{http.StatusCreated}, resp)
|
|
return err
|
|
}
|
|
|
|
// PostSecurityGroupRule posts a rule on a server
|
|
func (s *ScalewayAPI) PostSecurityGroupRule(SecurityGroupID string, rules ScalewayNewSecurityGroupRule) error {
|
|
resp, err := s.PostResponse(s.computeAPI, fmt.Sprintf("security_groups/%s/rules", SecurityGroupID), rules)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
_, err = s.handleHTTPError([]int{http.StatusCreated}, resp)
|
|
return err
|
|
}
|
|
|
|
// DeleteSecurityGroup deletes a SecurityGroup
|
|
func (s *ScalewayAPI) DeleteSecurityGroup(securityGroupID string) error {
|
|
resp, err := s.DeleteResponse(s.computeAPI, fmt.Sprintf("security_groups/%s", securityGroupID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
_, err = s.handleHTTPError([]int{http.StatusNoContent}, resp)
|
|
return err
|
|
}
|
|
|
|
// PutSecurityGroup updates a SecurityGroup
|
|
func (s *ScalewayAPI) PutSecurityGroup(group ScalewayUpdateSecurityGroup, securityGroupID string) error {
|
|
resp, err := s.PutResponse(s.computeAPI, fmt.Sprintf("security_groups/%s", securityGroupID), group)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
_, err = s.handleHTTPError([]int{http.StatusOK}, resp)
|
|
return err
|
|
}
|
|
|
|
// PutSecurityGroupRule updates a SecurityGroupRule
|
|
func (s *ScalewayAPI) PutSecurityGroupRule(rules ScalewayNewSecurityGroupRule, securityGroupID, RuleID string) error {
|
|
resp, err := s.PutResponse(s.computeAPI, fmt.Sprintf("security_groups/%s/rules/%s", securityGroupID, RuleID), rules)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
_, err = s.handleHTTPError([]int{http.StatusOK}, resp)
|
|
return err
|
|
}
|
|
|
|
// DeleteSecurityGroupRule deletes a SecurityGroupRule
|
|
func (s *ScalewayAPI) DeleteSecurityGroupRule(SecurityGroupID, RuleID string) error {
|
|
resp, err := s.DeleteResponse(s.computeAPI, fmt.Sprintf("security_groups/%s/rules/%s", SecurityGroupID, RuleID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
_, err = s.handleHTTPError([]int{http.StatusNoContent}, resp)
|
|
return err
|
|
}
|
|
|
|
// GetContainers returns a ScalewayGetContainers
|
|
func (s *ScalewayAPI) GetContainers() (*ScalewayGetContainers, error) {
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, "containers", 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
|
|
}
|
|
var containers ScalewayGetContainers
|
|
|
|
if err = json.Unmarshal(body, &containers); err != nil {
|
|
return nil, err
|
|
}
|
|
return &containers, nil
|
|
}
|
|
|
|
// GetContainerDatas returns a ScalewayGetContainerDatas
|
|
func (s *ScalewayAPI) GetContainerDatas(container string) (*ScalewayGetContainerDatas, error) {
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, fmt.Sprintf("containers/%s", container), 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
|
|
}
|
|
var datas ScalewayGetContainerDatas
|
|
|
|
if err = json.Unmarshal(body, &datas); err != nil {
|
|
return nil, err
|
|
}
|
|
return &datas, nil
|
|
}
|
|
|
|
// GetIPS returns a ScalewayGetIPS
|
|
func (s *ScalewayAPI) GetIPS() (*ScalewayGetIPS, error) {
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, "ips", 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
|
|
}
|
|
var ips ScalewayGetIPS
|
|
|
|
if err = json.Unmarshal(body, &ips); err != nil {
|
|
return nil, err
|
|
}
|
|
return &ips, nil
|
|
}
|
|
|
|
// NewIP returns a new IP
|
|
func (s *ScalewayAPI) NewIP() (*ScalewayGetIP, error) {
|
|
var orga struct {
|
|
Organization string `json:"organization"`
|
|
}
|
|
orga.Organization = s.Organization
|
|
resp, err := s.PostResponse(s.computeAPI, "ips", orga)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := s.handleHTTPError([]int{http.StatusCreated}, resp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var ip ScalewayGetIP
|
|
|
|
if err = json.Unmarshal(body, &ip); err != nil {
|
|
return nil, err
|
|
}
|
|
return &ip, nil
|
|
}
|
|
|
|
// AttachIP attachs an IP to a server
|
|
func (s *ScalewayAPI) AttachIP(ipID, serverID string) error {
|
|
var update struct {
|
|
Address string `json:"address"`
|
|
ID string `json:"id"`
|
|
Reverse *string `json:"reverse"`
|
|
Organization string `json:"organization"`
|
|
Server string `json:"server"`
|
|
}
|
|
|
|
ip, err := s.GetIP(ipID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
update.Address = ip.IP.Address
|
|
update.ID = ip.IP.ID
|
|
update.Organization = ip.IP.Organization
|
|
update.Server = serverID
|
|
resp, err := s.PutResponse(s.computeAPI, fmt.Sprintf("ips/%s", ipID), update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = s.handleHTTPError([]int{http.StatusOK}, resp)
|
|
return err
|
|
}
|
|
|
|
// DetachIP detaches an IP from a server
|
|
func (s *ScalewayAPI) DetachIP(ipID string) error {
|
|
ip, err := s.GetIP(ipID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ip.IP.Server = nil
|
|
resp, err := s.PutResponse(s.computeAPI, fmt.Sprintf("ips/%s", ipID), ip.IP)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
_, err = s.handleHTTPError([]int{http.StatusOK}, resp)
|
|
return err
|
|
}
|
|
|
|
// DeleteIP deletes an IP
|
|
func (s *ScalewayAPI) DeleteIP(ipID string) error {
|
|
resp, err := s.DeleteResponse(s.computeAPI, fmt.Sprintf("ips/%s", ipID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
_, err = s.handleHTTPError([]int{http.StatusNoContent}, resp)
|
|
return err
|
|
}
|
|
|
|
// GetIP returns a ScalewayGetIP
|
|
func (s *ScalewayAPI) GetIP(ipID string) (*ScalewayGetIP, error) {
|
|
resp, err := s.GetResponsePaginate(s.computeAPI, fmt.Sprintf("ips/%s", ipID), 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
|
|
}
|
|
var ip ScalewayGetIP
|
|
|
|
if err = json.Unmarshal(body, &ip); err != nil {
|
|
return nil, err
|
|
}
|
|
return &ip, nil
|
|
}
|
|
|
|
// GetQuotas returns a ScalewayGetQuotas
|
|
func (s *ScalewayAPI) GetQuotas() (*ScalewayGetQuotas, error) {
|
|
resp, err := s.GetResponsePaginate(AccountAPI, fmt.Sprintf("organizations/%s/quotas", s.Organization), 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
|
|
}
|
|
var quotas ScalewayGetQuotas
|
|
|
|
if err = json.Unmarshal(body, "as); err != nil {
|
|
return nil, err
|
|
}
|
|
return "as, nil
|
|
}
|
|
|
|
// GetBootscriptID returns exactly one bootscript matching
|
|
func (s *ScalewayAPI) GetBootscriptID(needle, arch string) (string, error) {
|
|
// Parses optional type prefix, i.e: "bootscript:name" -> "name"
|
|
_, needle = parseNeedle(needle)
|
|
|
|
bootscripts, err := s.ResolveBootscript(needle)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Unable to resolve bootscript %s: %s", needle, err)
|
|
}
|
|
bootscripts.FilterByArch(arch)
|
|
if len(bootscripts) == 1 {
|
|
return bootscripts[0].Identifier, nil
|
|
}
|
|
if len(bootscripts) == 0 {
|
|
return "", fmt.Errorf("No such bootscript: %s", needle)
|
|
}
|
|
return "", showResolverResults(needle, bootscripts)
|
|
}
|
|
|
|
func rootNetDial(network, addr string) (net.Conn, error) {
|
|
dialer := net.Dialer{
|
|
Timeout: 10 * time.Second,
|
|
KeepAlive: 10 * time.Second,
|
|
}
|
|
|
|
// bruteforce privileged ports
|
|
var localAddr net.Addr
|
|
var err error
|
|
for port := 1; port <= 1024; port++ {
|
|
localAddr, err = net.ResolveTCPAddr("tcp", fmt.Sprintf(":%d", port))
|
|
|
|
// this should never happen
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dialer.LocalAddr = localAddr
|
|
|
|
conn, err := dialer.Dial(network, addr)
|
|
|
|
// if err is nil, dialer.Dial succeed, so let's go
|
|
// else, err != nil, but we don't care
|
|
if err == nil {
|
|
return conn, nil
|
|
}
|
|
}
|
|
// if here, all privileged ports were tried without success
|
|
return nil, fmt.Errorf("bind: permission denied, are you root ?")
|
|
}
|
|
|
|
// SetPassword register the password
|
|
func (s *ScalewayAPI) SetPassword(password string) {
|
|
s.password = password
|
|
}
|
|
|
|
// GetMarketPlaceImages returns images from marketplace
|
|
func (s *ScalewayAPI) GetMarketPlaceImages(uuidImage string) (*MarketImages, error) {
|
|
resp, err := s.GetResponsePaginate(MarketplaceAPI, fmt.Sprintf("images/%s", uuidImage), 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
|
|
}
|
|
var ret MarketImages
|
|
|
|
if uuidImage != "" {
|
|
ret.Images = make([]MarketImage, 1)
|
|
|
|
var img MarketImage
|
|
|
|
if err = json.Unmarshal(body, &img); err != nil {
|
|
return nil, err
|
|
}
|
|
ret.Images[0] = img
|
|
} else {
|
|
if err = json.Unmarshal(body, &ret); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return &ret, nil
|
|
}
|
|
|
|
// GetMarketPlaceImageVersions returns image version
|
|
func (s *ScalewayAPI) GetMarketPlaceImageVersions(uuidImage, uuidVersion string) (*MarketVersions, error) {
|
|
resp, err := s.GetResponsePaginate(MarketplaceAPI, fmt.Sprintf("images/%v/versions/%s", uuidImage, uuidVersion), 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
|
|
}
|
|
var ret MarketVersions
|
|
|
|
if uuidImage != "" {
|
|
var version MarketVersion
|
|
ret.Versions = make([]MarketVersionDefinition, 1)
|
|
|
|
if err = json.Unmarshal(body, &version); err != nil {
|
|
return nil, err
|
|
}
|
|
ret.Versions[0] = version.Version
|
|
} else {
|
|
if err = json.Unmarshal(body, &ret); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return &ret, nil
|
|
}
|
|
|
|
// GetMarketPlaceImageCurrentVersion return the image current version
|
|
func (s *ScalewayAPI) GetMarketPlaceImageCurrentVersion(uuidImage string) (*MarketVersion, error) {
|
|
resp, err := s.GetResponsePaginate(MarketplaceAPI, fmt.Sprintf("images/%v/versions/current", uuidImage), 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
|
|
}
|
|
var ret MarketVersion
|
|
|
|
if err = json.Unmarshal(body, &ret); err != nil {
|
|
return nil, err
|
|
}
|
|
return &ret, nil
|
|
}
|
|
|
|
// GetMarketPlaceLocalImages returns images from local region
|
|
func (s *ScalewayAPI) GetMarketPlaceLocalImages(uuidImage, uuidVersion, uuidLocalImage string) (*MarketLocalImages, error) {
|
|
resp, err := s.GetResponsePaginate(MarketplaceAPI, fmt.Sprintf("images/%v/versions/%s/local_images/%s", uuidImage, uuidVersion, uuidLocalImage), 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
|
|
}
|
|
var ret MarketLocalImages
|
|
if uuidLocalImage != "" {
|
|
var localImage MarketLocalImage
|
|
ret.LocalImages = make([]MarketLocalImageDefinition, 1)
|
|
|
|
if err = json.Unmarshal(body, &localImage); err != nil {
|
|
return nil, err
|
|
}
|
|
ret.LocalImages[0] = localImage.LocalImages
|
|
} else {
|
|
if err = json.Unmarshal(body, &ret); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return &ret, nil
|
|
}
|
|
|
|
// PostMarketPlaceImage adds new image
|
|
func (s *ScalewayAPI) PostMarketPlaceImage(images MarketImage) error {
|
|
resp, err := s.PostResponse(MarketplaceAPI, "images/", images)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
_, err = s.handleHTTPError([]int{http.StatusAccepted}, resp)
|
|
return err
|
|
}
|
|
|
|
// PostMarketPlaceImageVersion adds new image version
|
|
func (s *ScalewayAPI) PostMarketPlaceImageVersion(uuidImage string, version MarketVersion) error {
|
|
resp, err := s.PostResponse(MarketplaceAPI, fmt.Sprintf("images/%v/versions", uuidImage), version)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
_, err = s.handleHTTPError([]int{http.StatusAccepted}, resp)
|
|
return err
|
|
}
|
|
|
|
// PostMarketPlaceLocalImage adds new local image
|
|
func (s *ScalewayAPI) PostMarketPlaceLocalImage(uuidImage, uuidVersion, uuidLocalImage string, local MarketLocalImage) error {
|
|
resp, err := s.PostResponse(MarketplaceAPI, fmt.Sprintf("images/%v/versions/%s/local_images/%v", uuidImage, uuidVersion, uuidLocalImage), local)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
_, err = s.handleHTTPError([]int{http.StatusAccepted}, resp)
|
|
return err
|
|
}
|
|
|
|
// PutMarketPlaceImage updates image
|
|
func (s *ScalewayAPI) PutMarketPlaceImage(uudiImage string, images MarketImage) error {
|
|
resp, err := s.PutResponse(MarketplaceAPI, fmt.Sprintf("images/%v", uudiImage), images)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
_, err = s.handleHTTPError([]int{http.StatusOK}, resp)
|
|
return err
|
|
}
|
|
|
|
// PutMarketPlaceImageVersion updates image version
|
|
func (s *ScalewayAPI) PutMarketPlaceImageVersion(uuidImage, uuidVersion string, version MarketVersion) error {
|
|
resp, err := s.PutResponse(MarketplaceAPI, fmt.Sprintf("images/%v/versions/%v", uuidImage, uuidVersion), version)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
_, err = s.handleHTTPError([]int{http.StatusOK}, resp)
|
|
return err
|
|
}
|
|
|
|
// PutMarketPlaceLocalImage updates local image
|
|
func (s *ScalewayAPI) PutMarketPlaceLocalImage(uuidImage, uuidVersion, uuidLocalImage string, local MarketLocalImage) error {
|
|
resp, err := s.PostResponse(MarketplaceAPI, fmt.Sprintf("images/%v/versions/%s/local_images/%v", uuidImage, uuidVersion, uuidLocalImage), local)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
_, err = s.handleHTTPError([]int{http.StatusOK}, resp)
|
|
return err
|
|
}
|
|
|
|
// DeleteMarketPlaceImage deletes image
|
|
func (s *ScalewayAPI) DeleteMarketPlaceImage(uudImage string) error {
|
|
resp, err := s.DeleteResponse(MarketplaceAPI, fmt.Sprintf("images/%v", uudImage))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
_, err = s.handleHTTPError([]int{http.StatusNoContent}, resp)
|
|
return err
|
|
}
|
|
|
|
// DeleteMarketPlaceImageVersion delete image version
|
|
func (s *ScalewayAPI) DeleteMarketPlaceImageVersion(uuidImage, uuidVersion string) error {
|
|
resp, err := s.DeleteResponse(MarketplaceAPI, fmt.Sprintf("images/%v/versions/%v", uuidImage, uuidVersion))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
_, err = s.handleHTTPError([]int{http.StatusNoContent}, resp)
|
|
return err
|
|
}
|
|
|
|
// DeleteMarketPlaceLocalImage deletes local image
|
|
func (s *ScalewayAPI) DeleteMarketPlaceLocalImage(uuidImage, uuidVersion, uuidLocalImage string) error {
|
|
resp, err := s.DeleteResponse(MarketplaceAPI, fmt.Sprintf("images/%v/versions/%s/local_images/%v", uuidImage, uuidVersion, uuidLocalImage))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
_, err = s.handleHTTPError([]int{http.StatusNoContent}, resp)
|
|
return err
|
|
}
|
|
|
|
// ResolveTTYUrl return an URL to get a tty
|
|
func (s *ScalewayAPI) ResolveTTYUrl() string {
|
|
switch s.Region {
|
|
case "par1", "":
|
|
return "https://tty-par1.scaleway.com/v2/"
|
|
case "ams1":
|
|
return "https://tty-ams1.scaleway.com"
|
|
}
|
|
return ""
|
|
}
|