Update scaleway-sdk-go to v1.0.0-beta.6

Signed-off-by: Karol Woźniak <wozniakk@gmail.com>
This commit is contained in:
Karol Woźniak
2020-05-01 01:15:24 +02:00
parent 0b488d805e
commit c750f54cb0
44 changed files with 5559 additions and 2340 deletions

View File

@@ -18,8 +18,6 @@ import (
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/api/marketplace/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/scaleway-sdk-go/scwconfig"
"github.com/scaleway/scaleway-sdk-go/utils"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/terminal"
@@ -29,7 +27,9 @@ var (
defaultScalewayCommercialType = "DEV1-S"
defaultScalewayImageName = "Ubuntu Bionic"
defaultScalewayImageArch = "x86_64"
defaultVolumeSize = uint64(10000000000)
defaultVolumeSize = scw.GB * 10
scalewayDynamicIPRequired = true
scalewayBootType = instance.BootTypeLocal
)
// ScalewayClient contains state required for communication with Scaleway as well as the instance
@@ -43,23 +43,28 @@ type ScalewayClient struct {
}
// NewScalewayClient creates a new scaleway client
func NewScalewayClient(secretKey, zone, projectID string) (*ScalewayClient, error) {
func NewScalewayClient(secretKey, zone, organizationID string) (*ScalewayClient, error) {
var scwClient *scw.Client
log.Debugf("Connecting to Scaleway")
if secretKey == "" {
config, err := scwconfig.Load()
config, err := scw.LoadConfig()
if err != nil {
return nil, err
}
profile, err := config.GetActiveProfile()
if err != nil {
return nil, err
}
scwClient, err = scw.NewClient(
scw.WithConfig(config),
scw.WithProfile(profile),
scw.WithEnv(),
)
if err != nil {
return nil, err
}
} else {
scwZone, err := utils.ParseZone(zone)
scwZone, err := scw.ParseZone(zone)
if err != nil {
return nil, err
}
@@ -67,7 +72,7 @@ func NewScalewayClient(secretKey, zone, projectID string) (*ScalewayClient, erro
scwClient, err = scw.NewClient(
scw.WithAuth("", secretKey),
scw.WithDefaultZone(scwZone),
scw.WithDefaultProjectID(projectID),
scw.WithDefaultOrganizationID(organizationID),
)
if err != nil {
return nil, err
@@ -140,10 +145,10 @@ func (s *ScalewayClient) CreateInstance() (string, error) {
createServerRequest := &instance.CreateServerRequest{
Name: "linuxkit-builder",
CommercialType: defaultScalewayCommercialType,
DynamicIPRequired: true,
DynamicIPRequired: &scalewayDynamicIPRequired,
Image: imageID,
EnableIPv6: false,
BootType: instance.ServerBootTypeLocal,
BootType: &scalewayBootType,
Volumes: volumeMap,
}
@@ -548,10 +553,10 @@ func (s *ScalewayClient) CreateLinuxkitInstance(instanceName, imageName, instanc
log.Debugf("Creating server %s on Scaleway", instanceName)
serverResp, err := s.instanceAPI.CreateServer(&instance.CreateServerRequest{
Name: instanceName,
DynamicIPRequired: true,
DynamicIPRequired: &scalewayDynamicIPRequired,
CommercialType: instanceType,
Image: imageID,
BootType: instance.ServerBootTypeLocal,
BootType: &scalewayBootType,
})
if err != nil {
return "", err

View File

@@ -67,7 +67,7 @@ github.com/prometheus/procfs abf152e5f3e97f2fafac028d2cc06c1feb87ffa5
github.com/radu-matei/azure-sdk-for-go 3b12823551999669c9a325a32472508e0af7978e
github.com/radu-matei/azure-vhd-utils e52754d5569d2a643a7775f72ff2a6cf524f4c25
github.com/rn/iso9660wrap baf8d62ad3155152b488d5ff9d4f2b9bb0d6986a
github.com/scaleway/scaleway-sdk-go 20b731586975c078d9c2d7dd0002127e9e9cdef2
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6
github.com/sirupsen/logrus v1.0.3
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.1

View File

@@ -1,4 +1,4 @@
<p align="center"><img height="125" src="docs/static_files/scaleway-logo.png" /></p>
<p align="center"><img width="50%" src="docs/static_files/sdk-artwork.png" /></p>
<p align="center">
<a href="https://godoc.org/github.com/scaleway/scaleway-sdk-go"><img src="https://godoc.org/github.com/scaleway/scaleway-sdk-go?status.svg" alt="GoDoc"/></a>
@@ -12,8 +12,7 @@
**:warning: This is an early release, keep in mind that the API can break**
Scaleway is a simple way to build, deploy and scale your infrastructure in the cloud.
We help thousands of developers and businesses to run their infrastructures without any issue.
Scaleway is a single way to create, deploy and scale your infrastructure in the cloud. We help thousands of businesses to run their infrastructures easily.
## Documentation
@@ -44,7 +43,7 @@ func main() {
// Create a Scaleway client
client, err := scw.NewClient(
// Get your credentials at https://console.scaleway.com/account/credentials
scw.WithDefaultProjectID("ORGANISATION_ID"),
scw.WithDefaultOrganizationID("ORGANISATION_ID"),
scw.WithAuth("ACCESS_KEY", "SECRET_KEY"),
)
if err != nil {
@@ -56,7 +55,7 @@ func main() {
// Call the ListServers method on the Instance SDK
response, err := instanceApi.ListServers(&instance.ListServersRequest{
Zone: utils.ZoneFrPar1,
Zone: scw.ZoneFrPar1,
})
if err != nil {
panic(err)
@@ -70,6 +69,10 @@ func main() {
}
```
## Examples
You can find additional examples in the [GoDoc](https://godoc.org/github.com/scaleway/scaleway-sdk-go).
## Development
This repository is at its early stage and is still in active development.

View File

@@ -117,8 +117,8 @@ type Metadata struct {
}
}
// ListUserdata returns the metadata available from the server
func (*MetadataAPI) ListUserdata() (res *Userdata, err error) {
// ListUserData returns the metadata available from the server
func (*MetadataAPI) ListUserData() (res *UserData, err error) {
retries := 0
for retries <= metadataRetryBindPort {
port := rand.Intn(1024)
@@ -144,20 +144,20 @@ func (*MetadataAPI) ListUserdata() (res *Userdata, err error) {
}
defer resp.Body.Close()
userdata := &Userdata{}
userdata := &UserData{}
err = json.NewDecoder(resp.Body).Decode(userdata)
if err != nil {
return nil, errors.Wrap(err, "error decoding userdata")
}
return userdata, nil
}
return nil, errors.New("too many bind port retries for ListUserdata")
return nil, errors.New("too many bind port retries for ListUserData")
}
// GetUserdata returns the value for the given metadata key
func (*MetadataAPI) GetUserdata(key string) ([]byte, error) {
// GetUserData returns the value for the given metadata key
func (*MetadataAPI) GetUserData(key string) ([]byte, error) {
if key == "" {
return make([]byte, 0), errors.New("key must not be empty in GetUserdata")
return make([]byte, 0), errors.New("key must not be empty in GetUserData")
}
retries := 0
@@ -192,13 +192,13 @@ func (*MetadataAPI) GetUserdata(key string) ([]byte, error) {
return body, nil
}
return make([]byte, 0), errors.New("too may bind port retries for GetUserdata")
return make([]byte, 0), errors.New("too may bind port retries for GetUserData")
}
// SetUserdata sets the userdata key with the given value
func (*MetadataAPI) SetUserdata(key string, value []byte) error {
// SetUserData sets the userdata key with the given value
func (*MetadataAPI) SetUserData(key string, value []byte) error {
if key == "" {
return errors.New("key must not be empty in SetUserdata")
return errors.New("key must not be empty in SetUserData")
}
retries := 0
@@ -231,13 +231,13 @@ func (*MetadataAPI) SetUserdata(key string, value []byte) error {
return nil
}
return errors.New("too may bind port retries for SetUserdata")
return errors.New("too may bind port retries for SetUserData")
}
// DeleteUserdata deletes the userdata key and the associated value
func (*MetadataAPI) DeleteUserdata(key string) error {
// DeleteUserData deletes the userdata key and the associated value
func (*MetadataAPI) DeleteUserData(key string) error {
if key == "" {
return errors.New("key must not be empty in DeleteUserdata")
return errors.New("key must not be empty in DeleteUserData")
}
retries := 0
@@ -269,10 +269,10 @@ func (*MetadataAPI) DeleteUserdata(key string) error {
return nil
}
return errors.New("too may bind port retries for DeleteUserdata")
return errors.New("too may bind port retries for DeleteUserData")
}
// Userdata represents the user data
type Userdata struct {
// UserData represents the user data
type UserData struct {
UserData []string `json:"user_data,omitempty"`
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +1,55 @@
package instance
import (
"encoding/json"
"fmt"
"sync"
"github.com/scaleway/scaleway-sdk-go/internal/errors"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/scaleway-sdk-go/utils"
)
var (
resourceLock sync.Map
)
// lockResource locks a resource from a specific resourceID
func lockResource(resourceID string) *sync.Mutex {
v, _ := resourceLock.LoadOrStore(resourceID, &sync.Mutex{})
mutex := v.(*sync.Mutex)
mutex.Lock()
return mutex
}
// lockServer locks a server from its zone and its ID
func lockServer(zone scw.Zone, serverID string) *sync.Mutex {
return lockResource(fmt.Sprint("server", zone, serverID))
}
// AttachIPRequest contains the parameters to attach an IP to a server
//
// Deprecated: UpdateIPRequest should be used instead
type AttachIPRequest struct {
Zone utils.Zone `json:"-"`
IPID string `json:"-"`
ServerID string `json:"server_id"`
Zone scw.Zone `json:"-"`
IP string `json:"-"`
ServerID string `json:"server_id"`
}
// AttachIPResponse contains the updated IP after attaching
//
// Deprecated: UpdateIPResponse should be used instead
type AttachIPResponse struct {
IP *IP
}
// AttachIP attaches an IP to a server.
//
// Deprecated: UpdateIP() should be used instead
func (s *API) AttachIP(req *AttachIPRequest, opts ...scw.RequestOption) (*AttachIPResponse, error) {
var ptrServerID = &req.ServerID
ipResponse, err := s.updateIP(&updateIPRequest{
ipResponse, err := s.UpdateIP(&UpdateIPRequest{
Zone: req.Zone,
IPID: req.IPID,
Server: &ptrServerID,
IP: req.IP,
Server: &NullableStringValue{Value: req.ServerID},
})
if err != nil {
return nil, err
@@ -35,23 +59,28 @@ func (s *API) AttachIP(req *AttachIPRequest, opts ...scw.RequestOption) (*Attach
}
// DetachIPRequest contains the parameters to detach an IP from a server
//
// Deprecated: UpdateIPRequest should be used instead
type DetachIPRequest struct {
Zone utils.Zone `json:"-"`
IPID string `json:"-"`
Zone scw.Zone `json:"-"`
IP string `json:"-"`
}
// DetachIPResponse contains the updated IP after detaching
//
// Deprecated: UpdateIPResponse should be used instead
type DetachIPResponse struct {
IP *IP
}
// DetachIP detaches an IP from a server.
//
// Deprecated: UpdateIP() should be used instead
func (s *API) DetachIP(req *DetachIPRequest, opts ...scw.RequestOption) (*DetachIPResponse, error) {
var ptrServerID *string
ipResponse, err := s.updateIP(&updateIPRequest{
ipResponse, err := s.UpdateIP(&UpdateIPRequest{
Zone: req.Zone,
IPID: req.IPID,
Server: &ptrServerID,
IP: req.IP,
Server: &NullableStringValue{Null: true},
})
if err != nil {
return nil, err
@@ -60,40 +89,11 @@ func (s *API) DetachIP(req *DetachIPRequest, opts ...scw.RequestOption) (*Detach
return &DetachIPResponse{IP: ipResponse.IP}, nil
}
// UpdateIPRequest contains the parameters to update an IP
// if Reverse is an empty string, the reverse will be removed
type UpdateIPRequest struct {
Zone utils.Zone `json:"-"`
IPID string `json:"-"`
Reverse *string `json:"reverse"`
}
// UpdateIP updates an IP
func (s *API) UpdateIP(req *UpdateIPRequest, opts ...scw.RequestOption) (*UpdateIPResponse, error) {
var reverse **string
if req.Reverse != nil {
if *req.Reverse == "" {
req.Reverse = nil
}
reverse = &req.Reverse
}
ipResponse, err := s.updateIP(&updateIPRequest{
Zone: req.Zone,
IPID: req.IPID,
Reverse: reverse,
})
if err != nil {
return nil, err
}
return &UpdateIPResponse{IP: ipResponse.IP}, nil
}
// AttachVolumeRequest contains the parameters to attach a volume to a server
type AttachVolumeRequest struct {
Zone utils.Zone `json:"-"`
ServerID string `json:"-"`
VolumeID string `json:"-"`
Zone scw.Zone `json:"-"`
ServerID string `json:"-"`
VolumeID string `json:"-"`
}
// AttachVolumeResponse contains the updated server after attaching a volume
@@ -112,7 +112,10 @@ func volumesToVolumeTemplates(volumes map[string]*Volume) map[string]*VolumeTemp
}
// AttachVolume attaches a volume to a server
//
// Note: Implementation is thread-safe.
func (s *API) AttachVolume(req *AttachVolumeRequest, opts ...scw.RequestOption) (*AttachVolumeResponse, error) {
defer lockServer(req.Zone, req.ServerID).Unlock()
// get server with volumes
getServerResponse, err := s.GetServer(&GetServerRequest{
Zone: req.Zone,
@@ -126,15 +129,29 @@ func (s *API) AttachVolume(req *AttachVolumeRequest, opts ...scw.RequestOption)
newVolumes := volumesToVolumeTemplates(volumes)
// add volume to volumes list
key := fmt.Sprintf("%d", len(volumes))
newVolumes[key] = &VolumeTemplate{
ID: req.VolumeID,
// name is ignored on this PATCH
Name: req.VolumeID,
// We loop through all the possible volume keys (0 to len(volumes))
// to find a non existing key and assign it to the requested volume.
// A key should always be found. However we return an error if no keys were found.
found := false
for i := 0; i <= len(volumes); i++ {
key := fmt.Sprintf("%d", i)
if _, ok := newVolumes[key]; !ok {
newVolumes[key] = &VolumeTemplate{
ID: req.VolumeID,
// name is ignored on this PATCH
Name: req.VolumeID,
}
found = true
break
}
}
if !found {
return nil, fmt.Errorf("could not find key to attach volume %s", req.VolumeID)
}
// update server
updateServerResponse, err := s.UpdateServer(&UpdateServerRequest{
updateServerResponse, err := s.updateServer(&UpdateServerRequest{
Zone: req.Zone,
ServerID: req.ServerID,
Volumes: &newVolumes,
@@ -148,8 +165,8 @@ func (s *API) AttachVolume(req *AttachVolumeRequest, opts ...scw.RequestOption)
// DetachVolumeRequest contains the parameters to detach a volume from a server
type DetachVolumeRequest struct {
Zone utils.Zone `json:"-"`
VolumeID string `json:"-"`
Zone scw.Zone `json:"-"`
VolumeID string `json:"-"`
}
// DetachVolumeResponse contains the updated server after detaching a volume
@@ -158,6 +175,8 @@ type DetachVolumeResponse struct {
}
// DetachVolume detaches a volume from a server
//
// Note: Implementation is thread-safe.
func (s *API) DetachVolume(req *DetachVolumeRequest, opts ...scw.RequestOption) (*DetachVolumeResponse, error) {
// get volume
getVolumeResponse, err := s.GetVolume(&GetVolumeRequest{
@@ -168,13 +187,14 @@ func (s *API) DetachVolume(req *DetachVolumeRequest, opts ...scw.RequestOption)
return nil, err
}
if getVolumeResponse.Volume == nil {
return nil, fmt.Errorf("expected volume to have value in response")
return nil, errors.New("expected volume to have value in response")
}
if getVolumeResponse.Volume.Server == nil {
return nil, fmt.Errorf("server should be attached to a server")
return nil, errors.New("volume should be attached to a server")
}
serverID := getVolumeResponse.Volume.Server.ID
defer lockServer(req.Zone, serverID).Unlock()
// get server with volumes
getServerResponse, err := s.GetServer(&GetServerRequest{
Zone: req.Zone,
@@ -194,7 +214,7 @@ func (s *API) DetachVolume(req *DetachVolumeRequest, opts ...scw.RequestOption)
newVolumes := volumesToVolumeTemplates(volumes)
// update server
updateServerResponse, err := s.UpdateServer(&UpdateServerRequest{
updateServerResponse, err := s.updateServer(&UpdateServerRequest{
Zone: req.Zone,
ServerID: serverID,
Volumes: &newVolumes,
@@ -220,7 +240,7 @@ func (r *ListBootscriptsResponse) UnsafeSetTotalCount(totalCount int) {
// UnsafeSetTotalCount should not be used
// Internal usage only
func (r *ListIpsResponse) UnsafeSetTotalCount(totalCount int) {
func (r *ListIPsResponse) UnsafeSetTotalCount(totalCount int) {
r.TotalCount = uint32(totalCount)
}
@@ -253,3 +273,57 @@ func (r *ListSnapshotsResponse) UnsafeSetTotalCount(totalCount int) {
func (r *ListVolumesResponse) UnsafeSetTotalCount(totalCount int) {
r.TotalCount = uint32(totalCount)
}
// UnsafeSetTotalCount should not be used
// Internal usage only
func (r *ListImagesResponse) UnsafeSetTotalCount(totalCount int) {
r.TotalCount = uint32(totalCount)
}
// UnsafeGetTotalCount should not be used
// Internal usage only
func (r *ListServersTypesResponse) UnsafeGetTotalCount() uint32 {
return r.TotalCount
}
// UnsafeAppend should not be used
// Internal usage only
func (r *ListServersTypesResponse) UnsafeAppend(res interface{}) (uint32, error) {
results, ok := res.(*ListServersTypesResponse)
if !ok {
return 0, errors.New("%T type cannot be appended to type %T", res, r)
}
if r.Servers == nil {
r.Servers = make(map[string]*ServerType, len(results.Servers))
}
for name, serverType := range results.Servers {
r.Servers[name] = serverType
}
r.TotalCount += uint32(len(results.Servers))
return uint32(len(results.Servers)), nil
}
func (v *NullableStringValue) UnmarshalJSON(b []byte) error {
if string(b) == "null" {
v.Null = true
return nil
}
var tmp string
if err := json.Unmarshal(b, &tmp); err != nil {
return err
}
v.Null = false
v.Value = tmp
return nil
}
func (v *NullableStringValue) MarshalJSON() ([]byte, error) {
if v.Null {
return []byte("null"), nil
}
return json.Marshal(v.Value)
}

View File

@@ -0,0 +1,208 @@
package instance
import (
"fmt"
"github.com/scaleway/scaleway-sdk-go/internal/errors"
"github.com/scaleway/scaleway-sdk-go/scw"
)
// UpdateSecurityGroupRequest contains the parameters to update a security group
type UpdateSecurityGroupRequest struct {
Zone scw.Zone `json:"-"`
SecurityGroupID string `json:"-"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
InboundDefaultPolicy *SecurityGroupPolicy `json:"inbound_default_policy,omitempty"`
OutboundDefaultPolicy *SecurityGroupPolicy `json:"outbound_default_policy,omitempty"`
Stateful *bool `json:"stateful,omitempty"`
OrganizationDefault *bool `json:"organization_default,omitempty"`
}
type UpdateSecurityGroupResponse struct {
SecurityGroup *SecurityGroup
}
// UpdateSecurityGroup updates a security group.
func (s *API) UpdateSecurityGroup(req *UpdateSecurityGroupRequest, opts ...scw.RequestOption) (*UpdateSecurityGroupResponse, error) {
var err error
if req.Zone == "" {
defaultZone, _ := s.client.GetDefaultZone()
req.Zone = defaultZone
}
if fmt.Sprint(req.Zone) == "" {
return nil, errors.New("field Zone cannot be empty in request")
}
if fmt.Sprint(req.SecurityGroupID) == "" {
return nil, errors.New("field SecurityGroupID cannot be empty in request")
}
getSGResponse, err := s.GetSecurityGroup(&GetSecurityGroupRequest{
Zone: req.Zone,
SecurityGroupID: req.SecurityGroupID,
}, opts...)
if err != nil {
return nil, err
}
setRequest := &setSecurityGroupRequest{
ID: getSGResponse.SecurityGroup.ID,
Name: getSGResponse.SecurityGroup.Name,
Description: getSGResponse.SecurityGroup.Description,
Organization: getSGResponse.SecurityGroup.Organization,
OrganizationDefault: getSGResponse.SecurityGroup.OrganizationDefault,
OutboundDefaultPolicy: getSGResponse.SecurityGroup.OutboundDefaultPolicy,
InboundDefaultPolicy: getSGResponse.SecurityGroup.InboundDefaultPolicy,
Stateful: getSGResponse.SecurityGroup.Stateful,
Zone: req.Zone,
EnableDefaultSecurity: getSGResponse.SecurityGroup.EnableDefaultSecurity,
CreationDate: getSGResponse.SecurityGroup.CreationDate,
ModificationDate: getSGResponse.SecurityGroup.ModificationDate,
Servers: getSGResponse.SecurityGroup.Servers,
}
// Override the values that need to be updated
if req.Name != nil {
setRequest.Name = *req.Name
}
if req.Description != nil {
setRequest.Description = *req.Description
}
if req.InboundDefaultPolicy != nil {
setRequest.InboundDefaultPolicy = *req.InboundDefaultPolicy
}
if req.OutboundDefaultPolicy != nil {
setRequest.OutboundDefaultPolicy = *req.OutboundDefaultPolicy
}
if req.Stateful != nil {
setRequest.Stateful = *req.Stateful
}
if req.OrganizationDefault != nil {
setRequest.OrganizationDefault = *req.OrganizationDefault
}
setRes, err := s.setSecurityGroup(setRequest, opts...)
if err != nil {
return nil, err
}
return &UpdateSecurityGroupResponse{
SecurityGroup: setRes.SecurityGroup,
}, nil
}
// UpdateSecurityGroupRuleRequest contains the parameters to update a security group rule
type UpdateSecurityGroupRuleRequest struct {
Zone scw.Zone `json:"-"`
SecurityGroupID string `json:"-"`
SecurityGroupRuleID string `json:"-"`
Protocol *SecurityGroupRuleProtocol `json:"protocol"`
Direction *SecurityGroupRuleDirection `json:"direction"`
Action *SecurityGroupRuleAction `json:"action"`
IPRange *scw.IPNet `json:"ip_range"`
Position *uint32 `json:"position"`
// If set to 0, DestPortFrom will be removed.
// See SecurityGroupRule.DestPortFrom for more information
DestPortFrom *uint32 `json:"dest_port_from"`
// If set to 0, DestPortTo will be removed.
// See SecurityGroupRule.DestPortTo for more information
DestPortTo *uint32 `json:"dest_port_to"`
}
type UpdateSecurityGroupRuleResponse struct {
Rule *SecurityGroupRule `json:"security_rule"`
}
// UpdateSecurityGroupRule updates a security group.
func (s *API) UpdateSecurityGroupRule(req *UpdateSecurityGroupRuleRequest, opts ...scw.RequestOption) (*UpdateSecurityGroupRuleResponse, error) {
var err error
if fmt.Sprint(req.Zone) == "" {
defaultZone, _ := s.client.GetDefaultZone()
req.Zone = defaultZone
}
if fmt.Sprint(req.Zone) == "" {
return nil, errors.New("field Zone cannot be empty in request")
}
res, err := s.GetSecurityGroupRule(&GetSecurityGroupRuleRequest{
SecurityGroupRuleID: req.SecurityGroupRuleID,
SecurityGroupID: req.SecurityGroupID,
Zone: req.Zone,
})
if err != nil {
return nil, err
}
setRequest := &setSecurityGroupRuleRequest{
Zone: req.Zone,
SecurityGroupID: req.SecurityGroupID,
SecurityGroupRuleID: req.SecurityGroupRuleID,
ID: req.SecurityGroupRuleID,
Direction: res.Rule.Direction,
Protocol: res.Rule.Protocol,
DestPortFrom: res.Rule.DestPortFrom,
DestPortTo: res.Rule.DestPortTo,
IPRange: res.Rule.IPRange,
Action: res.Rule.Action,
Position: res.Rule.Position,
Editable: res.Rule.Editable,
}
// Override the values that need to be updated
if req.Action != nil {
setRequest.Action = *req.Action
}
if req.IPRange != nil {
setRequest.IPRange = *req.IPRange
}
if req.DestPortTo != nil {
if *req.DestPortTo > 0 {
setRequest.DestPortTo = req.DestPortTo
} else {
setRequest.DestPortTo = nil
}
}
if req.DestPortFrom != nil {
if *req.DestPortFrom > 0 {
setRequest.DestPortFrom = req.DestPortFrom
} else {
setRequest.DestPortFrom = nil
}
}
if req.DestPortFrom != nil && req.DestPortTo != nil && *req.DestPortFrom == *req.DestPortTo {
setRequest.DestPortTo = nil
}
if req.Protocol != nil {
setRequest.Protocol = *req.Protocol
}
if req.Direction != nil {
setRequest.Direction = *req.Direction
}
if req.Position != nil {
setRequest.Position = *req.Position
}
// When we use ICMP protocol portFrom and portTo should be set to nil
if req.Protocol != nil && *req.Protocol == SecurityGroupRuleProtocolICMP {
setRequest.DestPortFrom = nil
setRequest.DestPortTo = nil
}
resp, err := s.setSecurityGroupRule(setRequest)
if err != nil {
return nil, err
}
return &UpdateSecurityGroupRuleResponse{
Rule: resp.Rule,
}, nil
}

View File

@@ -1,29 +1,65 @@
package instance
import (
"bytes"
"fmt"
"io"
"net/http"
"time"
"github.com/scaleway/scaleway-sdk-go/api/marketplace/v1"
"github.com/scaleway/scaleway-sdk-go/internal/async"
"github.com/scaleway/scaleway-sdk-go/utils"
"github.com/scaleway/scaleway-sdk-go/internal/errors"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/scaleway-sdk-go/validation"
)
// WaitForServerRequest is used by WaitForServer method
const (
defaultTimeout = 5 * time.Minute
defaultRetryInterval = 5 * time.Second
)
var (
RetryInterval = defaultRetryInterval
)
// CreateServer creates a server.
func (s *API) CreateServer(req *CreateServerRequest, opts ...scw.RequestOption) (*CreateServerResponse, error) {
// If image is not a UUID we try to fetch it from marketplace.
if req.Image != "" && !validation.IsUUID(req.Image) {
apiMarketplace := marketplace.NewAPI(s.client)
imageID, err := apiMarketplace.GetLocalImageIDByLabel(&marketplace.GetLocalImageIDByLabelRequest{
ImageLabel: req.Image,
Zone: req.Zone,
CommercialType: req.CommercialType,
})
if err != nil {
return nil, err
}
req.Image = imageID
}
return s.createServer(req, opts...)
}
// UpdateServer updates a server.
//
// Note: Implementation is thread-safe.
func (s *API) UpdateServer(req *UpdateServerRequest, opts ...scw.RequestOption) (*UpdateServerResponse, error) {
defer lockServer(req.Zone, req.ServerID).Unlock()
return s.updateServer(req, opts...)
}
// WaitForServerRequest is used by WaitForServer method.
type WaitForServerRequest struct {
ServerID string
Zone utils.Zone
// Timeout: maximum time to wait before (default: 5 minutes)
Timeout time.Duration
Zone scw.Zone
Timeout time.Duration
}
// WaitForServer wait for the server to be in a "terminal state" before returning.
// This function can be used to wait for a server to be started for example.
func (s *API) WaitForServer(req *WaitForServerRequest) (*Server, error) {
if req.Timeout == 0 {
req.Timeout = 5 * time.Minute
}
terminalStatus := map[ServerState]struct{}{
ServerStateStopped: {},
ServerStateStoppedInPlace: {},
@@ -32,22 +68,329 @@ func (s *API) WaitForServer(req *WaitForServerRequest) (*Server, error) {
}
server, err := async.WaitSync(&async.WaitSyncConfig{
Get: func() (interface{}, error, bool) {
Get: func() (interface{}, bool, error) {
res, err := s.GetServer(&GetServerRequest{
ServerID: req.ServerID,
Zone: req.Zone,
})
if err != nil {
return nil, err, false
return nil, false, err
}
_, isTerminal := terminalStatus[res.Server.State]
return res.Server, err, isTerminal
return res.Server, isTerminal, err
},
Timeout: req.Timeout,
IntervalStrategy: async.LinearIntervalStrategy(5 * time.Second),
IntervalStrategy: async.LinearIntervalStrategy(RetryInterval),
})
return server.(*Server), err
if err != nil {
return nil, errors.Wrap(err, "waiting for server failed")
}
return server.(*Server), nil
}
// ServerActionAndWaitRequest is used by ServerActionAndWait method.
type ServerActionAndWaitRequest struct {
ServerID string
Zone scw.Zone
Action ServerAction
// Timeout: maximum time to wait before (default: 5 minutes)
Timeout time.Duration
}
// ServerActionAndWait start an action and wait for the server to be in the correct "terminal state"
// expected by this action.
func (s *API) ServerActionAndWait(req *ServerActionAndWaitRequest) error {
if req.Timeout == 0 {
req.Timeout = defaultTimeout
}
_, err := s.ServerAction(&ServerActionRequest{
Zone: req.Zone,
ServerID: req.ServerID,
Action: req.Action,
})
if err != nil {
return err
}
finalServer, err := s.WaitForServer(&WaitForServerRequest{
Zone: req.Zone,
ServerID: req.ServerID,
Timeout: req.Timeout,
})
if err != nil {
return err
}
// check the action was properly executed
expectedState := ServerState("unknown")
switch req.Action {
case ServerActionPoweron, ServerActionReboot:
expectedState = ServerStateRunning
case ServerActionPoweroff:
expectedState = ServerStateStopped
case ServerActionStopInPlace:
expectedState = ServerStateStoppedInPlace
}
// backup can be performed from any state
if expectedState != ServerState("unknown") && finalServer.State != expectedState {
return errors.New("expected state %s but found %s: %s", expectedState, finalServer.State, finalServer.StateDetail)
}
return nil
}
// GetServerTypeRequest is used by GetServerType.
type GetServerTypeRequest struct {
Zone scw.Zone
Name string
}
// GetServerType get server type info by it's name.
func (s *API) GetServerType(req *GetServerTypeRequest) (*ServerType, error) {
res, err := s.ListServersTypes(&ListServersTypesRequest{
Zone: req.Zone,
}, scw.WithAllPages())
if err != nil {
return nil, err
}
if serverType, exist := res.Servers[req.Name]; exist {
return serverType, nil
}
return nil, errors.New("could not find server type %q", req.Name)
}
// GetServerUserDataRequest is used by GetServerUserData method.
type GetServerUserDataRequest struct {
Zone scw.Zone `json:"-"`
ServerID string `json:"-"`
// Key defines the user data key to get.
Key string `json:"-"`
}
// GetServerUserData gets the content of a user data on a server for the given key.
func (s *API) GetServerUserData(req *GetServerUserDataRequest, opts ...scw.RequestOption) (io.Reader, error) {
var err error
if req.Zone == "" {
defaultZone, _ := s.client.GetDefaultZone()
req.Zone = defaultZone
}
if fmt.Sprint(req.Zone) == "" {
return nil, errors.New("field Zone cannot be empty in request")
}
if fmt.Sprint(req.ServerID) == "" {
return nil, errors.New("field ServerID cannot be empty in request")
}
if fmt.Sprint(req.Key) == "" {
return nil, errors.New("field Key cannot be empty in request")
}
scwReq := &scw.ScalewayRequest{
Method: "GET",
Path: "/instance/v1/zones/" + fmt.Sprint(req.Zone) + "/servers/" + fmt.Sprint(req.ServerID) + "/user_data/" + fmt.Sprint(req.Key),
Headers: http.Header{},
}
res := &bytes.Buffer{}
err = s.client.Do(scwReq, res, opts...)
if err != nil {
return nil, err
}
return res, nil
}
// SetServerUserDataRequest is used by SetServerUserData method.
type SetServerUserDataRequest struct {
Zone scw.Zone `json:"-"`
ServerID string `json:"-"`
// Key defines the user data key to set.
Key string `json:"-"`
// Content defines the data to set.
Content io.Reader
}
// SetServerUserData sets the content of a user data on a server for the given key.
func (s *API) SetServerUserData(req *SetServerUserDataRequest, opts ...scw.RequestOption) error {
var err error
if req.Zone == "" {
defaultZone, _ := s.client.GetDefaultZone()
req.Zone = defaultZone
}
if fmt.Sprint(req.Zone) == "" {
return errors.New("field Zone cannot be empty in request")
}
if fmt.Sprint(req.ServerID) == "" {
return errors.New("field ServerID cannot be empty in request")
}
if fmt.Sprint(req.Key) == "" {
return errors.New("field Key cannot be empty in request")
}
if req.Content == nil {
return errors.New("field Content cannot be nil in request")
}
scwReq := &scw.ScalewayRequest{
Method: "PATCH",
Path: "/instance/v1/zones/" + fmt.Sprint(req.Zone) + "/servers/" + fmt.Sprint(req.ServerID) + "/user_data/" + fmt.Sprint(req.Key),
Headers: http.Header{},
}
err = scwReq.SetBody(req.Content)
if err != nil {
return err
}
err = s.client.Do(scwReq, nil, opts...)
if err != nil {
return err
}
return nil
}
// GetAllServerUserDataRequest is used by GetAllServerUserData method.
type GetAllServerUserDataRequest struct {
Zone scw.Zone `json:"-"`
ServerID string `json:"-"`
}
// GetAllServerUserDataResponse is used by GetAllServerUserData method.
type GetAllServerUserDataResponse struct {
UserData map[string]io.Reader `json:"-"`
}
// GetAllServerUserData gets all user data on a server.
func (s *API) GetAllServerUserData(req *GetAllServerUserDataRequest, opts ...scw.RequestOption) (*GetAllServerUserDataResponse, error) {
var err error
if req.Zone == "" {
defaultZone, _ := s.client.GetDefaultZone()
req.Zone = defaultZone
}
if fmt.Sprint(req.Zone) == "" {
return nil, errors.New("field Zone cannot be empty in request")
}
if fmt.Sprint(req.ServerID) == "" {
return nil, errors.New("field ServerID cannot be empty in request")
}
// get all user data keys
allUserDataRes, err := s.ListServerUserData(&ListServerUserDataRequest{
Zone: req.Zone,
ServerID: req.ServerID,
})
if err != nil {
return nil, err
}
res := &GetAllServerUserDataResponse{
UserData: make(map[string]io.Reader, len(allUserDataRes.UserData)),
}
// build a map with all user data
for _, key := range allUserDataRes.UserData {
value, err := s.GetServerUserData(&GetServerUserDataRequest{
Zone: req.Zone,
ServerID: req.ServerID,
Key: key,
})
if err != nil {
return nil, err
}
res.UserData[key] = value
}
return res, nil
}
// SetAllServerUserDataRequest is used by SetAllServerUserData method.
type SetAllServerUserDataRequest struct {
Zone scw.Zone `json:"-"`
ServerID string `json:"-"`
// UserData defines all user data that will be set to the server.
// This map is idempotent, it means that all the current data will be overwritten and
// all keys not present in this map will be deleted.. All data will be removed if this map is nil.
UserData map[string]io.Reader `json:"-"`
}
// SetAllServerUserData sets all user data on a server.
func (s *API) SetAllServerUserData(req *SetAllServerUserDataRequest, opts ...scw.RequestOption) error {
var err error
if req.Zone == "" {
defaultZone, _ := s.client.GetDefaultZone()
req.Zone = defaultZone
}
if fmt.Sprint(req.Zone) == "" {
return errors.New("field Zone cannot be empty in request")
}
if fmt.Sprint(req.ServerID) == "" {
return errors.New("field ServerID cannot be empty in request")
}
// get all current user data keys
allUserDataRes, err := s.ListServerUserData(&ListServerUserDataRequest{
Zone: req.Zone,
ServerID: req.ServerID,
})
if err != nil {
return err
}
// delete all current user data
for _, key := range allUserDataRes.UserData {
_, exist := req.UserData[key]
if exist {
continue
}
err := s.DeleteServerUserData(&DeleteServerUserDataRequest{
Zone: req.Zone,
ServerID: req.ServerID,
Key: key,
})
if err != nil {
return err
}
}
// set all new user data
for key, value := range req.UserData {
err := s.SetServerUserData(&SetServerUserDataRequest{
Zone: req.Zone,
ServerID: req.ServerID,
Key: key,
Content: value,
})
if err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,113 @@
package instance
import (
"fmt"
"net/http"
"time"
"github.com/scaleway/scaleway-sdk-go/internal/errors"
"github.com/scaleway/scaleway-sdk-go/scw"
)
// UpdateVolumeRequest contains the parameters to update on a volume
type UpdateVolumeRequest struct {
Zone scw.Zone `json:"-"`
// VolumeID is the volumes unique ID
VolumeID string `json:"-"`
// Name display the volumes names
Name *string `json:"name,omitempty"`
}
// UpdateVolumeResponse contains the updated volume.
type UpdateVolumeResponse struct {
Volume *Volume `json:"volume,omitempty"`
}
// setVolumeRequest contains all the params to PUT volumes
type setVolumeRequest struct {
Zone scw.Zone `json:"-"`
// ID display the volumes unique ID
ID string `json:"id"`
// Name display the volumes names
Name string `json:"name"`
// ExportURI show the volumes NBD export URI
ExportURI string `json:"export_uri"`
// Size display the volumes disk size
Size scw.Size `json:"size"`
// VolumeType display the volumes type
//
// Default value: l_ssd
VolumeType VolumeType `json:"volume_type"`
// CreationDate display the volumes creation date
CreationDate time.Time `json:"creation_date"`
// ModificationDate display the volumes modification date
ModificationDate time.Time `json:"modification_date"`
// Organization display the volumes organization
Organization string `json:"organization"`
// Server display information about the server attached to the volume
Server *ServerSummary `json:"server"`
}
// UpdateVolume updates the set fields on the volume.
func (s *API) UpdateVolume(req *UpdateVolumeRequest, opts ...scw.RequestOption) (*UpdateVolumeResponse, error) {
var err error
if req.Zone == "" {
defaultZone, _ := s.client.GetDefaultZone()
req.Zone = defaultZone
}
if fmt.Sprint(req.Zone) == "" {
return nil, errors.New("field Zone cannot be empty in request")
}
if fmt.Sprint(req.VolumeID) == "" {
return nil, errors.New("field VolumeID cannot be empty in request")
}
getVolumeResponse, err := s.GetVolume(&GetVolumeRequest{
Zone: req.Zone,
VolumeID: req.VolumeID,
})
if err != nil {
return nil, err
}
setVolumeRequest := &setVolumeRequest{
Zone: req.Zone,
ID: getVolumeResponse.Volume.ID,
Name: getVolumeResponse.Volume.Name,
ExportURI: getVolumeResponse.Volume.ExportURI,
Size: getVolumeResponse.Volume.Size,
VolumeType: getVolumeResponse.Volume.VolumeType,
CreationDate: getVolumeResponse.Volume.CreationDate,
ModificationDate: getVolumeResponse.Volume.ModificationDate,
Organization: getVolumeResponse.Volume.Organization,
Server: getVolumeResponse.Volume.Server,
}
// Override the values that need to be updated
if req.Name != nil {
setVolumeRequest.Name = *req.Name
}
scwReq := &scw.ScalewayRequest{
Method: "PUT",
Path: "/instance/v1/zones/" + fmt.Sprint(req.Zone) + "/volumes/" + fmt.Sprint(req.VolumeID) + "",
Headers: http.Header{},
}
err = scwReq.SetBody(setVolumeRequest)
if err != nil {
return nil, err
}
var res UpdateVolumeResponse
err = s.client.Do(scwReq, &res, opts...)
if err != nil {
return nil, err
}
return &res, nil
}

View File

@@ -1,6 +1,7 @@
// This file was automatically generated. DO NOT EDIT.
// If you have any remark or suggestion do not hesitate to open an issue.
// Package marketplace provides methods and message types of the marketplace v1 API.
package marketplace
import (
@@ -15,8 +16,8 @@ import (
"github.com/scaleway/scaleway-sdk-go/internal/errors"
"github.com/scaleway/scaleway-sdk-go/internal/marshaler"
"github.com/scaleway/scaleway-sdk-go/internal/parameter"
"github.com/scaleway/scaleway-sdk-go/namegenerator"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/scaleway-sdk-go/utils"
)
// always import dependencies
@@ -31,8 +32,9 @@ var (
_ scw.ScalewayRequest
_ marshaler.Duration
_ utils.File
_ scw.File
_ = parameter.AddToQuery
_ = namegenerator.GetRandomName
)
// API marketplace API
@@ -48,79 +50,91 @@ func NewAPI(client *scw.Client) *API {
}
type GetImageResponse struct {
Image *Image `json:"image,omitempty"`
Image *Image `json:"image"`
}
type GetServiceInfoResponse struct {
API string `json:"api,omitempty"`
API string `json:"api"`
Description string `json:"description,omitempty"`
Description string `json:"description"`
Version string `json:"version,omitempty"`
Version string `json:"version"`
}
type GetVersionResponse struct {
Version *Version `json:"version,omitempty"`
Version *Version `json:"version"`
}
// Image image
type Image struct {
ID string `json:"id,omitempty"`
// ID uUID of this image
ID string `json:"id"`
// Name name of the image
Name string `json:"name"`
// Description text description of this image
Description string `json:"description"`
// Logo uRL of this image's logo
Logo string `json:"logo"`
// Categories list of categories this image belongs to
Categories []string `json:"categories"`
// CreationDate creation date of this image
CreationDate time.Time `json:"creation_date"`
// ModificationDate date of the last modification of this image
ModificationDate time.Time `json:"modification_date"`
// ValidUntil expiration date of this image
ValidUntil time.Time `json:"valid_until"`
// Label label of this image
Label string `json:"label"`
// Versions list of versions of this image
Versions []*Version `json:"versions"`
// Organization organization this image belongs to
Organization *Organization `json:"organization"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Logo string `json:"logo,omitempty"`
Categories []string `json:"categories,omitempty"`
Organization *Organization `json:"organization,omitempty"`
ValidUntil time.Time `json:"valid_until,omitempty"`
CreationDate time.Time `json:"creation_date,omitempty"`
ModificationDate time.Time `json:"modification_date,omitempty"`
Versions []*Version `json:"versions,omitempty"`
CurrentPublicVersion string `json:"current_public_version,omitempty"`
CurrentPublicVersion string `json:"current_public_version"`
}
type ListImagesResponse struct {
Images []*Image `json:"images,omitempty"`
Images []*Image `json:"images"`
TotalCount uint32 `json:"total_count"`
}
type ListVersionsResponse struct {
Versions []*Version `json:"versions,omitempty"`
Versions []*Version `json:"versions"`
TotalCount uint32 `json:"total_count"`
}
// LocalImage local image
type LocalImage struct {
ID string `json:"id,omitempty"`
Arch string `json:"arch,omitempty"`
Zone utils.Zone `json:"zone,omitempty"`
CompatibleCommercialTypes []string `json:"compatible_commercial_types,omitempty"`
// ID uUID of this local image
ID string `json:"id"`
// CompatibleCommercialTypes list of all commercial types that are compatible with this local image
CompatibleCommercialTypes []string `json:"compatible_commercial_types"`
// Arch supported architecture for this local image
Arch string `json:"arch"`
// Zone availability Zone where this local image is available
Zone scw.Zone `json:"zone"`
}
type Organization struct {
ID string `json:"id,omitempty"`
ID string `json:"id"`
Name string `json:"name,omitempty"`
Name string `json:"name"`
}
// Version version
type Version struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
CreationDate time.Time `json:"creation_date,omitempty"`
ModificationDate time.Time `json:"modification_date,omitempty"`
LocalImages []*LocalImage `json:"local_images,omitempty"`
// ID uUID of this version
ID string `json:"id"`
// Name name of this version
Name string `json:"name"`
// CreationDate creation date of this image version
CreationDate time.Time `json:"creation_date"`
// ModificationDate date of the last modification of this version
ModificationDate time.Time `json:"modification_date"`
// LocalImages list of local images available in this version
LocalImages []*LocalImage `json:"local_images"`
}
// Service API
@@ -147,11 +161,13 @@ func (s *API) GetServiceInfo(req *GetServiceInfoRequest, opts ...scw.RequestOpti
}
type ListImagesRequest struct {
PerPage *int32 `json:"-"`
// PerPage a positive integer lower or equal to 100 to select the number of items to display
PerPage *uint32 `json:"-"`
// Page a positive integer to choose the page to display
Page *int32 `json:"-"`
}
// ListImages list marketplace images
func (s *API) ListImages(req *ListImagesRequest, opts ...scw.RequestOption) (*ListImagesResponse, error) {
var err error
@@ -180,10 +196,31 @@ func (s *API) ListImages(req *ListImagesRequest, opts ...scw.RequestOption) (*Li
return &resp, nil
}
// UnsafeGetTotalCount should not be used
// Internal usage only
func (r *ListImagesResponse) UnsafeGetTotalCount() uint32 {
return r.TotalCount
}
// UnsafeAppend should not be used
// Internal usage only
func (r *ListImagesResponse) UnsafeAppend(res interface{}) (uint32, error) {
results, ok := res.(*ListImagesResponse)
if !ok {
return 0, errors.New("%T type cannot be appended to type %T", res, r)
}
r.Images = append(r.Images, results.Images...)
r.TotalCount += uint32(len(results.Images))
return uint32(len(results.Images)), nil
}
type GetImageRequest struct {
// ImageID display the image name
ImageID string `json:"-"`
}
// GetImage get a specific marketplace image
func (s *API) GetImage(req *GetImageRequest, opts ...scw.RequestOption) (*GetImageResponse, error) {
var err error

View File

@@ -2,16 +2,16 @@ package marketplace
import (
"fmt"
"strings"
"github.com/scaleway/scaleway-sdk-go/utils"
"github.com/scaleway/scaleway-sdk-go/internal/errors"
"github.com/scaleway/scaleway-sdk-go/scw"
)
// getLocalImage returns the correct local version of an image matching
// the current zone and the compatible commercial type
func (version *Version) getLocalImage(zone utils.Zone, commercialType string) (*LocalImage, error) {
func (version *Version) getLocalImage(zone scw.Zone, commercialType string) (*LocalImage, error) {
for _, localImage := range version.LocalImages {
// Check if in correct zone
if localImage.Zone != zone {
continue
@@ -26,56 +26,73 @@ func (version *Version) getLocalImage(zone utils.Zone, commercialType string) (*
}
return nil, fmt.Errorf("couldn't find compatible local image for this image version (%s)", version.ID)
}
// getLatestVersion returns the current/latests version on an image,
// or an error in case the image doesn't have a public version.
func (image *Image) getLatestVersion() (*Version, error) {
for _, version := range image.Versions {
if version.ID == image.CurrentPublicVersion {
return version, nil
}
}
return nil, fmt.Errorf("latest version could not be found for image %s", image.Name)
return nil, errors.New("latest version could not be found for image %s", image.Label)
}
// FindLocalImageIDByName search for an image with the given name (exact match) in the given region
// GetLocalImageIDByLabelRequest is used by GetLocalImageIDByLabel
type GetLocalImageIDByLabelRequest struct {
ImageLabel string
Zone scw.Zone
CommercialType string
}
// GetLocalImageIDByLabel search for an image with the given label (exact match) in the given region
// it returns the latest version of this specific image.
func (s *API) FindLocalImageIDByName(imageName string, zone utils.Zone, commercialType string) (string, error) {
func (s *API) GetLocalImageIDByLabel(req *GetLocalImageIDByLabelRequest) (string, error) {
if req.Zone == "" {
defaultZone, _ := s.client.GetDefaultZone()
req.Zone = defaultZone
}
listImageRequest := &ListImagesRequest{}
listImageResponse, err := s.ListImages(listImageRequest)
listImageResponse, err := s.ListImages(listImageRequest, scw.WithAllPages())
if err != nil {
return "", err
}
// TODO: handle pagination
images := listImageResponse.Images
_ = images
label := strings.Replace(req.ImageLabel, "-", "_", -1)
commercialType := strings.ToUpper(req.CommercialType)
for _, image := range images {
// Match name of the image
if image.Name == imageName {
// Match label of the image
if label == image.Label {
latestVersion, err := image.getLatestVersion()
if err != nil {
return "", fmt.Errorf("couldn't find a matching image for the given name (%s), zone (%s) and commercial type (%s): %s", imageName, zone, commercialType, err)
return "", errors.Wrap(err, "couldn't find a matching image for the given label (%s), zone (%s) and commercial type (%s)", req.ImageLabel, req.Zone, req.CommercialType)
}
localImage, err := latestVersion.getLocalImage(zone, commercialType)
localImage, err := latestVersion.getLocalImage(req.Zone, commercialType)
if err != nil {
return "", fmt.Errorf("couldn't find a matching image for the given name (%s), zone (%s) and commercial type (%s): %s", imageName, zone, commercialType, err)
return "", errors.Wrap(err, "couldn't find a matching image for the given label (%s), zone (%s) and commercial type (%s)", req.ImageLabel, req.Zone, req.CommercialType)
}
return localImage.ID, nil
}
}
return "", fmt.Errorf("couldn't find a matching image for the given name (%s), zone (%s) and commercial type (%s)", imageName, zone, commercialType)
return "", errors.New("couldn't find a matching image for the given label (%s), zone (%s) and commercial type (%s)", req.ImageLabel, req.Zone, req.CommercialType)
}
// UnsafeSetTotalCount should not be used
// Internal usage only
func (r *ListImagesResponse) UnsafeSetTotalCount(totalCount int) {
r.TotalCount = uint32(totalCount)
}
// UnsafeSetTotalCount should not be used
// Internal usage only
func (r *ListVersionsResponse) UnsafeSetTotalCount(totalCount int) {
r.TotalCount = uint32(totalCount)
}

View File

@@ -15,7 +15,7 @@ type IntervalStrategy func() <-chan time.Time
// WaitSyncConfig defines the waiting options.
type WaitSyncConfig struct {
// This method will be called from another goroutine.
Get func() (value interface{}, err error, isTerminal bool)
Get func() (value interface{}, isTerminal bool, err error)
IntervalStrategy IntervalStrategy
Timeout time.Duration
}
@@ -43,6 +43,7 @@ func WaitSync(config *WaitSyncConfig) (terminalValue interface{}, err error) {
if config.IntervalStrategy == nil {
config.IntervalStrategy = LinearIntervalStrategy(defaultInterval)
}
if config.Timeout == 0 {
config.Timeout = defaultTimeout
}
@@ -54,7 +55,7 @@ func WaitSync(config *WaitSyncConfig) (terminalValue interface{}, err error) {
go func() {
for {
// get the payload
value, err, stopCondition := config.Get()
value, stopCondition, err := config.Get()
// send the payload
if err != nil {

View File

@@ -2,18 +2,18 @@ package auth
import "net/http"
type noAuth struct {
type NoAuth struct {
}
// NewNoAuth return an auth with no authentication method
func NewNoAuth() *noAuth {
return &noAuth{}
func NewNoAuth() *NoAuth {
return &NoAuth{}
}
func (t *noAuth) Headers() http.Header {
func (t *NoAuth) Headers() http.Header {
return http.Header{}
}
func (t *noAuth) AnonymizedHeaders() http.Header {
func (t *NoAuth) AnonymizedHeaders() http.Header {
return http.Header{}
}

View File

@@ -2,41 +2,44 @@ package auth
import "net/http"
type token struct {
accessKey string
secretKey string
// Token is the pair accessKey + secretKey.
// This type is public because it's an internal package.
type Token struct {
AccessKey string
SecretKey string
}
// XAuthTokenHeader is Scaleway standard auth header
const XAuthTokenHeader = "X-Auth-Token"
const XAuthTokenHeader = "X-Auth-Token" // #nosec G101
// NewToken create a token authentication from an
// access key and a secret key
func NewToken(accessKey, secretKey string) *token {
return &token{accessKey: accessKey, secretKey: secretKey}
func NewToken(accessKey, secretKey string) *Token {
return &Token{AccessKey: accessKey, SecretKey: secretKey}
}
// Headers returns headers that must be add to the http request
func (t *token) Headers() http.Header {
func (t *Token) Headers() http.Header {
headers := http.Header{}
headers.Set(XAuthTokenHeader, t.secretKey)
headers.Set(XAuthTokenHeader, t.SecretKey)
return headers
}
// AnonymizedHeaders returns an anonymized version of Headers()
// This method could be use for logging purpose.
func (t *token) AnonymizedHeaders() http.Header {
func (t *Token) AnonymizedHeaders() http.Header {
headers := http.Header{}
var secret string
switch {
case len(t.secretKey) == 0:
secret = ""
case len(t.secretKey) > 8:
secret = t.secretKey[0:8] + "-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
default:
secret = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
headers.Set(XAuthTokenHeader, secret)
headers.Set(XAuthTokenHeader, HideSecretKey(t.SecretKey))
return headers
}
func HideSecretKey(k string) string {
switch {
case len(k) == 0:
return ""
case len(k) > 8:
return k[0:8] + "-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
default:
return "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
}

View File

@@ -4,20 +4,20 @@ import "fmt"
// Error is a base error that implement scw.SdkError
type Error struct {
str string
err error
Str string
Err error
}
// Error implement standard xerror.Wrapper interface
func (e *Error) Unwrap() error {
return e.err
return e.Err
}
// Error implement standard error interface
func (e *Error) Error() string {
str := "[scaleway-sdk-go] " + e.str
if e.err != nil {
str += ": " + e.err.Error()
str := "scaleway-sdk-go: " + e.Str
if e.Err != nil {
str += ": " + e.Err.Error()
}
return str
}
@@ -28,14 +28,14 @@ func (e *Error) IsScwSdkError() {}
// New creates a new error with that same interface as fmt.Errorf
func New(format string, args ...interface{}) *Error {
return &Error{
str: fmt.Sprintf(format, args...),
Str: fmt.Sprintf(format, args...),
}
}
// Wrap an error with additional information
func Wrap(err error, format string, args ...interface{}) *Error {
return &Error{
err: err,
str: fmt.Sprintf(format, args...),
Err: err,
Str: fmt.Sprintf(format, args...),
}
}

View File

@@ -137,3 +137,24 @@ func (ds *LongDurationSlice) Standard() []*time.Duration {
}
return t
}
// LongDurationint32Map is a int32 map of *LongDuration
type LongDurationint32Map map[int32]*LongDuration
// NewLongDurationint32Map converts a map[int32]*time.LongDuration to a LongDurationint32Map type.
func NewLongDurationint32Map(t map[int32]*time.Duration) LongDurationint32Map {
dm := make(LongDurationint32Map, len(t))
for i := range t {
dm[i] = NewLongDuration(t[i])
}
return dm
}
// Standard converts a LongDurationint32Map to a map[int32]*time.LongDuration type.
func (dm *LongDurationint32Map) Standard() map[int32]*time.Duration {
t := make(map[int32]*time.Duration, len(*dm))
for key, value := range *dm {
t[key] = value.Standard()
}
return t
}

View File

@@ -27,5 +27,4 @@ func AddToQuery(query url.Values, key string, value interface{}) {
default:
query.Add(key, fmt.Sprint(elemValue.Interface()))
}
}

View File

@@ -94,9 +94,18 @@ func newLogger(w io.Writer, level LogLevel) *loggerT {
// Info logs will be written to infoW and debugW.
// Debug logs will be written to debugW.
var m [4]*log.Logger
m[LogLevelError] = log.New(io.MultiWriter(debugW, infoW, warningW, errorW), severityName[LogLevelError]+": ", log.LstdFlags)
m[LogLevelWarning] = log.New(io.MultiWriter(debugW, infoW, warningW), severityName[LogLevelWarning]+": ", log.LstdFlags)
m[LogLevelInfo] = log.New(io.MultiWriter(debugW, infoW), severityName[LogLevelInfo]+": ", log.LstdFlags)
m[LogLevelDebug] = log.New(debugW, severityName[LogLevelDebug]+": ", log.LstdFlags)
m[LogLevelError] = log.New(io.MultiWriter(debugW, infoW, warningW, errorW),
severityName[LogLevelError]+": ", log.LstdFlags)
m[LogLevelWarning] = log.New(io.MultiWriter(debugW, infoW, warningW),
severityName[LogLevelWarning]+": ", log.LstdFlags)
m[LogLevelInfo] = log.New(io.MultiWriter(debugW, infoW),
severityName[LogLevelInfo]+": ", log.LstdFlags)
m[LogLevelDebug] = log.New(debugW,
severityName[LogLevelDebug]+": ", log.LstdFlags)
return &loggerT{m: m, v: level}
}

View File

@@ -0,0 +1,863 @@
// Source: github.com/docker/docker/pkg/namesgenerator
package namegenerator
import (
"math/rand"
"strings"
"time"
)
var r *rand.Rand
func init() {
source := rand.NewSource(time.Now().UnixNano())
r = rand.New(source)
}
var (
left = [...]string{
"admiring",
"adoring",
"affectionate",
"agitated",
"amazing",
"angry",
"awesome",
"beautiful",
"blissful",
"bold",
"boring",
"brave",
"busy",
"charming",
"clever",
"cocky",
"cool",
"compassionate",
"competent",
"condescending",
"confident",
"cranky",
"crazy",
"dazzling",
"determined",
"distracted",
"dreamy",
"eager",
"ecstatic",
"elastic",
"elated",
"elegant",
"eloquent",
"epic",
"exciting",
"fervent",
"festive",
"flamboyant",
"focused",
"friendly",
"frosty",
"funny",
"gallant",
"gifted",
"goofy",
"gracious",
"great",
"happy",
"hardcore",
"heuristic",
"hopeful",
"hungry",
"infallible",
"inspiring",
"interesting",
"intelligent",
"jolly",
"jovial",
"keen",
"kind",
"laughing",
"loving",
"lucid",
"magical",
"mystifying",
"modest",
"musing",
"naughty",
"nervous",
"nice",
"nifty",
"nostalgic",
"objective",
"optimistic",
"peaceful",
"pedantic",
"pensive",
"practical",
"priceless",
"quirky",
"quizzical",
"recursing",
"relaxed",
"reverent",
"romantic",
"sad",
"serene",
"sharp",
"silly",
"sleepy",
"stoic",
"strange",
"stupefied",
"suspicious",
"sweet",
"tender",
"thirsty",
"trusting",
"unruffled",
"upbeat",
"vibrant",
"vigilant",
"vigorous",
"wizardly",
"wonderful",
"xenodochial",
"youthful",
"zealous",
"zen",
}
// Docker, starting from 0.7.x, generates names from notable scientists and hackers.
// Please, for any amazing man that you add to the list, consider adding an equally amazing woman to it, and vice versa.
right = [...]string{
// Muhammad ibn Jābir al-Ḥarrānī al-Battānī was a founding father of astronomy. https://en.wikipedia.org/wiki/Mu%E1%B8%A5ammad_ibn_J%C4%81bir_al-%E1%B8%A4arr%C4%81n%C4%AB_al-Batt%C4%81n%C4%AB
"albattani",
// Frances E. Allen, became the first female IBM Fellow in 1989. In 2006, she became the first female recipient of the ACM's Turing Award. https://en.wikipedia.org/wiki/Frances_E._Allen
"allen",
// June Almeida - Scottish virologist who took the first pictures of the rubella virus - https://en.wikipedia.org/wiki/June_Almeida
"almeida",
// Kathleen Antonelli, American computer programmer and one of the six original programmers of the ENIAC - https://en.wikipedia.org/wiki/Kathleen_Antonelli
"antonelli",
// Maria Gaetana Agnesi - Italian mathematician, philosopher, theologian and humanitarian. She was the first woman to write a mathematics handbook and the first woman appointed as a Mathematics Professor at a University. https://en.wikipedia.org/wiki/Maria_Gaetana_Agnesi
"agnesi",
// Archimedes was a physicist, engineer and mathematician who invented too many things to list them here. https://en.wikipedia.org/wiki/Archimedes
"archimedes",
// Maria Ardinghelli - Italian translator, mathematician and physicist - https://en.wikipedia.org/wiki/Maria_Ardinghelli
"ardinghelli",
// Aryabhata - Ancient Indian mathematician-astronomer during 476-550 CE https://en.wikipedia.org/wiki/Aryabhata
"aryabhata",
// Wanda Austin - Wanda Austin is the President and CEO of The Aerospace Corporation, a leading architect for the US security space programs. https://en.wikipedia.org/wiki/Wanda_Austin
"austin",
// Charles Babbage invented the concept of a programmable computer. https://en.wikipedia.org/wiki/Charles_Babbage.
"babbage",
// Stefan Banach - Polish mathematician, was one of the founders of modern functional analysis. https://en.wikipedia.org/wiki/Stefan_Banach
"banach",
// Buckaroo Banzai and his mentor Dr. Hikita perfectd the "oscillation overthruster", a device that allows one to pass through solid matter. - https://en.wikipedia.org/wiki/The_Adventures_of_Buckaroo_Banzai_Across_the_8th_Dimension
"banzai",
// John Bardeen co-invented the transistor - https://en.wikipedia.org/wiki/John_Bardeen
"bardeen",
// Jean Bartik, born Betty Jean Jennings, was one of the original programmers for the ENIAC computer. https://en.wikipedia.org/wiki/Jean_Bartik
"bartik",
// Laura Bassi, the world's first female professor https://en.wikipedia.org/wiki/Laura_Bassi
"bassi",
// Hugh Beaver, British engineer, founder of the Guinness Book of World Records https://en.wikipedia.org/wiki/Hugh_Beaver
"beaver",
// Alexander Graham Bell - an eminent Scottish-born scientist, inventor, engineer and innovator who is credited with inventing the first practical telephone - https://en.wikipedia.org/wiki/Alexander_Graham_Bell
"bell",
// Karl Friedrich Benz - a German automobile engineer. Inventor of the first practical motorcar. https://en.wikipedia.org/wiki/Karl_Benz
"benz",
// Homi J Bhabha - was an Indian nuclear physicist, founding director, and professor of physics at the Tata Institute of Fundamental Research. Colloquially known as "father of Indian nuclear programme"- https://en.wikipedia.org/wiki/Homi_J._Bhabha
"bhabha",
// Bhaskara II - Ancient Indian mathematician-astronomer whose work on calculus predates Newton and Leibniz by over half a millennium - https://en.wikipedia.org/wiki/Bh%C4%81skara_II#Calculus
"bhaskara",
// Sue Black - British computer scientist and campaigner. She has been instrumental in saving Bletchley Park, the site of World War II codebreaking - https://en.wikipedia.org/wiki/Sue_Black_(computer_scientist)
"black",
// Elizabeth Helen Blackburn - Australian-American Nobel laureate; best known for co-discovering telomerase. https://en.wikipedia.org/wiki/Elizabeth_Blackburn
"blackburn",
// Elizabeth Blackwell - American doctor and first American woman to receive a medical degree - https://en.wikipedia.org/wiki/Elizabeth_Blackwell
"blackwell",
// Niels Bohr is the father of quantum theory. https://en.wikipedia.org/wiki/Niels_Bohr.
"bohr",
// Kathleen Booth, she's credited with writing the first assembly language. https://en.wikipedia.org/wiki/Kathleen_Booth
"booth",
// Anita Borg - Anita Borg was the founding director of the Institute for Women and Technology (IWT). https://en.wikipedia.org/wiki/Anita_Borg
"borg",
// Satyendra Nath Bose - He provided the foundation for BoseEinstein statistics and the theory of the BoseEinstein condensate. - https://en.wikipedia.org/wiki/Satyendra_Nath_Bose
"bose",
// Katherine Louise Bouman is an imaging scientist and Assistant Professor of Computer Science at the California Institute of Technology. She researches computational methods for imaging, and developed an algorithm that made possible the picture first visualization of a black hole using the Event Horizon Telescope. - https://en.wikipedia.org/wiki/Katie_Bouman
"bouman",
// Evelyn Boyd Granville - She was one of the first African-American woman to receive a Ph.D. in mathematics; she earned it in 1949 from Yale University. https://en.wikipedia.org/wiki/Evelyn_Boyd_Granville
"boyd",
// Brahmagupta - Ancient Indian mathematician during 598-670 CE who gave rules to compute with zero - https://en.wikipedia.org/wiki/Brahmagupta#Zero
"brahmagupta",
// Walter Houser Brattain co-invented the transistor - https://en.wikipedia.org/wiki/Walter_Houser_Brattain
"brattain",
// Emmett Brown invented time travel. https://en.wikipedia.org/wiki/Emmett_Brown (thanks Brian Goff)
"brown",
// Linda Brown Buck - American biologist and Nobel laureate best known for her genetic and molecular analyses of the mechanisms of smell. https://en.wikipedia.org/wiki/Linda_B._Buck
"buck",
// Dame Susan Jocelyn Bell Burnell - Northern Irish astrophysicist who discovered radio pulsars and was the first to analyse them. https://en.wikipedia.org/wiki/Jocelyn_Bell_Burnell
"burnell",
// Annie Jump Cannon - pioneering female astronomer who classified hundreds of thousands of stars and created the system we use to understand stars today. https://en.wikipedia.org/wiki/Annie_Jump_Cannon
"cannon",
// Rachel Carson - American marine biologist and conservationist, her book Silent Spring and other writings are credited with advancing the global environmental movement. https://en.wikipedia.org/wiki/Rachel_Carson
"carson",
// Dame Mary Lucy Cartwright - British mathematician who was one of the first to study what is now known as chaos theory. Also known for Cartwright's theorem which finds applications in signal processing. https://en.wikipedia.org/wiki/Mary_Cartwright
"cartwright",
// Vinton Gray Cerf - American Internet pioneer, recognised as one of "the fathers of the Internet". With Robert Elliot Kahn, he designed TCP and IP, the primary data communication protocols of the Internet and other computer networks. https://en.wikipedia.org/wiki/Vint_Cerf
"cerf",
// Subrahmanyan Chandrasekhar - Astrophysicist known for his mathematical theory on different stages and evolution in structures of the stars. He has won nobel prize for physics - https://en.wikipedia.org/wiki/Subrahmanyan_Chandrasekhar
"chandrasekhar",
// Sergey Alexeyevich Chaplygin (Russian: Серге́й Алексе́евич Чаплы́гин; April 5, 1869 October 8, 1942) was a Russian and Soviet physicist, mathematician, and mechanical engineer. He is known for mathematical formulas such as Chaplygin's equation and for a hypothetical substance in cosmology called Chaplygin gas, named after him. https://en.wikipedia.org/wiki/Sergey_Chaplygin
"chaplygin",
// Émilie du Châtelet - French natural philosopher, mathematician, physicist, and author during the early 1730s, known for her translation of and commentary on Isaac Newton's book Principia containing basic laws of physics. https://en.wikipedia.org/wiki/%C3%89milie_du_Ch%C3%A2telet
"chatelet",
// Asima Chatterjee was an Indian organic chemist noted for her research on vinca alkaloids, development of drugs for treatment of epilepsy and malaria - https://en.wikipedia.org/wiki/Asima_Chatterjee
"chatterjee",
// Pafnuty Chebyshev - Russian mathematician. He is known fo his works on probability, statistics, mechanics, analytical geometry and number theory https://en.wikipedia.org/wiki/Pafnuty_Chebyshev
"chebyshev",
// Bram Cohen - American computer programmer and author of the BitTorrent peer-to-peer protocol. https://en.wikipedia.org/wiki/Bram_Cohen
"cohen",
// David Lee Chaum - American computer scientist and cryptographer. Known for his seminal contributions in the field of anonymous communication. https://en.wikipedia.org/wiki/David_Chaum
"chaum",
// Joan Clarke - Bletchley Park code breaker during the Second World War who pioneered techniques that remained top secret for decades. Also an accomplished numismatist https://en.wikipedia.org/wiki/Joan_Clarke
"clarke",
// Jane Colden - American botanist widely considered the first female American botanist - https://en.wikipedia.org/wiki/Jane_Colden
"colden",
// Gerty Theresa Cori - American biochemist who became the third woman—and first American woman—to win a Nobel Prize in science, and the first woman to be awarded the Nobel Prize in Physiology or Medicine. Cori was born in Prague. https://en.wikipedia.org/wiki/Gerty_Cori
"cori",
// Seymour Roger Cray was an American electrical engineer and supercomputer architect who designed a series of computers that were the fastest in the world for decades. https://en.wikipedia.org/wiki/Seymour_Cray
"cray",
// This entry reflects a husband and wife team who worked together:
// Joan Curran was a Welsh scientist who developed radar and invented chaff, a radar countermeasure. https://en.wikipedia.org/wiki/Joan_Curran
// Samuel Curran was an Irish physicist who worked alongside his wife during WWII and invented the proximity fuse. https://en.wikipedia.org/wiki/Samuel_Curran
"curran",
// Marie Curie discovered radioactivity. https://en.wikipedia.org/wiki/Marie_Curie.
"curie",
// Charles Darwin established the principles of natural evolution. https://en.wikipedia.org/wiki/Charles_Darwin.
"darwin",
// Leonardo Da Vinci invented too many things to list here. https://en.wikipedia.org/wiki/Leonardo_da_Vinci.
"davinci",
// A. K. (Alexander Keewatin) Dewdney, Canadian mathematician, computer scientist, author and filmmaker. Contributor to Scientific American's "Computer Recreations" from 1984 to 1991. Author of Core War (program), The Planiverse, The Armchair Universe, The Magic Machine, The New Turing Omnibus, and more. https://en.wikipedia.org/wiki/Alexander_Dewdney
"dewdney",
// Satish Dhawan - Indian mathematician and aerospace engineer, known for leading the successful and indigenous development of the Indian space programme. https://en.wikipedia.org/wiki/Satish_Dhawan
"dhawan",
// Bailey Whitfield Diffie - American cryptographer and one of the pioneers of public-key cryptography. https://en.wikipedia.org/wiki/Whitfield_Diffie
"diffie",
// Edsger Wybe Dijkstra was a Dutch computer scientist and mathematical scientist. https://en.wikipedia.org/wiki/Edsger_W._Dijkstra.
"dijkstra",
// Paul Adrien Maurice Dirac - English theoretical physicist who made fundamental contributions to the early development of both quantum mechanics and quantum electrodynamics. https://en.wikipedia.org/wiki/Paul_Dirac
"dirac",
// Agnes Meyer Driscoll - American cryptanalyst during World Wars I and II who successfully cryptanalysed a number of Japanese ciphers. She was also the co-developer of one of the cipher machines of the US Navy, the CM. https://en.wikipedia.org/wiki/Agnes_Meyer_Driscoll
"driscoll",
// Donna Dubinsky - played an integral role in the development of personal digital assistants (PDAs) serving as CEO of Palm, Inc. and co-founding Handspring. https://en.wikipedia.org/wiki/Donna_Dubinsky
"dubinsky",
// Annie Easley - She was a leading member of the team which developed software for the Centaur rocket stage and one of the first African-Americans in her field. https://en.wikipedia.org/wiki/Annie_Easley
"easley",
// Thomas Alva Edison, prolific inventor https://en.wikipedia.org/wiki/Thomas_Edison
"edison",
// Albert Einstein invented the general theory of relativity. https://en.wikipedia.org/wiki/Albert_Einstein
"einstein",
// Alexandra Asanovna Elbakyan (Russian: Алекса́ндра Аса́новна Элбакя́н) is a Kazakhstani graduate student, computer programmer, internet pirate in hiding, and the creator of the site Sci-Hub. Nature has listed her in 2016 in the top ten people that mattered in science, and Ars Technica has compared her to Aaron Swartz. - https://en.wikipedia.org/wiki/Alexandra_Elbakyan
"elbakyan",
// Taher A. ElGamal - Egyptian cryptographer best known for the ElGamal discrete log cryptosystem and the ElGamal digital signature scheme. https://en.wikipedia.org/wiki/Taher_Elgamal
"elgamal",
// Gertrude Elion - American biochemist, pharmacologist and the 1988 recipient of the Nobel Prize in Medicine - https://en.wikipedia.org/wiki/Gertrude_Elion
"elion",
// James Henry Ellis - British engineer and cryptographer employed by the GCHQ. Best known for conceiving for the first time, the idea of public-key cryptography. https://en.wikipedia.org/wiki/James_H._Ellis
"ellis",
// Douglas Engelbart gave the mother of all demos: https://en.wikipedia.org/wiki/Douglas_Engelbart
"engelbart",
// Euclid invented geometry. https://en.wikipedia.org/wiki/Euclid
"euclid",
// Leonhard Euler invented large parts of modern mathematics. https://de.wikipedia.org/wiki/Leonhard_Euler
"euler",
// Michael Faraday - British scientist who contributed to the study of electromagnetism and electrochemistry. https://en.wikipedia.org/wiki/Michael_Faraday
"faraday",
// Horst Feistel - German-born American cryptographer who was one of the earliest non-government researchers to study the design and theory of block ciphers. Co-developer of DES and Lucifer. Feistel networks, a symmetric structure used in the construction of block ciphers are named after him. https://en.wikipedia.org/wiki/Horst_Feistel
"feistel",
// Pierre de Fermat pioneered several aspects of modern mathematics. https://en.wikipedia.org/wiki/Pierre_de_Fermat
"fermat",
// Enrico Fermi invented the first nuclear reactor. https://en.wikipedia.org/wiki/Enrico_Fermi.
"fermi",
// Richard Feynman was a key contributor to quantum mechanics and particle physics. https://en.wikipedia.org/wiki/Richard_Feynman
"feynman",
// Benjamin Franklin is famous for his experiments in electricity and the invention of the lightning rod.
"franklin",
// Yuri Alekseyevich Gagarin - Soviet pilot and cosmonaut, best known as the first human to journey into outer space. https://en.wikipedia.org/wiki/Yuri_Gagarin
"gagarin",
// Galileo was a founding father of modern astronomy, and faced politics and obscurantism to establish scientific truth. https://en.wikipedia.org/wiki/Galileo_Galilei
"galileo",
// Évariste Galois - French mathematician whose work laid the foundations of Galois theory and group theory, two major branches of abstract algebra, and the subfield of Galois connections, all while still in his late teens. https://en.wikipedia.org/wiki/%C3%89variste_Galois
"galois",
// Kadambini Ganguly - Indian physician, known for being the first South Asian female physician, trained in western medicine, to graduate in South Asia. https://en.wikipedia.org/wiki/Kadambini_Ganguly
"ganguly",
// William Henry "Bill" Gates III is an American business magnate, philanthropist, investor, computer programmer, and inventor. https://en.wikipedia.org/wiki/Bill_Gates
"gates",
// Johann Carl Friedrich Gauss - German mathematician who made significant contributions to many fields, including number theory, algebra, statistics, analysis, differential geometry, geodesy, geophysics, mechanics, electrostatics, magnetic fields, astronomy, matrix theory, and optics. https://en.wikipedia.org/wiki/Carl_Friedrich_Gauss
"gauss",
// Marie-Sophie Germain - French mathematician, physicist and philosopher. Known for her work on elasticity theory, number theory and philosophy. https://en.wikipedia.org/wiki/Sophie_Germain
"germain",
// Adele Goldberg, was one of the designers and developers of the Smalltalk language. https://en.wikipedia.org/wiki/Adele_Goldberg_(computer_scientist)
"goldberg",
// Adele Goldstine, born Adele Katz, wrote the complete technical description for the first electronic digital computer, ENIAC. https://en.wikipedia.org/wiki/Adele_Goldstine
"goldstine",
// Shafi Goldwasser is a computer scientist known for creating theoretical foundations of modern cryptography. Winner of 2012 ACM Turing Award. https://en.wikipedia.org/wiki/Shafi_Goldwasser
"goldwasser",
// James Golick, all around gangster.
"golick",
// Jane Goodall - British primatologist, ethologist, and anthropologist who is considered to be the world's foremost expert on chimpanzees - https://en.wikipedia.org/wiki/Jane_Goodall
"goodall",
// Stephen Jay Gould was was an American paleontologist, evolutionary biologist, and historian of science. He is most famous for the theory of punctuated equilibrium - https://en.wikipedia.org/wiki/Stephen_Jay_Gould
"gould",
// Carolyn Widney Greider - American molecular biologist and joint winner of the 2009 Nobel Prize for Physiology or Medicine for the discovery of telomerase. https://en.wikipedia.org/wiki/Carol_W._Greider
"greider",
// Alexander Grothendieck - German-born French mathematician who became a leading figure in the creation of modern algebraic geometry. https://en.wikipedia.org/wiki/Alexander_Grothendieck
"grothendieck",
// Lois Haibt - American computer scientist, part of the team at IBM that developed FORTRAN - https://en.wikipedia.org/wiki/Lois_Haibt
"haibt",
// Margaret Hamilton - Director of the Software Engineering Division of the MIT Instrumentation Laboratory, which developed on-board flight software for the Apollo space program. https://en.wikipedia.org/wiki/Margaret_Hamilton_(scientist)
"hamilton",
// Caroline Harriet Haslett - English electrical engineer, electricity industry administrator and champion of women's rights. Co-author of British Standard 1363 that specifies AC power plugs and sockets used across the United Kingdom (which is widely considered as one of the safest designs). https://en.wikipedia.org/wiki/Caroline_Haslett
"haslett",
// Stephen Hawking pioneered the field of cosmology by combining general relativity and quantum mechanics. https://en.wikipedia.org/wiki/Stephen_Hawking
"hawking",
// Martin Edward Hellman - American cryptologist, best known for his invention of public-key cryptography in co-operation with Whitfield Diffie and Ralph Merkle. https://en.wikipedia.org/wiki/Martin_Hellman
"hellman",
// Werner Heisenberg was a founding father of quantum mechanics. https://en.wikipedia.org/wiki/Werner_Heisenberg
"heisenberg",
// Grete Hermann was a German philosopher noted for her philosophical work on the foundations of quantum mechanics. https://en.wikipedia.org/wiki/Grete_Hermann
"hermann",
// Caroline Lucretia Herschel - German astronomer and discoverer of several comets. https://en.wikipedia.org/wiki/Caroline_Herschel
"herschel",
// Heinrich Rudolf Hertz - German physicist who first conclusively proved the existence of the electromagnetic waves. https://en.wikipedia.org/wiki/Heinrich_Hertz
"hertz",
// Jaroslav Heyrovský was the inventor of the polarographic method, father of the electroanalytical method, and recipient of the Nobel Prize in 1959. His main field of work was polarography. https://en.wikipedia.org/wiki/Jaroslav_Heyrovsk%C3%BD
"heyrovsky",
// Dorothy Hodgkin was a British biochemist, credited with the development of protein crystallography. She was awarded the Nobel Prize in Chemistry in 1964. https://en.wikipedia.org/wiki/Dorothy_Hodgkin
"hodgkin",
// Douglas R. Hofstadter is an American professor of cognitive science and author of the Pulitzer Prize and American Book Award-winning work Goedel, Escher, Bach: An Eternal Golden Braid in 1979. A mind-bending work which coined Hofstadter's Law: "It always takes longer than you expect, even when you take into account Hofstadter's Law." https://en.wikipedia.org/wiki/Douglas_Hofstadter
"hofstadter",
// Erna Schneider Hoover revolutionized modern communication by inventing a computerized telephone switching method. https://en.wikipedia.org/wiki/Erna_Schneider_Hoover
"hoover",
// Grace Hopper developed the first compiler for a computer programming language and is credited with popularizing the term "debugging" for fixing computer glitches. https://en.wikipedia.org/wiki/Grace_Hopper
"hopper",
// Frances Hugle, she was an American scientist, engineer, and inventor who contributed to the understanding of semiconductors, integrated circuitry, and the unique electrical principles of microscopic materials. https://en.wikipedia.org/wiki/Frances_Hugle
"hugle",
// Hypatia - Greek Alexandrine Neoplatonist philosopher in Egypt who was one of the earliest mothers of mathematics - https://en.wikipedia.org/wiki/Hypatia
"hypatia",
// Teruko Ishizaka - Japanese scientist and immunologist who co-discovered the antibody class Immunoglobulin E. https://en.wikipedia.org/wiki/Teruko_Ishizaka
"ishizaka",
// Mary Jackson, American mathematician and aerospace engineer who earned the highest title within NASA's engineering department - https://en.wikipedia.org/wiki/Mary_Jackson_(engineer)
"jackson",
// Yeong-Sil Jang was a Korean scientist and astronomer during the Joseon Dynasty; he invented the first metal printing press and water gauge. https://en.wikipedia.org/wiki/Jang_Yeong-sil
"jang",
// Betty Jennings - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Jean_Bartik
"jennings",
// Mary Lou Jepsen, was the founder and chief technology officer of One Laptop Per Child (OLPC), and the founder of Pixel Qi. https://en.wikipedia.org/wiki/Mary_Lou_Jepsen
"jepsen",
// Katherine Coleman Goble Johnson - American physicist and mathematician contributed to the NASA. https://en.wikipedia.org/wiki/Katherine_Johnson
"johnson",
// Irène Joliot-Curie - French scientist who was awarded the Nobel Prize for Chemistry in 1935. Daughter of Marie and Pierre Curie. https://en.wikipedia.org/wiki/Ir%C3%A8ne_Joliot-Curie
"joliot",
// Karen Spärck Jones came up with the concept of inverse document frequency, which is used in most search engines today. https://en.wikipedia.org/wiki/Karen_Sp%C3%A4rck_Jones
"jones",
// A. P. J. Abdul Kalam - is an Indian scientist aka Missile Man of India for his work on the development of ballistic missile and launch vehicle technology - https://en.wikipedia.org/wiki/A._P._J._Abdul_Kalam
"kalam",
// Sergey Petrovich Kapitsa (Russian: Серге́й Петро́вич Капи́ца; 14 February 1928 14 August 2012) was a Russian physicist and demographer. He was best known as host of the popular and long-running Russian scientific TV show, Evident, but Incredible. His father was the Nobel laureate Soviet-era physicist Pyotr Kapitsa, and his brother was the geographer and Antarctic explorer Andrey Kapitsa. - https://en.wikipedia.org/wiki/Sergey_Kapitsa
"kapitsa",
// Susan Kare, created the icons and many of the interface elements for the original Apple Macintosh in the 1980s, and was an original employee of NeXT, working as the Creative Director. https://en.wikipedia.org/wiki/Susan_Kare
"kare",
// Mstislav Keldysh - a Soviet scientist in the field of mathematics and mechanics, academician of the USSR Academy of Sciences (1946), President of the USSR Academy of Sciences (19611975), three times Hero of Socialist Labor (1956, 1961, 1971), fellow of the Royal Society of Edinburgh (1968). https://en.wikipedia.org/wiki/Mstislav_Keldysh
"keldysh",
// Mary Kenneth Keller, Sister Mary Kenneth Keller became the first American woman to earn a PhD in Computer Science in 1965. https://en.wikipedia.org/wiki/Mary_Kenneth_Keller
"keller",
// Johannes Kepler, German astronomer known for his three laws of planetary motion - https://en.wikipedia.org/wiki/Johannes_Kepler
"kepler",
// Omar Khayyam - Persian mathematician, astronomer and poet. Known for his work on the classification and solution of cubic equations, for his contribution to the understanding of Euclid's fifth postulate and for computing the length of a year very accurately. https://en.wikipedia.org/wiki/Omar_Khayyam
"khayyam",
// Har Gobind Khorana - Indian-American biochemist who shared the 1968 Nobel Prize for Physiology - https://en.wikipedia.org/wiki/Har_Gobind_Khorana
"khorana",
// Jack Kilby invented silicone integrated circuits and gave Silicon Valley its name. - https://en.wikipedia.org/wiki/Jack_Kilby
"kilby",
// Maria Kirch - German astronomer and first woman to discover a comet - https://en.wikipedia.org/wiki/Maria_Margarethe_Kirch
"kirch",
// Donald Knuth - American computer scientist, author of "The Art of Computer Programming" and creator of the TeX typesetting system. https://en.wikipedia.org/wiki/Donald_Knuth
"knuth",
// Sophie Kowalevski - Russian mathematician responsible for important original contributions to analysis, differential equations and mechanics - https://en.wikipedia.org/wiki/Sofia_Kovalevskaya
"kowalevski",
// Marie-Jeanne de Lalande - French astronomer, mathematician and cataloguer of stars - https://en.wikipedia.org/wiki/Marie-Jeanne_de_Lalande
"lalande",
// Hedy Lamarr - Actress and inventor. The principles of her work are now incorporated into modern Wi-Fi, CDMA and Bluetooth technology. https://en.wikipedia.org/wiki/Hedy_Lamarr
"lamarr",
// Leslie B. Lamport - American computer scientist. Lamport is best known for his seminal work in distributed systems and was the winner of the 2013 Turing Award. https://en.wikipedia.org/wiki/Leslie_Lamport
"lamport",
// Mary Leakey - British paleoanthropologist who discovered the first fossilized Proconsul skull - https://en.wikipedia.org/wiki/Mary_Leakey
"leakey",
// Henrietta Swan Leavitt - she was an American astronomer who discovered the relation between the luminosity and the period of Cepheid variable stars. https://en.wikipedia.org/wiki/Henrietta_Swan_Leavitt
"leavitt",
// Esther Miriam Zimmer Lederberg - American microbiologist and a pioneer of bacterial genetics. https://en.wikipedia.org/wiki/Esther_Lederberg
"lederberg",
// Inge Lehmann - Danish seismologist and geophysicist. Known for discovering in 1936 that the Earth has a solid inner core inside a molten outer core. https://en.wikipedia.org/wiki/Inge_Lehmann
"lehmann",
// Daniel Lewin - Mathematician, Akamai co-founder, soldier, 9/11 victim-- Developed optimization techniques for routing traffic on the internet. Died attempting to stop the 9-11 hijackers. https://en.wikipedia.org/wiki/Daniel_Lewin
"lewin",
// Ruth Lichterman - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Ruth_Teitelbaum
"lichterman",
// Barbara Liskov - co-developed the Liskov substitution principle. Liskov was also the winner of the Turing Prize in 2008. - https://en.wikipedia.org/wiki/Barbara_Liskov
"liskov",
// Ada Lovelace invented the first algorithm. https://en.wikipedia.org/wiki/Ada_Lovelace (thanks James Turnbull)
"lovelace",
// Auguste and Louis Lumière - the first filmmakers in history - https://en.wikipedia.org/wiki/Auguste_and_Louis_Lumi%C3%A8re
"lumiere",
// Mahavira - Ancient Indian mathematician during 9th century AD who discovered basic algebraic identities - https://en.wikipedia.org/wiki/Mah%C4%81v%C4%ABra_(mathematician)
"mahavira",
// Lynn Margulis (b. Lynn Petra Alexander) - an American evolutionary theorist and biologist, science author, educator, and popularizer, and was the primary modern proponent for the significance of symbiosis in evolution. - https://en.wikipedia.org/wiki/Lynn_Margulis
"margulis",
// Yukihiro Matsumoto - Japanese computer scientist and software programmer best known as the chief designer of the Ruby programming language. https://en.wikipedia.org/wiki/Yukihiro_Matsumoto
"matsumoto",
// James Clerk Maxwell - Scottish physicist, best known for his formulation of electromagnetic theory. https://en.wikipedia.org/wiki/James_Clerk_Maxwell
"maxwell",
// Maria Mayer - American theoretical physicist and Nobel laureate in Physics for proposing the nuclear shell model of the atomic nucleus - https://en.wikipedia.org/wiki/Maria_Mayer
"mayer",
// John McCarthy invented LISP: https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)
"mccarthy",
// Barbara McClintock - a distinguished American cytogeneticist, 1983 Nobel Laureate in Physiology or Medicine for discovering transposons. https://en.wikipedia.org/wiki/Barbara_McClintock
"mcclintock",
// Anne Laura Dorinthea McLaren - British developmental biologist whose work helped lead to human in-vitro fertilisation. https://en.wikipedia.org/wiki/Anne_McLaren
"mclaren",
// Malcolm McLean invented the modern shipping container: https://en.wikipedia.org/wiki/Malcom_McLean
"mclean",
// Kay McNulty - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Kathleen_Antonelli
"mcnulty",
// Gregor Johann Mendel - Czech scientist and founder of genetics. https://en.wikipedia.org/wiki/Gregor_Mendel
"mendel",
// Dmitri Mendeleev - a chemist and inventor. He formulated the Periodic Law, created a farsighted version of the periodic table of elements, and used it to correct the properties of some already discovered elements and also to predict the properties of eight elements yet to be discovered. https://en.wikipedia.org/wiki/Dmitri_Mendeleev
"mendeleev",
// Lise Meitner - Austrian/Swedish physicist who was involved in the discovery of nuclear fission. The element meitnerium is named after her - https://en.wikipedia.org/wiki/Lise_Meitner
"meitner",
// Carla Meninsky, was the game designer and programmer for Atari 2600 games Dodge 'Em and Warlords. https://en.wikipedia.org/wiki/Carla_Meninsky
"meninsky",
// Ralph C. Merkle - American computer scientist, known for devising Merkle's puzzles - one of the very first schemes for public-key cryptography. Also, inventor of Merkle trees and co-inventor of the Merkle-Damgård construction for building collision-resistant cryptographic hash functions and the Merkle-Hellman knapsack cryptosystem. https://en.wikipedia.org/wiki/Ralph_Merkle
"merkle",
// Johanna Mestorf - German prehistoric archaeologist and first female museum director in Germany - https://en.wikipedia.org/wiki/Johanna_Mestorf
"mestorf",
// Marvin Minsky - Pioneer in Artificial Intelligence, co-founder of the MIT's AI Lab, won the Turing Award in 1969. https://en.wikipedia.org/wiki/Marvin_Minsky
"minsky",
// Maryam Mirzakhani - an Iranian mathematician and the first woman to win the Fields Medal. https://en.wikipedia.org/wiki/Maryam_Mirzakhani
"mirzakhani",
// Gordon Earle Moore - American engineer, Silicon Valley founding father, author of Moore's law. https://en.wikipedia.org/wiki/Gordon_Moore
"moore",
// Samuel Morse - contributed to the invention of a single-wire telegraph system based on European telegraphs and was a co-developer of the Morse code - https://en.wikipedia.org/wiki/Samuel_Morse
"morse",
// Ian Murdock - founder of the Debian project - https://en.wikipedia.org/wiki/Ian_Murdock
"murdock",
// May-Britt Moser - Nobel prize winner neuroscientist who contributed to the discovery of grid cells in the brain. https://en.wikipedia.org/wiki/May-Britt_Moser
"moser",
// John Napier of Merchiston - Scottish landowner known as an astronomer, mathematician and physicist. Best known for his discovery of logarithms. https://en.wikipedia.org/wiki/John_Napier
"napier",
// John Forbes Nash, Jr. - American mathematician who made fundamental contributions to game theory, differential geometry, and the study of partial differential equations. https://en.wikipedia.org/wiki/John_Forbes_Nash_Jr.
"nash",
// John von Neumann - todays computer architectures are based on the von Neumann architecture. https://en.wikipedia.org/wiki/Von_Neumann_architecture
"neumann",
// Isaac Newton invented classic mechanics and modern optics. https://en.wikipedia.org/wiki/Isaac_Newton
"newton",
// Xavier Niel - ;) https://en.wikipedia.org/wiki/Xavier_Niel
"niel",
// Florence Nightingale, more prominently known as a nurse, was also the first female member of the Royal Statistical Society and a pioneer in statistical graphics https://en.wikipedia.org/wiki/Florence_Nightingale#Statistics_and_sanitary_reform
"nightingale",
// Alfred Nobel - a Swedish chemist, engineer, innovator, and armaments manufacturer (inventor of dynamite) - https://en.wikipedia.org/wiki/Alfred_Nobel
"nobel",
// Emmy Noether, German mathematician. Noether's Theorem is named after her. https://en.wikipedia.org/wiki/Emmy_Noether
"noether",
// Poppy Northcutt. Poppy Northcutt was the first woman to work as part of NASAs Mission Control. http://www.businessinsider.com/poppy-northcutt-helped-apollo-astronauts-2014-12?op=1
"northcutt",
// Robert Noyce invented silicone integrated circuits and gave Silicon Valley its name. - https://en.wikipedia.org/wiki/Robert_Noyce
"noyce",
// Panini - Ancient Indian linguist and grammarian from 4th century CE who worked on the world's first formal system - https://en.wikipedia.org/wiki/P%C4%81%E1%B9%87ini#Comparison_with_modern_formal_systems
"panini",
// Ambroise Pare invented modern surgery. https://en.wikipedia.org/wiki/Ambroise_Par%C3%A9
"pare",
// Blaise Pascal, French mathematician, physicist, and inventor - https://en.wikipedia.org/wiki/Blaise_Pascal
"pascal",
// Louis Pasteur discovered vaccination, fermentation and pasteurization. https://en.wikipedia.org/wiki/Louis_Pasteur.
"pasteur",
// Cecilia Payne-Gaposchkin was an astronomer and astrophysicist who, in 1925, proposed in her Ph.D. thesis an explanation for the composition of stars in terms of the relative abundances of hydrogen and helium. https://en.wikipedia.org/wiki/Cecilia_Payne-Gaposchkin
"payne",
// Radia Perlman is a software designer and network engineer and most famous for her invention of the spanning-tree protocol (STP). https://en.wikipedia.org/wiki/Radia_Perlman
"perlman",
// Rob Pike was a key contributor to Unix, Plan 9, the X graphic system, utf-8, and the Go programming language. https://en.wikipedia.org/wiki/Rob_Pike
"pike",
// Henri Poincaré made fundamental contributions in several fields of mathematics. https://en.wikipedia.org/wiki/Henri_Poincar%C3%A9
"poincare",
// Laura Poitras is a director and producer whose work, made possible by open source crypto tools, advances the causes of truth and freedom of information by reporting disclosures by whistleblowers such as Edward Snowden. https://en.wikipedia.org/wiki/Laura_Poitras
"poitras",
// Tatyana Avenirovna Proskuriakova (Russian: Татья́на Авени́ровна Проскуряко́ва) (January 23 [O.S. January 10] 1909 August 30, 1985) was a Russian-American Mayanist scholar and archaeologist who contributed significantly to the deciphering of Maya hieroglyphs, the writing system of the pre-Columbian Maya civilization of Mesoamerica. https://en.wikipedia.org/wiki/Tatiana_Proskouriakoff
"proskuriakova",
// Claudius Ptolemy - a Greco-Egyptian writer of Alexandria, known as a mathematician, astronomer, geographer, astrologer, and poet of a single epigram in the Greek Anthology - https://en.wikipedia.org/wiki/Ptolemy
"ptolemy",
// C. V. Raman - Indian physicist who won the Nobel Prize in 1930 for proposing the Raman effect. - https://en.wikipedia.org/wiki/C._V._Raman
"raman",
// Srinivasa Ramanujan - Indian mathematician and autodidact who made extraordinary contributions to mathematical analysis, number theory, infinite series, and continued fractions. - https://en.wikipedia.org/wiki/Srinivasa_Ramanujan
"ramanujan",
// Sally Kristen Ride was an American physicist and astronaut. She was the first American woman in space, and the youngest American astronaut. https://en.wikipedia.org/wiki/Sally_Ride
"ride",
// Rita Levi-Montalcini - Won Nobel Prize in Physiology or Medicine jointly with colleague Stanley Cohen for the discovery of nerve growth factor (https://en.wikipedia.org/wiki/Rita_Levi-Montalcini)
"montalcini",
// Dennis Ritchie - co-creator of UNIX and the C programming language. - https://en.wikipedia.org/wiki/Dennis_Ritchie
"ritchie",
// Ida Rhodes - American pioneer in computer programming, designed the first computer used for Social Security. https://en.wikipedia.org/wiki/Ida_Rhodes
"rhodes",
// Julia Hall Bowman Robinson - American mathematician renowned for her contributions to the fields of computability theory and computational complexity theory. https://en.wikipedia.org/wiki/Julia_Robinson
"robinson",
// Wilhelm Conrad Röntgen - German physicist who was awarded the first Nobel Prize in Physics in 1901 for the discovery of X-rays (Röntgen rays). https://en.wikipedia.org/wiki/Wilhelm_R%C3%B6ntgen
"roentgen",
// Rosalind Franklin - British biophysicist and X-ray crystallographer whose research was critical to the understanding of DNA - https://en.wikipedia.org/wiki/Rosalind_Franklin
"rosalind",
// Vera Rubin - American astronomer who pioneered work on galaxy rotation rates. https://en.wikipedia.org/wiki/Vera_Rubin
"rubin",
// Meghnad Saha - Indian astrophysicist best known for his development of the Saha equation, used to describe chemical and physical conditions in stars - https://en.wikipedia.org/wiki/Meghnad_Saha
"saha",
// Jean E. Sammet developed FORMAC, the first widely used computer language for symbolic manipulation of mathematical formulas. https://en.wikipedia.org/wiki/Jean_E._Sammet
"sammet",
// Mildred Sanderson - American mathematician best known for Sanderson's theorem concerning modular invariants. https://en.wikipedia.org/wiki/Mildred_Sanderson
"sanderson",
// Satoshi Nakamoto is the name used by the unknown person or group of people who developed bitcoin, authored the bitcoin white paper, and created and deployed bitcoin's original reference implementation. https://en.wikipedia.org/wiki/Satoshi_Nakamoto
"satoshi",
// Adi Shamir - Israeli cryptographer whose numerous inventions and contributions to cryptography include the Ferge Fiat Shamir identification scheme, the Rivest Shamir Adleman (RSA) public-key cryptosystem, the Shamir's secret sharing scheme, the breaking of the Merkle-Hellman cryptosystem, the TWINKLE and TWIRL factoring devices and the discovery of differential cryptanalysis (with Eli Biham). https://en.wikipedia.org/wiki/Adi_Shamir
"shamir",
// Claude Shannon - The father of information theory and founder of digital circuit design theory. (https://en.wikipedia.org/wiki/Claude_Shannon)
"shannon",
// Carol Shaw - Originally an Atari employee, Carol Shaw is said to be the first female video game designer. https://en.wikipedia.org/wiki/Carol_Shaw_(video_game_designer)
"shaw",
// Dame Stephanie "Steve" Shirley - Founded a software company in 1962 employing women working from home. https://en.wikipedia.org/wiki/Steve_Shirley
"shirley",
// William Shockley co-invented the transistor - https://en.wikipedia.org/wiki/William_Shockley
"shockley",
// Lina Solomonovna Stern (or Shtern; Russian: Лина Соломоновна Штерн; 26 August 1878 7 March 1968) was a Soviet biochemist, physiologist and humanist whose medical discoveries saved thousands of lives at the fronts of World War II. She is best known for her pioneering work on bloodbrain barrier, which she described as hemato-encephalic barrier in 1921. https://en.wikipedia.org/wiki/Lina_Stern
"shtern",
// Françoise Barré-Sinoussi - French virologist and Nobel Prize Laureate in Physiology or Medicine; her work was fundamental in identifying HIV as the cause of AIDS. https://en.wikipedia.org/wiki/Fran%C3%A7oise_Barr%C3%A9-Sinoussi
"sinoussi",
// Betty Snyder - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Betty_Holberton
"snyder",
// Cynthia Solomon - Pioneer in the fields of artificial intelligence, computer science and educational computing. Known for creation of Logo, an educational programming language. https://en.wikipedia.org/wiki/Cynthia_Solomon
"solomon",
// Frances Spence - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Frances_Spence
"spence",
// Richard Matthew Stallman - the founder of the Free Software movement, the GNU project, the Free Software Foundation, and the League for Programming Freedom. He also invented the concept of copyleft to protect the ideals of this movement, and enshrined this concept in the widely-used GPL (General Public License) for software. https://en.wikiquote.org/wiki/Richard_Stallman
"stallman",
// Michael Stonebraker is a database research pioneer and architect of Ingres, Postgres, VoltDB and SciDB. Winner of 2014 ACM Turing Award. https://en.wikipedia.org/wiki/Michael_Stonebraker
"stonebraker",
// Ivan Edward Sutherland - American computer scientist and Internet pioneer, widely regarded as the father of computer graphics. https://en.wikipedia.org/wiki/Ivan_Sutherland
"sutherland",
// Janese Swanson (with others) developed the first of the Carmen Sandiego games. She went on to found Girl Tech. https://en.wikipedia.org/wiki/Janese_Swanson
"swanson",
// Aaron Swartz was influential in creating RSS, Markdown, Creative Commons, Reddit, and much of the internet as we know it today. He was devoted to freedom of information on the web. https://en.wikiquote.org/wiki/Aaron_Swartz
"swartz",
// Bertha Swirles was a theoretical physicist who made a number of contributions to early quantum theory. https://en.wikipedia.org/wiki/Bertha_Swirles
"swirles",
// Helen Brooke Taussig - American cardiologist and founder of the field of paediatric cardiology. https://en.wikipedia.org/wiki/Helen_B._Taussig
"taussig",
// Valentina Tereshkova is a Russian engineer, cosmonaut and politician. She was the first woman to fly to space in 1963. In 2013, at the age of 76, she offered to go on a one-way mission to Mars. https://en.wikipedia.org/wiki/Valentina_Tereshkova
"tereshkova",
// Nikola Tesla invented the AC electric system and every gadget ever used by a James Bond villain. https://en.wikipedia.org/wiki/Nikola_Tesla
"tesla",
// Marie Tharp - American geologist and oceanic cartographer who co-created the first scientific map of the Atlantic Ocean floor. Her work led to the acceptance of the theories of plate tectonics and continental drift. https://en.wikipedia.org/wiki/Marie_Tharp
"tharp",
// Ken Thompson - co-creator of UNIX and the C programming language - https://en.wikipedia.org/wiki/Ken_Thompson
"thompson",
// Linus Torvalds invented Linux and Git. https://en.wikipedia.org/wiki/Linus_Torvalds
"torvalds",
// Youyou Tu - Chinese pharmaceutical chemist and educator known for discovering artemisinin and dihydroartemisinin, used to treat malaria, which has saved millions of lives. Joint winner of the 2015 Nobel Prize in Physiology or Medicine. https://en.wikipedia.org/wiki/Tu_Youyou
"tu",
// Alan Turing was a founding father of computer science. https://en.wikipedia.org/wiki/Alan_Turing.
"turing",
// Varahamihira - Ancient Indian mathematician who discovered trigonometric formulae during 505-587 CE - https://en.wikipedia.org/wiki/Var%C4%81hamihira#Contributions
"varahamihira",
// Dorothy Vaughan was a NASA mathematician and computer programmer on the SCOUT launch vehicle program that put America's first satellites into space - https://en.wikipedia.org/wiki/Dorothy_Vaughan
"vaughan",
// Sir Mokshagundam Visvesvaraya - is a notable Indian engineer. He is a recipient of the Indian Republic's highest honour, the Bharat Ratna, in 1955. On his birthday, 15 September is celebrated as Engineer's Day in India in his memory - https://en.wikipedia.org/wiki/Visvesvaraya
"visvesvaraya",
// Christiane Nüsslein-Volhard - German biologist, won Nobel Prize in Physiology or Medicine in 1995 for research on the genetic control of embryonic development. https://en.wikipedia.org/wiki/Christiane_N%C3%BCsslein-Volhard
"volhard",
// Cédric Villani - French mathematician, won Fields Medal, Fermat Prize and Poincaré Price for his work in differential geometry and statistical mechanics. https://en.wikipedia.org/wiki/C%C3%A9dric_Villani
"villani",
// Marlyn Wescoff - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Marlyn_Meltzer
"wescoff",
// Sylvia B. Wilbur - British computer scientist who helped develop the ARPANET, was one of the first to exchange email in the UK and a leading researcher in computer-supported collaborative work. https://en.wikipedia.org/wiki/Sylvia_Wilbur
"wilbur",
// Andrew Wiles - Notable British mathematician who proved the enigmatic Fermat's Last Theorem - https://en.wikipedia.org/wiki/Andrew_Wiles
"wiles",
// Roberta Williams, did pioneering work in graphical adventure games for personal computers, particularly the King's Quest series. https://en.wikipedia.org/wiki/Roberta_Williams
"williams",
// Malcolm John Williamson - British mathematician and cryptographer employed by the GCHQ. Developed in 1974 what is now known as Diffie-Hellman key exchange (Diffie and Hellman first published the scheme in 1976). https://en.wikipedia.org/wiki/Malcolm_J._Williamson
"williamson",
// Sophie Wilson designed the first Acorn Micro-Computer and the instruction set for ARM processors. https://en.wikipedia.org/wiki/Sophie_Wilson
"wilson",
// Jeannette Wing - co-developed the Liskov substitution principle. - https://en.wikipedia.org/wiki/Jeannette_Wing
"wing",
// Steve Wozniak invented the Apple I and Apple II. https://en.wikipedia.org/wiki/Steve_Wozniak
"wozniak",
// The Wright brothers, Orville and Wilbur - credited with inventing and building the world's first successful airplane and making the first controlled, powered and sustained heavier-than-air human flight - https://en.wikipedia.org/wiki/Wright_brothers
"wright",
// Chien-Shiung Wu - Chinese-American experimental physicist who made significant contributions to nuclear physics. https://en.wikipedia.org/wiki/Chien-Shiung_Wu
"wu",
// Rosalyn Sussman Yalow - Rosalyn Sussman Yalow was an American medical physicist, and a co-winner of the 1977 Nobel Prize in Physiology or Medicine for development of the radioimmunoassay technique. https://en.wikipedia.org/wiki/Rosalyn_Sussman_Yalow
"yalow",
// Ada Yonath - an Israeli crystallographer, the first woman from the Middle East to win a Nobel prize in the sciences. https://en.wikipedia.org/wiki/Ada_Yonath
"yonath",
// Nikolay Yegorovich Zhukovsky (Russian: Никола́й Его́рович Жуко́вский, January 17 1847 March 17, 1921) was a Russian scientist, mathematician and engineer, and a founding father of modern aero- and hydrodynamics. Whereas contemporary scientists scoffed at the idea of human flight, Zhukovsky was the first to undertake the study of airflow. He is often called the Father of Russian Aviation. https://en.wikipedia.org/wiki/Nikolay_Yegorovich_Zhukovsky
"zhukovsky",
}
)
// GetRandomName generates a random name from the list of adjectives and surnames in this package
// formatted as "scw-adjective-surname". For example 'scw-focused-turing'.
func GetRandomName(prefixes ...string) string {
begin:
parts := append(prefixes, left[r.Intn(len(left))], right[r.Intn(len(right))])
name := strings.Join(parts, "-")
if strings.Contains(name, "boring-wozniak") /* Steve Wozniak is not boring */ {
goto begin
}
return name
}

View File

@@ -8,32 +8,38 @@ Recommended config file:
# get your credentials on https://console.scaleway.com/account/credentials
access_key: SCWXXXXXXXXXXXXXXXXX
secret_key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
default_project_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
default_organization_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
default_region: fr-par
default_zone: fr-par-1
```
## Config file path
This package will try to locate the config file in the following ways:
The function [`GetConfigPath`](https://godoc.org/github.com/scaleway/scaleway-sdk-go/scw#GetConfigPath) will try to locate the config file in the following ways:
1. Custom directory: `$SCW_CONFIG_PATH`
2. [XDG base directory](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html): `$XDG_CONFIG_HOME/scw/config.yaml`
3. Home directory: `$HOME/.config/scw/config.yaml` (`%USERPROFILE%/.config/scw/config.yaml` on windows)
3. Unix home directory: `$HOME/.config/scw/config.yaml`
3. Windows home directory: `%USERPROFILE%/.config/scw/config.yaml`
## V1 config (DEPRECATED)
The V1 config `.scwrc` is supported but deprecated.
When found in the home directory, the V1 config is automatically migrated to a V2 config file in `$HOME/.config/scw/config.yaml`.
The V1 config (AKA legacy config) `.scwrc` is deprecated.
To migrate the V1 config to the new format use the function [`MigrateLegacyConfig`](https://godoc.org/github.com/scaleway/scaleway-sdk-go/scw#MigrateLegacyConfig), this will create a [proper config file](#tl-dr) the new [config file path](#config-file-path).
## Reading config order
When getting the value of a config field, the following priority order will be respected:
[ClientOption](https://godoc.org/github.com/scaleway/scaleway-sdk-go/scw#ClientOption) ordering will decide the order in which the config should apply:
1. Environment variable
2. Legacy environment variable
3. Config file V2
4. Config file V1
```go
p, _ := scw.MustLoadConfig().GetActiveProfile()
scw.NewClient(
scw.WithProfile(p), // active profile applies first
scw.WithEnv(), // existing env variables may overwrite active profile
scw.WithDefaultRegion(scw.RegionFrPar) // any prior region set will be discarded to usr the new one
)
```
## Environment variables
@@ -41,8 +47,9 @@ When getting the value of a config field, the following priority order will be r
| :------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ |
| `$SCW_ACCESS_KEY` | Access key of a token ([get yours](https://console.scaleway.com/account/credentials)) | `$SCALEWAY_ACCESS_KEY` (used by terraform) |
| `$SCW_SECRET_KEY` | Secret key of a token ([get yours](https://console.scaleway.com/account/credentials)) | `$SCW_TOKEN` (used by cli), `$SCALEWAY_TOKEN` (used by terraform), `$SCALEWAY_ACCESS_KEY` (used by terraform) |
| `$SCW_DEFAULT_PROJECT_ID` | Your default project ID, if you don't have one use your organization ID ([get yours](https://console.scaleway.com/account/credentials)) | `$SCW_ORGANIZATION` (used by cli),`$SCALEWAY_ORGANIZATION` (used by terraform) |
| `$SCW_DEFAULT_ORGANIZATION_ID` | Your default organization ID, if you don't have one use your organization ID ([get yours](https://console.scaleway.com/account/credentials)) | `$SCW_ORGANIZATION` (used by cli),`$SCALEWAY_ORGANIZATION` (used by terraform) |
| `$SCW_DEFAULT_REGION` | Your default [region](https://developers.scaleway.com/en/quickstart/#region-and-zone) | `$SCW_REGION` (used by cli),`$SCALEWAY_REGION` (used by terraform) |
| `$SCW_DEFAULT_ZONE` | Your default [availability zone](https://developers.scaleway.com/en/quickstart/#region-and-zone) | `$SCW_ZONE` (used by cli),`$SCALEWAY_ZONE` (used by terraform) |
| `$SCW_API_URL` | Url of the API | - |
| `$SCW_INSECURE` | Set this to `true` to enable the insecure mode | `$SCW_TLSVERIFY` (inverse flag used by the cli) |
| `$SCW_PROFILE` | Set the config profile to use | - |

View File

@@ -3,9 +3,12 @@ package scw
import (
"crypto/tls"
"encoding/json"
"io"
"math"
"net"
"net/http"
"net/http/httputil"
"reflect"
"strconv"
"sync/atomic"
"time"
@@ -13,7 +16,6 @@ import (
"github.com/scaleway/scaleway-sdk-go/internal/auth"
"github.com/scaleway/scaleway-sdk-go/internal/errors"
"github.com/scaleway/scaleway-sdk-go/logger"
"github.com/scaleway/scaleway-sdk-go/utils"
)
// Client is the Scaleway client which performs API requests.
@@ -21,18 +23,19 @@ import (
// This client should be passed in the `NewApi` functions whenever an API instance is created.
// Creating a Client is done with the `NewClient` function.
type Client struct {
httpClient httpClient
auth auth.Auth
apiURL string
userAgent string
defaultProjectID *string
defaultRegion *utils.Region
defaultZone *utils.Zone
defaultPageSize *int32
httpClient httpClient
auth auth.Auth
apiURL string
userAgent string
defaultOrganizationID *string
defaultRegion *Region
defaultZone *Zone
defaultPageSize *uint32
}
func defaultOptions() []ClientOption {
return []ClientOption{
WithoutAuth(),
WithAPIURL("https://api.scaleway.com"),
withDefaultUserAgent(userAgent),
}
@@ -68,51 +71,51 @@ func NewClient(opts ...ClientOption) (*Client, error) {
logger.Debugf("client: using sdk version " + version)
return &Client{
auth: s.token,
httpClient: s.httpClient,
apiURL: s.apiURL,
userAgent: s.userAgent,
defaultProjectID: s.defaultProjectID,
defaultRegion: s.defaultRegion,
defaultZone: s.defaultZone,
defaultPageSize: s.defaultPageSize,
auth: s.token,
httpClient: s.httpClient,
apiURL: s.apiURL,
userAgent: s.userAgent,
defaultOrganizationID: s.defaultOrganizationID,
defaultRegion: s.defaultRegion,
defaultZone: s.defaultZone,
defaultPageSize: s.defaultPageSize,
}, nil
}
// GetDefaultProjectID return the default project ID
// GetDefaultOrganizationID returns the default organization ID
// of the client. This value can be set in the client option
// WithDefaultProjectID(). Be aware this value can be empty.
func (c *Client) GetDefaultProjectID() (string, bool) {
if c.defaultProjectID != nil {
return *c.defaultProjectID, true
// WithDefaultOrganizationID(). Be aware this value can be empty.
func (c *Client) GetDefaultOrganizationID() (organizationID string, exists bool) {
if c.defaultOrganizationID != nil {
return *c.defaultOrganizationID, true
}
return "", false
}
// GetDefaultRegion return the default region of the client.
// GetDefaultRegion returns the default region of the client.
// This value can be set in the client option
// WithDefaultRegion(). Be aware this value can be empty.
func (c *Client) GetDefaultRegion() (utils.Region, bool) {
func (c *Client) GetDefaultRegion() (region Region, exists bool) {
if c.defaultRegion != nil {
return *c.defaultRegion, true
}
return utils.Region(""), false
return Region(""), false
}
// GetDefaultZone return the default zone of the client.
// GetDefaultZone returns the default zone of the client.
// This value can be set in the client option
// WithDefaultZone(). Be aware this value can be empty.
func (c *Client) GetDefaultZone() (utils.Zone, bool) {
func (c *Client) GetDefaultZone() (zone Zone, exists bool) {
if c.defaultZone != nil {
return *c.defaultZone, true
}
return utils.Zone(""), false
return Zone(""), false
}
// GetDefaultPageSize return the default page size of the client.
// GetDefaultPageSize returns the default page size of the client.
// This value can be set in the client option
// WithDefaultPageSize(). Be aware this value can be empty.
func (c *Client) GetDefaultPageSize() (int32, bool) {
func (c *Client) GetDefaultPageSize() (pageSize uint32, exists bool) {
if c.defaultPageSize != nil {
return *c.defaultPageSize, true
}
@@ -122,22 +125,20 @@ func (c *Client) GetDefaultPageSize() (int32, bool) {
// Do performs HTTP request(s) based on the ScalewayRequest object.
// RequestOptions are applied prior to doing the request.
func (c *Client) Do(req *ScalewayRequest, res interface{}, opts ...RequestOption) (err error) {
requestSettings := newRequestSettings()
// apply request options
requestSettings.apply(opts)
req.apply(opts)
// validate request options
err = requestSettings.validate()
err = req.validate()
if err != nil {
return err
}
if requestSettings.ctx != nil {
req.Ctx = requestSettings.ctx
if req.auth == nil {
req.auth = c.auth
}
if requestSettings.allPages {
if req.allPages {
return c.doListAll(req, res)
}
@@ -149,8 +150,7 @@ func (c *Client) Do(req *ScalewayRequest, res interface{}, opts ...RequestOption
var requestNumber uint32
// do performs a single HTTP request based on the ScalewayRequest object.
func (c *Client) do(req *ScalewayRequest, res interface{}) (sdkErr SdkError) {
func (c *Client) do(req *ScalewayRequest, res interface{}) (sdkErr error) {
currentRequestNumber := atomic.AddUint32(&requestNumber, 1)
if req == nil {
@@ -170,19 +170,18 @@ func (c *Client) do(req *ScalewayRequest, res interface{}) (sdkErr SdkError) {
return errors.Wrap(err, "could not create request")
}
httpRequest.Header = req.getAllHeaders(c.auth, c.userAgent, false)
httpRequest.Header = req.getAllHeaders(req.auth, c.userAgent, false)
if req.Ctx != nil {
httpRequest = httpRequest.WithContext(req.Ctx)
if req.ctx != nil {
httpRequest = httpRequest.WithContext(req.ctx)
}
if logger.ShouldLog(logger.LogLevelDebug) {
// Keep original headers (before anonymization)
originalHeaders := httpRequest.Header
// Get anonymized headers
httpRequest.Header = req.getAllHeaders(c.auth, c.userAgent, true)
httpRequest.Header = req.getAllHeaders(req.auth, c.userAgent, true)
dump, err := httputil.DumpRequestOut(httpRequest, true)
if err != nil {
@@ -232,9 +231,24 @@ func (c *Client) do(req *ScalewayRequest, res interface{}) (sdkErr SdkError) {
}
if res != nil {
err = json.NewDecoder(httpResponse.Body).Decode(&res)
if err != nil {
return errors.Wrap(err, "could not parse response body")
contentType := httpResponse.Header.Get("Content-Type")
switch contentType {
case "application/json":
err = json.NewDecoder(httpResponse.Body).Decode(&res)
if err != nil {
return errors.Wrap(err, "could not parse %s response body", contentType)
}
default:
buffer, isBuffer := res.(io.Writer)
if !isBuffer {
return errors.Wrap(err, "could not handle %s response body with %T result type", contentType, buffer)
}
_, err := io.Copy(buffer, httpResponse.Body)
if err != nil {
return errors.Wrap(err, "could not copy %s response body", contentType)
}
}
// Handle instance API X-Total-Count header
@@ -246,12 +260,66 @@ func (c *Client) do(req *ScalewayRequest, res interface{}) (sdkErr SdkError) {
}
legacyLister.UnsafeSetTotalCount(xTotalCount)
}
}
return nil
}
type lister interface {
UnsafeGetTotalCount() uint32
UnsafeAppend(interface{}) (uint32, error)
}
type legacyLister interface {
UnsafeSetTotalCount(totalCount int)
}
const maxPageCount uint32 = math.MaxUint32
// doListAll collects all pages of a List request and aggregate all results on a single response.
func (c *Client) doListAll(req *ScalewayRequest, res interface{}) (err error) {
// check for lister interface
if response, isLister := res.(lister); isLister {
pageCount := maxPageCount
for page := uint32(1); page <= pageCount; page++ {
// set current page
req.Query.Set("page", strconv.FormatUint(uint64(page), 10))
// request the next page
nextPage := newVariableFromType(response)
err := c.do(req, nextPage)
if err != nil {
return err
}
// append results
pageSize, err := response.UnsafeAppend(nextPage)
if err != nil {
return err
}
if pageSize == 0 {
return nil
}
// set total count on first request
if pageCount == maxPageCount {
totalCount := nextPage.(lister).UnsafeGetTotalCount()
pageCount = (totalCount + pageSize - 1) / pageSize
}
}
return nil
}
return errors.New("%T does not support pagination", res)
}
// newVariableFromType returns a variable set to the zero value of the given type
func newVariableFromType(t interface{}) interface{} {
// reflect.New always create a pointer, that's why we use reflect.Indirect before
return reflect.New(reflect.Indirect(reflect.ValueOf(t)).Type()).Interface()
}
func newHTTPClient() *http.Client {
return &http.Client{
Timeout: 30 * time.Second,
@@ -280,25 +348,3 @@ func setInsecureMode(c httpClient) {
}
transportClient.TLSClientConfig.InsecureSkipVerify = true
}
func hasResponseError(res *http.Response) SdkError {
if res.StatusCode >= 200 && res.StatusCode <= 299 {
return nil
}
newErr := &ResponseError{
StatusCode: res.StatusCode,
Status: res.Status,
}
if res.Body == nil {
return newErr
}
err := json.NewDecoder(res.Body).Decode(newErr)
if err != nil {
return errors.Wrap(err, "could not parse error response body")
}
return newErr
}

View File

@@ -2,10 +2,11 @@ package scw
import (
"net/http"
"strings"
"github.com/scaleway/scaleway-sdk-go/internal/auth"
"github.com/scaleway/scaleway-sdk-go/scwconfig"
"github.com/scaleway/scaleway-sdk-go/utils"
"github.com/scaleway/scaleway-sdk-go/internal/errors"
"github.com/scaleway/scaleway-sdk-go/validation"
)
// ClientOption is a function which applies options to a settings object.
@@ -68,56 +69,61 @@ func withDefaultUserAgent(ua string) ClientOption {
}
}
// WithConfig client option configure a client with Scaleway configuration.
func WithConfig(config scwconfig.Config) ClientOption {
// WithProfile client option configures a client from the given profile.
func WithProfile(p *Profile) ClientOption {
return func(s *settings) {
// The access key is not used for API authentications.
accessKey, _ := config.GetAccessKey()
secretKey, secretKeyExist := config.GetSecretKey()
if secretKeyExist {
s.token = auth.NewToken(accessKey, secretKey)
accessKey := ""
if p.AccessKey != nil {
accessKey = *p.AccessKey
}
apiURL, exist := config.GetAPIURL()
if exist {
s.apiURL = apiURL
if p.SecretKey != nil {
s.token = auth.NewToken(accessKey, *p.SecretKey)
}
insecure, exist := config.GetInsecure()
if exist {
s.insecure = insecure
if p.APIURL != nil {
s.apiURL = *p.APIURL
}
defaultProjectID, exist := config.GetDefaultProjectID()
if exist {
s.defaultProjectID = &defaultProjectID
if p.Insecure != nil {
s.insecure = *p.Insecure
}
defaultRegion, exist := config.GetDefaultRegion()
if exist {
if p.DefaultOrganizationID != nil {
organizationID := *p.DefaultOrganizationID
s.defaultOrganizationID = &organizationID
}
if p.DefaultRegion != nil {
defaultRegion := Region(*p.DefaultRegion)
s.defaultRegion = &defaultRegion
}
defaultZone, exist := config.GetDefaultZone()
if exist {
if p.DefaultZone != nil {
defaultZone := Zone(*p.DefaultZone)
s.defaultZone = &defaultZone
}
}
}
// WithDefaultProjectID client option sets the client default project ID.
// WithProfile client option configures a client from the environment variables.
func WithEnv() ClientOption {
return WithProfile(LoadEnvProfile())
}
// WithDefaultOrganizationID client option sets the client default organization ID.
//
// It will be used as the default value of the project_id field in all requests made with this client.
func WithDefaultProjectID(projectID string) ClientOption {
// It will be used as the default value of the organization_id field in all requests made with this client.
func WithDefaultOrganizationID(organizationID string) ClientOption {
return func(s *settings) {
s.defaultProjectID = &projectID
s.defaultOrganizationID = &organizationID
}
}
// WithDefaultRegion client option sets the client default region.
//
// It will be used as the default value of the region field in all requests made with this client.
func WithDefaultRegion(region utils.Region) ClientOption {
func WithDefaultRegion(region Region) ClientOption {
return func(s *settings) {
s.defaultRegion = &region
}
@@ -126,7 +132,7 @@ func WithDefaultRegion(region utils.Region) ClientOption {
// WithDefaultZone client option sets the client default zone.
//
// It will be used as the default value of the zone field in all requests made with this client.
func WithDefaultZone(zone utils.Zone) ClientOption {
func WithDefaultZone(zone Zone) ClientOption {
return func(s *settings) {
s.defaultZone = &zone
}
@@ -135,8 +141,100 @@ func WithDefaultZone(zone utils.Zone) ClientOption {
// WithDefaultPageSize client option overrides the default page size of the SDK.
//
// It will be used as the default value of the page_size field in all requests made with this client.
func WithDefaultPageSize(pageSize int32) ClientOption {
func WithDefaultPageSize(pageSize uint32) ClientOption {
return func(s *settings) {
s.defaultPageSize = &pageSize
}
}
// settings hold the values of all client options
type settings struct {
apiURL string
token auth.Auth
userAgent string
httpClient httpClient
insecure bool
defaultOrganizationID *string
defaultRegion *Region
defaultZone *Zone
defaultPageSize *uint32
}
func newSettings() *settings {
return &settings{}
}
func (s *settings) apply(opts []ClientOption) {
for _, opt := range opts {
opt(s)
}
}
func (s *settings) validate() error {
// Auth.
if s.token == nil {
// It should not happen, WithoutAuth option is used by default.
panic(errors.New("no credential option provided"))
}
if token, isToken := s.token.(*auth.Token); isToken {
if token.AccessKey == "" {
return NewInvalidClientOptionError("access key cannot be empty")
}
if !validation.IsAccessKey(token.AccessKey) {
return NewInvalidClientOptionError("invalid access key format '%s', expected SCWXXXXXXXXXXXXXXXXX format", token.AccessKey)
}
if token.SecretKey == "" {
return NewInvalidClientOptionError("secret key cannot be empty")
}
if !validation.IsSecretKey(token.SecretKey) {
return NewInvalidClientOptionError("invalid secret key format '%s', expected a UUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", token.SecretKey)
}
}
// Default Organization ID.
if s.defaultOrganizationID != nil {
if *s.defaultOrganizationID == "" {
return NewInvalidClientOptionError("default organization ID cannot be empty")
}
if !validation.IsOrganizationID(*s.defaultOrganizationID) {
return NewInvalidClientOptionError("invalid organization ID format '%s', expected a UUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", *s.defaultOrganizationID)
}
}
// Default Region.
if s.defaultRegion != nil {
if *s.defaultRegion == "" {
return NewInvalidClientOptionError("default region cannot be empty")
}
if !validation.IsRegion(string(*s.defaultRegion)) {
regions := []string(nil)
for _, r := range AllRegions {
regions = append(regions, string(r))
}
return NewInvalidClientOptionError("invalid default region format '%s', available regions are: %s", *s.defaultRegion, strings.Join(regions, ", "))
}
}
// Default Zone.
if s.defaultZone != nil {
if *s.defaultZone == "" {
return NewInvalidClientOptionError("default zone cannot be empty")
}
if !validation.IsZone(string(*s.defaultZone)) {
zones := []string(nil)
for _, z := range AllZones {
zones = append(zones, string(z))
}
return NewInvalidClientOptionError("invalid default zone format '%s', available zones are: %s", *s.defaultZone, strings.Join(zones, ", "))
}
}
// API URL.
if !validation.IsURL(s.apiURL) {
return NewInvalidClientOptionError("invalid url %s", s.apiURL)
}
// TODO: check for max s.defaultPageSize
return nil
}

View File

@@ -0,0 +1,318 @@
package scw
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"syscall"
"text/template"
"github.com/scaleway/scaleway-sdk-go/internal/auth"
"github.com/scaleway/scaleway-sdk-go/internal/errors"
"github.com/scaleway/scaleway-sdk-go/logger"
"gopkg.in/yaml.v2"
)
const (
documentationLink = "https://github.com/scaleway/scaleway-sdk-go/blob/master/scw/README.md"
defaultConfigPermission = 0600
)
const configFileTemplate = `# Scaleway configuration file
# https://github.com/scaleway/scaleway-sdk-go/tree/master/scw#scaleway-config
# This configuration file can be used with:
# - Scaleway SDK Go (https://github.com/scaleway/scaleway-sdk-go)
# - Scaleway CLI (>2.0.0) (https://github.com/scaleway/scaleway-cli)
# - Scaleway Terraform Provider (https://www.terraform.io/docs/providers/scaleway/index.html)
# You need an access key and a secret key to connect to Scaleway API.
# Generate your token at the following address: https://console.scaleway.com/account/credentials
# An access key is a secret key identifier.
{{ if .AccessKey }}access_key: {{.AccessKey}}{{ else }}# access_key: SCW11111111111111111{{ end }}
# The secret key is the value that can be used to authenticate against the API (the value used in X-Auth-Token HTTP-header).
# The secret key MUST remain secret and not given to anyone or published online.
{{ if .SecretKey }}secret_key: {{ .SecretKey }}{{ else }}# secret_key: 11111111-1111-1111-1111-111111111111{{ end }}
# Your organization ID is the identifier of your account inside Scaleway infrastructure.
{{ if .DefaultOrganizationID }}default_organization_id: {{ .DefaultOrganizationID }}{{ else }}# default_organization_id: 11111111-1111-1111-1111-111111111111{{ end }}
# A region is represented as a geographical area such as France (Paris) or the Netherlands (Amsterdam).
# It can contain multiple availability zones.
# Example of region: fr-par, nl-ams
{{ if .DefaultRegion }}default_region: {{ .DefaultRegion }}{{ else }}# default_region: fr-par{{ end }}
# A region can be split into many availability zones (AZ).
# Latency between multiple AZ of the same region are low as they have a common network layer.
# Example of zones: fr-par-1, nl-ams-1
{{ if .DefaultZone }}default_zone: {{.DefaultZone}}{{ else }}# default_zone: fr-par-1{{ end }}
# APIURL overrides the API URL of the Scaleway API to the given URL.
# Change that if you want to direct requests to a different endpoint.
{{ if .APIURL }}apiurl: {{ .APIURL }}{{ else }}# api_url: https://api.scaleway.com{{ end }}
# Insecure enables insecure transport on the client.
# Default to false
{{ if .Insecure }}insecure: {{ .Insecure }}{{ else }}# insecure: false{{ end }}
# A configuration is a named set of Scaleway properties.
# Starting off with a Scaleway SDK or Scaleway CLI, youll work with a single configuration named default.
# You can set properties of the default profile by running either scw init or scw config set.
# This single default configuration is suitable for most use cases.
{{ if .ActiveProfile }}active_profile: {{ .ActiveProfile }}{{ else }}# active_profile: myProfile{{ end }}
# To improve the Scaleway CLI we rely on diagnostic and usage data.
# Sending such data is optional and can be disable at any time by setting send_telemetry variable to false.
{{ if .SendTelemetry }}send_telemetry: {{ .SendTelemetry }}{{ else }}# send_telemetry: false{{ end }}
# To work with multiple projects or authorization accounts, you can set up multiple configurations with scw config configurations create and switch among them accordingly.
# You can use a profile by either:
# - Define the profile you want to use as the SCW_PROFILE environment variable
# - Use the GetActiveProfile() function in the SDK
# - Use the --profile flag with the CLI
# You can define a profile using the following syntax:
{{ if gt (len .Profiles) 0 }}
profiles:
{{- range $k,$v := .Profiles }}
{{ $k }}:
{{ if $v.AccessKey }}access_key: {{ $v.AccessKey }}{{ else }}# access_key: SCW11111111111111111{{ end }}
{{ if $v.SecretKey }}secret_key: {{ $v.SecretKey }}{{ else }}# secret_key: 11111111-1111-1111-1111-111111111111{{ end }}
{{ if $v.DefaultOrganizationID }}default_organization_id: {{ $v.DefaultOrganizationID }}{{ else }}# default_organization_id: 11111111-1111-1111-1111-111111111111{{ end }}
{{ if $v.DefaultZone }}default_zone: {{ $v.DefaultZone }}{{ else }}# default_zone: fr-par-1{{ end }}
{{ if $v.DefaultRegion }}default_region: {{ $v.DefaultRegion }}{{ else }}# default_region: fr-par{{ end }}
{{ if $v.APIURL }}api_url: {{ $v.APIURL }}{{ else }}# api_url: https://api.scaleway.com{{ end }}
{{ if $v.Insecure }}insecure: {{ $v.Insecure }}{{ else }}# insecure: false{{ end }}
{{ end }}
{{- else }}
# profiles:
# myProfile:
# access_key: 11111111-1111-1111-1111-111111111111
# secret_key: 11111111-1111-1111-1111-111111111111
# organization_id: 11111111-1111-1111-1111-111111111111
# default_zone: fr-par-1
# default_region: fr-par
# api_url: https://api.scaleway.com
# insecure: false
{{ end -}}
`
type Config struct {
Profile `yaml:",inline"`
ActiveProfile *string `yaml:"active_profile,omitempty"`
SendTelemetry bool `yaml:"send_telemetry,omitempty"`
Profiles map[string]*Profile `yaml:"profiles,omitempty"`
}
type Profile struct {
AccessKey *string `yaml:"access_key,omitempty"`
SecretKey *string `yaml:"secret_key,omitempty"`
APIURL *string `yaml:"api_url,omitempty"`
Insecure *bool `yaml:"insecure,omitempty"`
DefaultOrganizationID *string `yaml:"default_organization_id,omitempty"`
DefaultRegion *string `yaml:"default_region,omitempty"`
DefaultZone *string `yaml:"default_zone,omitempty"`
}
func (p *Profile) String() string {
p2 := *p
p2.SecretKey = hideSecretKey(p2.SecretKey)
configRaw, _ := yaml.Marshal(p2)
return string(configRaw)
}
// clone deep copy config object
func (c *Config) clone() *Config {
c2 := &Config{}
configRaw, _ := yaml.Marshal(c)
_ = yaml.Unmarshal(configRaw, c2)
return c2
}
func (c *Config) String() string {
c2 := c.clone()
c2.SecretKey = hideSecretKey(c2.SecretKey)
for _, p := range c2.Profiles {
p.SecretKey = hideSecretKey(p.SecretKey)
}
configRaw, _ := yaml.Marshal(c2)
return string(configRaw)
}
func hideSecretKey(key *string) *string {
if key == nil {
return nil
}
newKey := auth.HideSecretKey(*key)
return &newKey
}
func unmarshalConfV2(content []byte) (*Config, error) {
var config Config
err := yaml.Unmarshal(content, &config)
if err != nil {
return nil, err
}
return &config, nil
}
// MustLoadConfig is like LoadConfig but panic instead of returning an error.
func MustLoadConfig() *Config {
c, err := LoadConfigFromPath(GetConfigPath())
if err != nil {
panic(err)
}
return c
}
// LoadConfig read the config from the default path.
func LoadConfig() (*Config, error) {
return LoadConfigFromPath(GetConfigPath())
}
// LoadConfigFromPath read the config from the given path.
func LoadConfigFromPath(path string) (*Config, error) {
file, err := ioutil.ReadFile(path)
if err != nil {
if pathError, isPathError := err.(*os.PathError); isPathError && pathError.Err == syscall.ENOENT {
return nil, configFileNotFound(pathError.Path)
}
return nil, errors.Wrap(err, "cannot read config file")
}
_, err = unmarshalConfV1(file)
if err == nil {
// reject V1 config
return nil, errors.New("found legacy config in %s: legacy config is not allowed, please switch to the new config file format: %s", path, documentationLink)
}
confV2, err := unmarshalConfV2(file)
if err != nil {
return nil, errors.Wrap(err, "content of config file %s is invalid", path)
}
return confV2, nil
}
// GetProfile returns the profile corresponding to the given profile name.
func (c *Config) GetProfile(profileName string) (*Profile, error) {
if profileName == "" {
return nil, errors.New("active profile cannot be empty")
}
p, exist := c.Profiles[profileName]
if !exist {
return nil, errors.New("given profile %s does not exist", profileName)
}
// Merge selected profile on top of default profile
return MergeProfiles(&c.Profile, p), nil
}
// GetActiveProfile returns the active profile of the config based on the following order:
// env SCW_PROFILE > config active_profile > config root profile
func (c *Config) GetActiveProfile() (*Profile, error) {
switch {
case os.Getenv(scwActiveProfileEnv) != "":
logger.Debugf("using active profile from env: %s=%s", scwActiveProfileEnv, os.Getenv(scwActiveProfileEnv))
return c.GetProfile(os.Getenv(scwActiveProfileEnv))
case c.ActiveProfile != nil:
logger.Debugf("using active profile from config: active_profile=%s", scwActiveProfileEnv, *c.ActiveProfile)
return c.GetProfile(*c.ActiveProfile)
default:
return &c.Profile, nil
}
}
// SaveTo will save the config to the default config path. This
// action will overwrite the previous file when it exists.
func (c *Config) Save() error {
return c.SaveTo(GetConfigPath())
}
// HumanConfig will generate a config file with documented arguments.
func (c *Config) HumanConfig() (string, error) {
tmpl, err := template.New("configuration").Parse(configFileTemplate)
if err != nil {
return "", err
}
var buf bytes.Buffer
err = tmpl.Execute(&buf, c)
if err != nil {
return "", err
}
return buf.String(), nil
}
// SaveTo will save the config to the given path. This action will
// overwrite the previous file when it exists.
func (c *Config) SaveTo(path string) error {
path = filepath.Clean(path)
// STEP 1: Render the configuration file as a file
file, err := c.HumanConfig()
if err != nil {
return err
}
// STEP 2: create config path dir in cases it didn't exist before
err = os.MkdirAll(filepath.Dir(path), 0700)
if err != nil {
return err
}
// STEP 3: write new config file
err = ioutil.WriteFile(path, []byte(file), defaultConfigPermission)
if err != nil {
return err
}
return nil
}
// MergeProfiles merges profiles in a new one. The last profile has priority.
func MergeProfiles(original *Profile, others ...*Profile) *Profile {
np := &Profile{
AccessKey: original.AccessKey,
SecretKey: original.SecretKey,
APIURL: original.APIURL,
Insecure: original.Insecure,
DefaultOrganizationID: original.DefaultOrganizationID,
DefaultRegion: original.DefaultRegion,
DefaultZone: original.DefaultZone,
}
for _, other := range others {
if other.AccessKey != nil {
np.AccessKey = other.AccessKey
}
if other.SecretKey != nil {
np.SecretKey = other.SecretKey
}
if other.APIURL != nil {
np.APIURL = other.APIURL
}
if other.Insecure != nil {
np.Insecure = other.Insecure
}
if other.DefaultOrganizationID != nil {
np.DefaultOrganizationID = other.DefaultOrganizationID
}
if other.DefaultRegion != nil {
np.DefaultRegion = other.DefaultRegion
}
if other.DefaultZone != nil {
np.DefaultZone = other.DefaultZone
}
}
return np
}

View File

@@ -0,0 +1,91 @@
package scw
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"github.com/scaleway/scaleway-sdk-go/internal/errors"
"github.com/scaleway/scaleway-sdk-go/logger"
"gopkg.in/yaml.v2"
)
// configV1 is a Scaleway CLI configuration file
type configV1 struct {
// Organization is the identifier of the Scaleway organization
Organization string `json:"organization"`
// Token is the authentication token for the Scaleway organization
Token string `json:"token"`
// Version is the actual version of scw CLI
Version string `json:"version"`
}
func unmarshalConfV1(content []byte) (*configV1, error) {
var config configV1
err := json.Unmarshal(content, &config)
if err != nil {
return nil, err
}
return &config, err
}
func (v1 *configV1) toV2() *Config {
return &Config{
Profile: Profile{
DefaultOrganizationID: &v1.Organization,
SecretKey: &v1.Token,
// ignore v1 version
},
}
}
// MigrateLegacyConfig will migrate the legacy config to the V2 when none exist yet.
// Returns a boolean set to true when the migration happened.
// TODO: get accesskey from account?
func MigrateLegacyConfig() (bool, error) {
// STEP 1: try to load config file V2
v2Path, v2PathOk := getConfigV2FilePath()
if !v2PathOk || fileExist(v2Path) {
return false, nil
}
// STEP 2: try to load config file V1
v1Path, v1PathOk := getConfigV1FilePath()
if !v1PathOk {
return false, nil
}
file, err := ioutil.ReadFile(v1Path)
if err != nil {
return false, nil
}
confV1, err := unmarshalConfV1(file)
if err != nil {
return false, errors.Wrap(err, "content of config file %s is invalid json", v1Path)
}
// STEP 3: create dir
err = os.MkdirAll(filepath.Dir(v2Path), 0700)
if err != nil {
return false, errors.Wrap(err, "mkdir did not work on %s", filepath.Dir(v2Path))
}
// STEP 4: marshal yaml config
newConfig := confV1.toV2()
file, err = yaml.Marshal(newConfig)
if err != nil {
return false, err
}
// STEP 5: save config
err = ioutil.WriteFile(v2Path, file, defaultConfigPermission)
if err != nil {
return false, errors.Wrap(err, "cannot write file %s", v2Path)
}
// STEP 6: log success
logger.Warningf("migrated existing config to %s", v2Path)
return true, nil
}

View File

@@ -0,0 +1,171 @@
package scw
import (
"net"
"time"
)
// StringPtr returns a pointer to the string value passed in.
func StringPtr(v string) *string {
return &v
}
// StringSlicePtr converts a slice of string values into a slice of
// string pointers
func StringSlicePtr(src []string) []*string {
dst := make([]*string, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// StringsPtr returns a pointer to the []string value passed in.
func StringsPtr(v []string) *[]string {
return &v
}
// StringsSlicePtr converts a slice of []string values into a slice of
// []string pointers
func StringsSlicePtr(src [][]string) []*[]string {
dst := make([]*[]string, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// BytesPtr returns a pointer to the []byte value passed in.
func BytesPtr(v []byte) *[]byte {
return &v
}
// BytesSlicePtr converts a slice of []byte values into a slice of
// []byte pointers
func BytesSlicePtr(src [][]byte) []*[]byte {
dst := make([]*[]byte, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// BoolPtr returns a pointer to the bool value passed in.
func BoolPtr(v bool) *bool {
return &v
}
// BoolSlicePtr converts a slice of bool values into a slice of
// bool pointers
func BoolSlicePtr(src []bool) []*bool {
dst := make([]*bool, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Int32Ptr returns a pointer to the int32 value passed in.
func Int32Ptr(v int32) *int32 {
return &v
}
// Int32SlicePtr converts a slice of int32 values into a slice of
// int32 pointers
func Int32SlicePtr(src []int32) []*int32 {
dst := make([]*int32, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Int64Ptr returns a pointer to the int64 value passed in.
func Int64Ptr(v int64) *int64 {
return &v
}
// Int64SlicePtr converts a slice of int64 values into a slice of
// int64 pointers
func Int64SlicePtr(src []int64) []*int64 {
dst := make([]*int64, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Uint32Ptr returns a pointer to the uint32 value passed in.
func Uint32Ptr(v uint32) *uint32 {
return &v
}
// Uint32SlicePtr converts a slice of uint32 values into a slice of
// uint32 pointers
func Uint32SlicePtr(src []uint32) []*uint32 {
dst := make([]*uint32, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Uint64Ptr returns a pointer to the uint64 value passed in.
func Uint64Ptr(v uint64) *uint64 {
return &v
}
// Uint64SlicePtr converts a slice of uint64 values into a slice of
// uint64 pointers
func Uint64SlicePtr(src []uint64) []*uint64 {
dst := make([]*uint64, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Float32Ptr returns a pointer to the float32 value passed in.
func Float32Ptr(v float32) *float32 {
return &v
}
// Float32SlicePtr converts a slice of float32 values into a slice of
// float32 pointers
func Float32SlicePtr(src []float32) []*float32 {
dst := make([]*float32, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Float64Ptr returns a pointer to the float64 value passed in.
func Float64Ptr(v float64) *float64 {
return &v
}
// Float64SlicePtr converts a slice of float64 values into a slice of
// float64 pointers
func Float64SlicePtr(src []float64) []*float64 {
dst := make([]*float64, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// DurationPtr returns a pointer to the Duration value passed in.
func DurationPtr(v time.Duration) *time.Duration {
return &v
}
// SizePtr returns a pointer to the Size value passed in.
func SizePtr(v Size) *Size {
return &v
}
// IPPtr returns a pointer to the net.IP value passed in.
func IPPtr(v net.IP) *net.IP {
return &v
}

View File

@@ -0,0 +1,339 @@
package scw
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net"
"strconv"
"strings"
"time"
"github.com/scaleway/scaleway-sdk-go/internal/errors"
"github.com/scaleway/scaleway-sdk-go/logger"
)
// ServiceInfo contains API metadata
// These metadata are only here for debugging. Do not rely on these values
type ServiceInfo struct {
// Name is the name of the API
Name string `json:"name"`
// Description is a human readable description for the API
Description string `json:"description"`
// Version is the version of the API
Version string `json:"version"`
// DocumentationURL is the a web url where the documentation of the API can be found
DocumentationURL *string `json:"documentation_url"`
}
// File is the structure used to receive / send a file from / to the API
type File struct {
// Name of the file
Name string `json:"name"`
// ContentType used in the HTTP header `Content-Type`
ContentType string `json:"content_type"`
// Content of the file
Content io.Reader `json:"content"`
}
func (f *File) UnmarshalJSON(b []byte) error {
type file File
var tmpFile struct {
file
Content []byte `json:"content"`
}
err := json.Unmarshal(b, &tmpFile)
if err != nil {
return err
}
tmpFile.file.Content = bytes.NewReader(tmpFile.Content)
*f = File(tmpFile.file)
return nil
}
// Money represents an amount of money with its currency type.
type Money struct {
// CurrencyCode is the 3-letter currency code defined in ISO 4217.
CurrencyCode string `json:"currency_code"`
// Units is the whole units of the amount.
// For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar.
Units int64 `json:"units"`
// Nanos is the number of nano (10^-9) units of the amount.
// The value must be between -999,999,999 and +999,999,999 inclusive.
// If `units` is positive, `nanos` must be positive or zero.
// If `units` is zero, `nanos` can be positive, zero, or negative.
// If `units` is negative, `nanos` must be negative or zero.
// For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000.
Nanos int32 `json:"nanos"`
}
// NewMoneyFromFloat converts a float with currency to a Money.
//
// value: The float value.
// currencyCode: The 3-letter currency code defined in ISO 4217.
// precision: The number of digits after the decimal point used to parse the nanos part of the value.
//
// Examples:
// - (value = 1.3333, precision = 2) => Money{Units = 1, Nanos = 330000000}
// - (value = 1.123456789, precision = 9) => Money{Units = 1, Nanos = 123456789}
func NewMoneyFromFloat(value float64, currencyCode string, precision int) *Money {
if precision > 9 {
panic(fmt.Errorf("max precision is 9"))
}
strValue := strconv.FormatFloat(value, 'f', precision, 64)
units, nanos, err := splitFloatString(strValue)
if err != nil {
panic(err)
}
return &Money{
CurrencyCode: currencyCode,
Units: units,
Nanos: nanos,
}
}
// String returns the string representation of Money.
func (m Money) String() string {
currencySignsByCodes := map[string]string{
"EUR": "€",
"USD": "$",
}
currencySign, currencySignFound := currencySignsByCodes[m.CurrencyCode]
if !currencySignFound {
logger.Debugf("%s currency code is not supported", m.CurrencyCode)
currencySign = m.CurrencyCode
}
cents := fmt.Sprintf("%09d", m.Nanos)
cents = cents[:2] + strings.TrimRight(cents[2:], "0")
return fmt.Sprintf("%s %d.%s", currencySign, m.Units, cents)
}
// ToFloat converts a Money object to a float.
func (m Money) ToFloat() float64 {
return float64(m.Units) + float64(m.Nanos)/1e9
}
// Size represents a size in bytes.
type Size uint64
const (
B Size = 1
KB = 1000 * B
MB = 1000 * KB
GB = 1000 * MB
TB = 1000 * GB
PB = 1000 * TB
)
// String returns the string representation of a Size.
func (s Size) String() string {
return fmt.Sprintf("%d", s)
}
// TimeSeries represents a time series that could be used for graph purposes.
type TimeSeries struct {
// Name of the metric.
Name string `json:"name"`
// Points contains all the points that composed the series.
Points []*TimeSeriesPoint `json:"points"`
// Metadata contains some string metadata related to a metric.
Metadata map[string]string `json:"metadata"`
}
// TimeSeriesPoint represents a point of a time series.
type TimeSeriesPoint struct {
Timestamp time.Time
Value float32
}
func (tsp TimeSeriesPoint) MarshalJSON() ([]byte, error) {
timestamp := tsp.Timestamp.Format(time.RFC3339)
value, err := json.Marshal(tsp.Value)
if err != nil {
return nil, err
}
return []byte(`["` + timestamp + `",` + string(value) + "]"), nil
}
func (tsp *TimeSeriesPoint) UnmarshalJSON(b []byte) error {
point := [2]interface{}{}
err := json.Unmarshal(b, &point)
if err != nil {
return err
}
if len(point) != 2 {
return fmt.Errorf("invalid point array")
}
strTimestamp, isStrTimestamp := point[0].(string)
if !isStrTimestamp {
return fmt.Errorf("%s timestamp is not a string in RFC 3339 format", point[0])
}
timestamp, err := time.Parse(time.RFC3339, strTimestamp)
if err != nil {
return fmt.Errorf("%s timestamp is not in RFC 3339 format", point[0])
}
tsp.Timestamp = timestamp
// By default, JSON unmarshal a float in float64 but the TimeSeriesPoint is a float32 value.
value, isValue := point[1].(float64)
if !isValue {
return fmt.Errorf("%s is not a valid float32 value", point[1])
}
tsp.Value = float32(value)
return nil
}
// IPNet inherits net.IPNet and represents an IP network.
type IPNet struct {
net.IPNet
}
func (n IPNet) MarshalJSON() ([]byte, error) {
value := n.String()
if value == "<nil>" {
value = ""
}
return []byte(`"` + value + `"`), nil
}
func (n *IPNet) UnmarshalJSON(b []byte) error {
var str string
err := json.Unmarshal(b, &str)
if err != nil {
return err
}
if str == "" {
*n = IPNet{}
return nil
}
switch ip := net.ParseIP(str); {
case ip.To4() != nil:
str += "/32"
case ip.To16() != nil:
str += "/128"
}
_, value, err := net.ParseCIDR(str)
if err != nil {
return err
}
n.IPNet = *value
return nil
}
// Duration represents a signed, fixed-length span of time represented as a
// count of seconds and fractions of seconds at nanosecond resolution. It is
// independent of any calendar and concepts like "day" or "month". It is related
// to Timestamp in that the difference between two Timestamp values is a Duration
// and it can be added or subtracted from a Timestamp.
// Range is approximately +-10,000 years.
type Duration struct {
Seconds int64
Nanos int32
}
func (d *Duration) ToTimeDuration() *time.Duration {
if d == nil {
return nil
}
timeDuration := time.Duration(d.Nanos) + time.Duration(d.Seconds/1e9)
return &timeDuration
}
func (d Duration) MarshalJSON() ([]byte, error) {
nanos := d.Nanos
if nanos < 0 {
nanos = -nanos
}
return []byte(`"` + fmt.Sprintf("%d.%09d", d.Seconds, nanos) + `s"`), nil
}
func (d *Duration) UnmarshalJSON(b []byte) error {
if string(b) == "null" {
return nil
}
var str string
err := json.Unmarshal(b, &str)
if err != nil {
return err
}
if str == "" {
*d = Duration{}
return nil
}
seconds, nanos, err := splitFloatString(strings.TrimRight(str, "s"))
if err != nil {
return err
}
*d = Duration{
Seconds: seconds,
Nanos: nanos,
}
return nil
}
// splitFloatString splits a float represented in a string, and returns its units (left-coma part) and nanos (right-coma part).
// E.g.:
// "3" ==> units = 3 | nanos = 0
// "3.14" ==> units = 3 | nanos = 14*1e7
// "-3.14" ==> units = -3 | nanos = -14*1e7
func splitFloatString(input string) (units int64, nanos int32, err error) {
parts := strings.SplitN(input, ".", 2)
// parse units as int64
units, err = strconv.ParseInt(parts[0], 10, 64)
if err != nil {
return 0, 0, errors.Wrap(err, "invalid units")
}
// handle nanos
if len(parts) == 2 {
// add leading zeros
strNanos := parts[1] + "000000000"[len(parts[1]):]
// parse nanos as int32
n, err := strconv.ParseUint(strNanos, 10, 32)
if err != nil {
return 0, 0, errors.Wrap(err, "invalid nanos")
}
nanos = int32(n)
}
if units < 0 {
nanos = -nanos
}
return units, nanos, nil
}

View File

@@ -0,0 +1,137 @@
package scw
import (
"os"
"strconv"
"github.com/scaleway/scaleway-sdk-go/logger"
)
// Environment variables
const (
// Up-to-date
scwCacheDirEnv = "SCW_CACHE_DIR"
scwConfigPathEnv = "SCW_CONFIG_PATH"
scwAccessKeyEnv = "SCW_ACCESS_KEY"
scwSecretKeyEnv = "SCW_SECRET_KEY" // #nosec G101
scwActiveProfileEnv = "SCW_PROFILE"
scwAPIURLEnv = "SCW_API_URL"
scwInsecureEnv = "SCW_INSECURE"
scwDefaultOrganizationIDEnv = "SCW_DEFAULT_ORGANIZATION_ID"
scwDefaultRegionEnv = "SCW_DEFAULT_REGION"
scwDefaultZoneEnv = "SCW_DEFAULT_ZONE"
// All deprecated (cli&terraform)
terraformAccessKeyEnv = "SCALEWAY_ACCESS_KEY" // used both as access key and secret key
terraformSecretKeyEnv = "SCALEWAY_TOKEN"
terraformOrganizationEnv = "SCALEWAY_ORGANIZATION"
terraformRegionEnv = "SCALEWAY_REGION"
cliTLSVerifyEnv = "SCW_TLSVERIFY"
cliOrganizationEnv = "SCW_ORGANIZATION"
cliRegionEnv = "SCW_REGION"
cliSecretKeyEnv = "SCW_TOKEN"
// TBD
//cliVerboseEnv = "SCW_VERBOSE_API"
//cliDebugEnv = "DEBUG"
//cliNoCheckVersionEnv = "SCW_NOCHECKVERSION"
//cliTestWithRealAPIEnv = "TEST_WITH_REAL_API"
//cliSecureExecEnv = "SCW_SECURE_EXEC"
//cliGatewayEnv = "SCW_GATEWAY"
//cliSensitiveEnv = "SCW_SENSITIVE"
//cliAccountAPIEnv = "SCW_ACCOUNT_API"
//cliMetadataAPIEnv = "SCW_METADATA_API"
//cliMarketPlaceAPIEnv = "SCW_MARKETPLACE_API"
//cliComputePar1APIEnv = "SCW_COMPUTE_PAR1_API"
//cliComputeAms1APIEnv = "SCW_COMPUTE_AMS1_API"
//cliCommercialTypeEnv = "SCW_COMMERCIAL_TYPE"
//cliTargetArchEnv = "SCW_TARGET_ARCH"
)
const (
v1RegionFrPar = "par1"
v1RegionNlAms = "ams1"
)
func LoadEnvProfile() *Profile {
p := &Profile{}
accessKey, _, envExist := getEnv(scwAccessKeyEnv, terraformAccessKeyEnv)
if envExist {
p.AccessKey = &accessKey
}
secretKey, _, envExist := getEnv(scwSecretKeyEnv, cliSecretKeyEnv, terraformSecretKeyEnv, terraformAccessKeyEnv)
if envExist {
p.SecretKey = &secretKey
}
apiURL, _, envExist := getEnv(scwAPIURLEnv)
if envExist {
p.APIURL = &apiURL
}
insecureValue, envKey, envExist := getEnv(scwInsecureEnv, cliTLSVerifyEnv)
if envExist {
insecure, err := strconv.ParseBool(insecureValue)
if err != nil {
logger.Warningf("env variable %s cannot be parsed: %s is invalid boolean", envKey, insecureValue)
}
if envKey == cliTLSVerifyEnv {
insecure = !insecure // TLSVerify is the inverse of Insecure
}
p.Insecure = &insecure
}
organizationID, _, envExist := getEnv(scwDefaultOrganizationIDEnv, cliOrganizationEnv, terraformOrganizationEnv)
if envExist {
p.DefaultOrganizationID = &organizationID
}
region, _, envExist := getEnv(scwDefaultRegionEnv, cliRegionEnv, terraformRegionEnv)
if envExist {
region = v1RegionToV2(region)
p.DefaultRegion = &region
}
zone, _, envExist := getEnv(scwDefaultZoneEnv)
if envExist {
p.DefaultZone = &zone
}
return p
}
func getEnv(upToDateKey string, deprecatedKeys ...string) (string, string, bool) {
value, exist := os.LookupEnv(upToDateKey)
if exist {
logger.Infof("reading value from %s", upToDateKey)
return value, upToDateKey, true
}
for _, key := range deprecatedKeys {
value, exist := os.LookupEnv(key)
if exist {
logger.Infof("reading value from %s", key)
logger.Warningf("%s is deprecated, please use %s instead", key, upToDateKey)
return value, key, true
}
}
return "", "", false
}
func v1RegionToV2(region string) string {
switch region {
case v1RegionFrPar:
logger.Warningf("par1 is a deprecated name for region, use fr-par instead")
return "fr-par"
case v1RegionNlAms:
logger.Warningf("ams1 is a deprecated name for region, use nl-ams instead")
return "nl-ams"
default:
return region
}
}

View File

@@ -1,7 +1,15 @@
package scw
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"sort"
"strings"
"time"
"github.com/scaleway/scaleway-sdk-go/internal/errors"
)
// SdkError is a base interface for all Scaleway SDK errors.
@@ -15,10 +23,13 @@ type ResponseError struct {
// Message is a human-friendly error message
Message string `json:"message"`
// Type is a string code that defines the kind of error
// Type is a string code that defines the kind of error. This field is only used by instance API
Type string `json:"type,omitempty"`
// Fields contains detail about validation error
// Resource is a string code that defines the resource concerned by the error. This field is only used by instance API
Resource string `json:"resource,omitempty"`
// Fields contains detail about validation error. This field is only used by instance API
Fields map[string][]string `json:"fields,omitempty"`
// StatusCode is the HTTP status code received
@@ -26,11 +37,34 @@ type ResponseError struct {
// Status is the HTTP status received
Status string `json:"-"`
RawBody json.RawMessage `json:"-"`
}
func (e *ResponseError) UnmarshalJSON(b []byte) error {
type tmpResponseError ResponseError
tmp := tmpResponseError(*e)
err := json.Unmarshal(b, &tmp)
if err != nil {
return err
}
tmp.Message = strings.ToLower(tmp.Message)
*e = ResponseError(tmp)
return nil
}
// IsScwSdkError implement SdkError interface
func (e *ResponseError) IsScwSdkError() {}
func (e *ResponseError) Error() string {
s := fmt.Sprintf("scaleway-sdk-go: http error %s", e.Status)
if e.Resource != "" {
s = fmt.Sprintf("%s: resource %s", s, e.Resource)
}
if e.Message != "" {
s = fmt.Sprintf("%s: %s", s, e.Message)
}
@@ -41,6 +75,347 @@ func (e *ResponseError) Error() string {
return s
}
func (e *ResponseError) GetRawBody() json.RawMessage {
return e.RawBody
}
// IsScwSdkError implement SdkError interface
func (e *ResponseError) IsScwSdkError() {}
// hasResponseError returns an SdkError when the HTTP status is not OK.
func hasResponseError(res *http.Response) error {
if res.StatusCode >= 200 && res.StatusCode <= 299 {
return nil
}
newErr := &ResponseError{
StatusCode: res.StatusCode,
Status: res.Status,
}
if res.Body == nil {
return newErr
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return errors.Wrap(err, "cannot read error response body")
}
newErr.RawBody = body
// The error content is not encoded in JSON, only returns HTTP data.
if res.Header.Get("Content-Type") != "application/json" {
newErr.Message = res.Status
return newErr
}
err = json.Unmarshal(body, newErr)
if err != nil {
return errors.Wrap(err, "could not parse error response body")
}
err = unmarshalStandardError(newErr.Type, body)
if err != nil {
return err
}
err = unmarshalNonStandardError(newErr.Type, body)
if err != nil {
return err
}
return newErr
}
func unmarshalStandardError(errorType string, body []byte) error {
var stdErr SdkError
switch errorType {
case "invalid_arguments":
stdErr = &InvalidArgumentsError{RawBody: body}
case "quotas_exceeded":
stdErr = &QuotasExceededError{RawBody: body}
case "transient_state":
stdErr = &TransientStateError{RawBody: body}
case "not_found":
stdErr = &ResourceNotFoundError{RawBody: body}
case "permissions_denied":
stdErr = &PermissionsDeniedError{RawBody: body}
case "out_of_stock":
stdErr = &OutOfStockError{RawBody: body}
case "resource_expired":
stdErr = &ResourceExpiredError{RawBody: body}
default:
return nil
}
err := json.Unmarshal(body, stdErr)
if err != nil {
return errors.Wrap(err, "could not parse error %s response body", errorType)
}
return stdErr
}
func unmarshalNonStandardError(errorType string, body []byte) error {
switch errorType {
// Only in instance API.
case "invalid_request_error":
invalidRequestError := &InvalidRequestError{RawBody: body}
err := json.Unmarshal(body, invalidRequestError)
if err != nil {
return errors.Wrap(err, "could not parse error %s response body", errorType)
}
invalidArgumentsError := invalidRequestError.ToInvalidArgumentsError()
if invalidArgumentsError != nil {
return invalidArgumentsError
}
quotasExceededError := invalidRequestError.ToQuotasExceededError()
if quotasExceededError != nil {
return quotasExceededError
}
// At this point, the invalid_request_error is not an InvalidArgumentsError and
// the default marshalling will be used.
return nil
default:
return nil
}
}
type InvalidArgumentsErrorDetail struct {
ArgumentName string `json:"argument_name"`
Reason string `json:"reason"`
HelpMessage string `json:"help_message"`
}
type InvalidArgumentsError struct {
Details []InvalidArgumentsErrorDetail `json:"details"`
RawBody json.RawMessage `json:"-"`
}
// IsScwSdkError implements the SdkError interface
func (e *InvalidArgumentsError) IsScwSdkError() {}
func (e *InvalidArgumentsError) Error() string {
invalidArgs := make([]string, len(e.Details))
for i, d := range e.Details {
invalidArgs[i] = d.ArgumentName
switch d.Reason {
case "unknown":
invalidArgs[i] += " is invalid for unexpected reason"
case "required":
invalidArgs[i] += " is required"
case "format":
invalidArgs[i] += " is wrongly formatted"
case "constraint":
invalidArgs[i] += " does not respect constraint"
}
if d.HelpMessage != "" {
invalidArgs[i] += ", " + d.HelpMessage
}
}
return "scaleway-sdk-go: invalid argument(s): " + strings.Join(invalidArgs, "; ")
}
func (e *InvalidArgumentsError) GetRawBody() json.RawMessage {
return e.RawBody
}
// InvalidRequestError is only returned by the instance API.
// Warning: this is not a standard error.
type InvalidRequestError struct {
Message string `json:"message"`
Fields map[string][]string `json:"fields"`
Resource string `json:"resource"`
RawBody json.RawMessage `json:"-"`
}
// ToSdkError returns a standard error InvalidArgumentsError or nil Fields is nil.
func (e *InvalidRequestError) ToInvalidArgumentsError() SdkError {
// If error has no fields, it is not an InvalidArgumentsError.
if e.Fields == nil || len(e.Fields) == 0 {
return nil
}
invalidArguments := &InvalidArgumentsError{
RawBody: e.RawBody,
}
fieldNames := []string(nil)
for fieldName := range e.Fields {
fieldNames = append(fieldNames, fieldName)
}
sort.Strings(fieldNames)
for _, fieldName := range fieldNames {
for _, message := range e.Fields[fieldName] {
invalidArguments.Details = append(invalidArguments.Details, InvalidArgumentsErrorDetail{
ArgumentName: fieldName,
Reason: "constraint",
HelpMessage: message,
})
}
}
return invalidArguments
}
func (e *InvalidRequestError) ToQuotasExceededError() SdkError {
if !strings.Contains(strings.ToLower(e.Message), "quota exceeded for this resource") {
return nil
}
return &QuotasExceededError{
Details: []QuotasExceededErrorDetail{
{
Resource: e.Resource,
Quota: 0,
Current: 0,
},
},
RawBody: e.RawBody,
}
}
type QuotasExceededErrorDetail struct {
Resource string `json:"resource"`
Quota uint32 `json:"quota"`
Current uint32 `json:"current"`
}
type QuotasExceededError struct {
Details []QuotasExceededErrorDetail `json:"details"`
RawBody json.RawMessage `json:"-"`
}
// IsScwSdkError implements the SdkError interface
func (e *QuotasExceededError) IsScwSdkError() {}
func (e *QuotasExceededError) Error() string {
invalidArgs := make([]string, len(e.Details))
for i, d := range e.Details {
invalidArgs[i] = fmt.Sprintf("%s has reached its quota (%d/%d)", d.Resource, d.Current, d.Current)
}
return "scaleway-sdk-go: quota exceeded(s): " + strings.Join(invalidArgs, "; ")
}
func (e *QuotasExceededError) GetRawBody() json.RawMessage {
return e.RawBody
}
type PermissionsDeniedError struct {
Details []struct {
Resource string `json:"resource"`
Action string `json:"action"`
} `json:"details"`
RawBody json.RawMessage `json:"-"`
}
// IsScwSdkError implements the SdkError interface
func (e *PermissionsDeniedError) IsScwSdkError() {}
func (e *PermissionsDeniedError) Error() string {
invalidArgs := make([]string, len(e.Details))
for i, d := range e.Details {
invalidArgs[i] = fmt.Sprintf("%s %s", d.Action, d.Resource)
}
return "scaleway-sdk-go: insufficient permissions: " + strings.Join(invalidArgs, "; ")
}
func (e *PermissionsDeniedError) GetRawBody() json.RawMessage {
return e.RawBody
}
type TransientStateError struct {
Resource string `json:"resource"`
ResourceID string `json:"resource_id"`
CurrentState string `json:"current_state"`
RawBody json.RawMessage `json:"-"`
}
// IsScwSdkError implements the SdkError interface
func (e *TransientStateError) IsScwSdkError() {}
func (e *TransientStateError) Error() string {
return fmt.Sprintf("scaleway-sdk-go: resource %s with ID %s is in a transient state: %s", e.Resource, e.ResourceID, e.CurrentState)
}
func (e *TransientStateError) GetRawBody() json.RawMessage {
return e.RawBody
}
type ResourceNotFoundError struct {
Resource string `json:"resource"`
ResourceID string `json:"resource_id"`
RawBody json.RawMessage `json:"-"`
}
// IsScwSdkError implements the SdkError interface
func (e *ResourceNotFoundError) IsScwSdkError() {}
func (e *ResourceNotFoundError) Error() string {
return fmt.Sprintf("scaleway-sdk-go: resource %s with ID %s is not found", e.Resource, e.ResourceID)
}
func (e *ResourceNotFoundError) GetRawBody() json.RawMessage {
return e.RawBody
}
type OutOfStockError struct {
Resource string `json:"resource"`
RawBody json.RawMessage `json:"-"`
}
// IsScwSdkError implements the SdkError interface
func (e *OutOfStockError) IsScwSdkError() {}
func (e *OutOfStockError) Error() string {
return fmt.Sprintf("scaleway-sdk-go: resource %s is out of stock", e.Resource)
}
func (e *OutOfStockError) GetRawBody() json.RawMessage {
return e.RawBody
}
// InvalidClientOptionError indicates that at least one of client data has been badly provided for the client creation.
type InvalidClientOptionError struct {
errorType string
}
func NewInvalidClientOptionError(format string, a ...interface{}) *InvalidClientOptionError {
return &InvalidClientOptionError{errorType: fmt.Sprintf(format, a...)}
}
// IsScwSdkError implements the SdkError interface
func (e InvalidClientOptionError) IsScwSdkError() {}
func (e InvalidClientOptionError) Error() string {
return fmt.Sprintf("scaleway-sdk-go: %s", e.errorType)
}
// ConfigFileNotFound indicates that the config file could not be found
type ConfigFileNotFoundError struct {
path string
}
func configFileNotFound(path string) *ConfigFileNotFoundError {
return &ConfigFileNotFoundError{path: path}
}
// ConfigFileNotFoundError implements the SdkError interface
func (e ConfigFileNotFoundError) IsScwSdkError() {}
func (e ConfigFileNotFoundError) Error() string {
return fmt.Sprintf("scaleway-sdk-go: cannot read config file %s: no such file or directory", e.path)
}
// ResourceExpiredError implements the SdkError interface
type ResourceExpiredError struct {
Resource string `json:"resource"`
ResourceID string `json:"resource_id"`
ExpiredSince time.Time `json:"expired_since"`
RawBody json.RawMessage `json:"-"`
}
func (r ResourceExpiredError) Error() string {
return fmt.Sprintf("scaleway-sdk-go: resource %s with ID %s expired since %s", r.Resource, r.ResourceID, r.ExpiredSince.String())
}
func (r ResourceExpiredError) IsScwSdkError() {}

View File

@@ -1,64 +0,0 @@
package scw
import (
"math"
"reflect"
"strconv"
"github.com/scaleway/scaleway-sdk-go/internal/errors"
)
type lister interface {
UnsafeGetTotalCount() int
UnsafeAppend(interface{}) (int, SdkError)
}
type legacyLister interface {
UnsafeSetTotalCount(totalCount int)
}
// doListAll collects all pages of a List request and aggregate all results on a single response.
func (c *Client) doListAll(req *ScalewayRequest, res interface{}) (err SdkError) {
// check for lister interface
if response, isLister := res.(lister); isLister {
pageCount := math.MaxUint32
for page := 1; page <= pageCount; page++ {
// set current page
req.Query.Set("page", strconv.Itoa(page))
// request the next page
nextPage := newPage(response)
err := c.do(req, nextPage)
if err != nil {
return err
}
// append results
pageSize, err := response.UnsafeAppend(nextPage)
if err != nil {
return err
}
if pageSize == 0 {
return nil
}
// set total count on first request
if pageCount == math.MaxUint32 {
totalCount := nextPage.(lister).UnsafeGetTotalCount()
pageCount = (totalCount + pageSize - 1) / pageSize
}
}
return nil
}
return errors.New("%T does not support pagination", res)
}
// newPage returns a variable set to the zero value of the given type
func newPage(v interface{}) interface{} {
// reflect.New always create a pointer, that's why we use reflect.Indirect before
return reflect.New(reflect.Indirect(reflect.ValueOf(v)).Type()).Interface()
}

View File

@@ -1,11 +1,18 @@
package utils
package scw
import (
"encoding/json"
"fmt"
"strings"
"github.com/scaleway/scaleway-sdk-go/internal/errors"
"github.com/scaleway/scaleway-sdk-go/logger"
"github.com/scaleway/scaleway-sdk-go/validation"
)
// localityPartsSeparator is the separator used in Zone and Region
const localityPartsSeparator = "-"
// Zone is an availability zone
type Zone string
@@ -37,6 +44,22 @@ func (zone *Zone) Exists() bool {
return false
}
// String returns a Zone as a string
func (zone *Zone) String() string {
return string(*zone)
}
// Region returns the parent Region for the Zone.
// Manipulates the string directly to allow unlisted zones formatted as xx-yyy-z.
func (zone *Zone) Region() (Region, error) {
zoneStr := zone.String()
if !validation.IsZone(zoneStr) {
return "", fmt.Errorf("invalid zone '%v'", zoneStr)
}
zoneParts := strings.Split(zoneStr, localityPartsSeparator)
return Region(strings.Join(zoneParts[:2], localityPartsSeparator)), nil
}
// Region is a geographical location
type Region string
@@ -77,7 +100,7 @@ func (region Region) GetZones() []Zone {
}
}
// ParseZone parse a string value into a Zone object
// ParseZone parses a string value into a Zone and returns an error if it has a bad format.
func ParseZone(zone string) (Zone, error) {
switch zone {
case "par1":
@@ -89,6 +112,14 @@ func ParseZone(zone string) (Zone, error) {
// logger.Warningf("ams1 is a deprecated name for zone, use nl-ams-1 instead")
return ZoneNlAms1, nil
default:
if !validation.IsZone(zone) {
zones := []string(nil)
for _, z := range AllZones {
zones = append(zones, string(z))
}
return "", errors.New("bad zone format, available zones are: %s", strings.Join(zones, ", "))
}
newZone := Zone(zone)
if !newZone.Exists() {
logger.Warningf("%s is an unknown zone", newZone)
@@ -100,7 +131,6 @@ func ParseZone(zone string) (Zone, error) {
// UnmarshalJSON implements the Unmarshaler interface for a Zone.
// this to call ParseZone on the string input and return the correct Zone object.
func (zone *Zone) UnmarshalJSON(input []byte) error {
// parse input value as string
var stringValue string
err := json.Unmarshal(input, &stringValue)
@@ -116,7 +146,7 @@ func (zone *Zone) UnmarshalJSON(input []byte) error {
return nil
}
// ParseRegion parse a string value into a Zone object
// ParseRegion parses a string value into a Region and returns an error if it has a bad format.
func ParseRegion(region string) (Region, error) {
switch region {
case "par1":
@@ -128,6 +158,14 @@ func ParseRegion(region string) (Region, error) {
// logger.Warningf("ams1 is a deprecated name for region, use nl-ams instead")
return RegionNlAms, nil
default:
if !validation.IsRegion(region) {
regions := []string(nil)
for _, r := range AllRegions {
regions = append(regions, string(r))
}
return "", errors.New("bad region format, available regions are: %s", strings.Join(regions, ", "))
}
newRegion := Region(region)
if !newRegion.Exists() {
logger.Warningf("%s is an unknown region", newRegion)
@@ -153,3 +191,8 @@ func (region *Region) UnmarshalJSON(input []byte) error {
}
return nil
}
// String returns a Region as a string
func (region *Region) String() string {
return string(*region)
}

View File

@@ -0,0 +1,110 @@
package scw
import (
"errors"
"os"
"path/filepath"
)
const (
// XDG wiki: https://wiki.archlinux.org/index.php/XDG_Base_Directory
xdgConfigDirEnv = "XDG_CONFIG_HOME"
xdgCacheDirEnv = "XDG_CACHE_HOME"
unixHomeDirEnv = "HOME"
windowsHomeDirEnv = "USERPROFILE"
defaultConfigFileName = "config.yaml"
)
var (
// ErrNoHomeDir errors when no user directory is found
ErrNoHomeDir = errors.New("user home directory not found")
)
// GetCacheDirectory returns the default cache directory.
// Cache directory is based on the following priority order:
// - $SCW_CACHE_DIR
// - $XDG_CACHE_HOME/scw
// - $HOME/.cache/scw
// - $USERPROFILE/.cache/scw
func GetCacheDirectory() string {
cacheDir := ""
switch {
case os.Getenv(scwCacheDirEnv) != "":
cacheDir = os.Getenv(scwCacheDirEnv)
case os.Getenv(xdgCacheDirEnv) != "":
cacheDir = filepath.Join(os.Getenv(xdgCacheDirEnv), "scw")
case os.Getenv(unixHomeDirEnv) != "":
cacheDir = filepath.Join(os.Getenv(unixHomeDirEnv), ".cache", "scw")
case os.Getenv(windowsHomeDirEnv) != "":
cacheDir = filepath.Join(os.Getenv(windowsHomeDirEnv), ".cache", "scw")
default:
// TODO: fallback on local folder?
}
// Clean the cache directory path when exiting the function
return filepath.Clean(cacheDir)
}
// GetConfigPath returns the default path.
// Default path is based on the following priority order:
// - $SCW_CONFIG_PATH
// - $XDG_CONFIG_HOME/scw/config.yaml
// - $HOME/.config/scw/config.yaml
// - $USERPROFILE/.config/scw/config.yaml
func GetConfigPath() string {
configPath := os.Getenv(scwConfigPathEnv)
if configPath == "" {
configPath, _ = getConfigV2FilePath()
}
return filepath.Clean(configPath)
}
// getConfigV2FilePath returns the path to the v2 config file
func getConfigV2FilePath() (string, bool) {
configDir, err := getScwConfigDir()
if err != nil {
return "", false
}
return filepath.Clean(filepath.Join(configDir, defaultConfigFileName)), true
}
// getConfigV1FilePath returns the path to the v1 config file
func getConfigV1FilePath() (string, bool) {
path, err := getHomeDir()
if err != nil {
return "", false
}
return filepath.Clean(filepath.Join(path, ".scwrc")), true
}
// getScwConfigDir returns the path to scw config folder
func getScwConfigDir() (string, error) {
if xdgPath := os.Getenv(xdgConfigDirEnv); xdgPath != "" {
return filepath.Join(xdgPath, "scw"), nil
}
homeDir, err := getHomeDir()
if err != nil {
return "", err
}
return filepath.Join(homeDir, ".config", "scw"), nil
}
// getHomeDir returns the path to your home directory
func getHomeDir() (string, error) {
switch {
case os.Getenv(unixHomeDirEnv) != "":
return os.Getenv(unixHomeDirEnv), nil
case os.Getenv(windowsHomeDirEnv) != "":
return os.Getenv(windowsHomeDirEnv), nil
default:
return "", ErrNoHomeDir
}
}
func fileExist(name string) bool {
_, err := os.Stat(name)
return err == nil
}

View File

@@ -10,7 +10,6 @@ import (
"github.com/scaleway/scaleway-sdk-go/internal/auth"
"github.com/scaleway/scaleway-sdk-go/internal/errors"
"github.com/scaleway/scaleway-sdk-go/utils"
)
// ScalewayRequest contains all the contents related to performing a request on the Scaleway API.
@@ -20,7 +19,11 @@ type ScalewayRequest struct {
Headers http.Header
Query url.Values
Body io.Reader
Ctx context.Context
// request options
ctx context.Context
auth auth.Auth
allPages bool
}
// getAllHeaders constructs a http.Header object and aggregates all headers into the object.
@@ -34,11 +37,12 @@ func (req *ScalewayRequest) getAllHeaders(token auth.Auth, userAgent string, ano
allHeaders.Set("User-Agent", userAgent)
if req.Body != nil {
allHeaders.Set("content-type", "application/json")
allHeaders.Set("Content-Type", "application/json")
}
for key, value := range req.Headers {
allHeaders.Del(key)
for _, v := range value {
allHeaders.Set(key, v)
allHeaders.Add(key, v)
}
}
@@ -46,7 +50,7 @@ func (req *ScalewayRequest) getAllHeaders(token auth.Auth, userAgent string, ano
}
// getURL constructs a URL based on the base url and the client.
func (req *ScalewayRequest) getURL(baseURL string) (*url.URL, SdkError) {
func (req *ScalewayRequest) getURL(baseURL string) (*url.URL, error) {
url, err := url.Parse(baseURL + req.Path)
if err != nil {
return nil, errors.New("invalid url %s: %s", baseURL+req.Path, err)
@@ -63,9 +67,12 @@ func (req *ScalewayRequest) SetBody(body interface{}) error {
var content io.Reader
switch b := body.(type) {
case *utils.File:
case *File:
contentType = b.ContentType
content = b.Content
case io.Reader:
contentType = "text/plain"
content = b
default:
buf, err := json.Marshal(body)
if err != nil {
@@ -75,8 +82,23 @@ func (req *ScalewayRequest) SetBody(body interface{}) error {
content = bytes.NewReader(buf)
}
if req.Headers == nil {
req.Headers = http.Header{}
}
req.Headers.Set("Content-Type", contentType)
req.Body = content
return nil
}
func (req *ScalewayRequest) apply(opts []RequestOption) {
for _, opt := range opts {
opt(req)
}
}
func (req *ScalewayRequest) validate() error {
// nothing so far
return nil
}

View File

@@ -2,14 +2,16 @@ package scw
import (
"context"
"github.com/scaleway/scaleway-sdk-go/internal/auth"
)
// RequestOption is a function that applies options to a ScalewayRequest.
type RequestOption func(*requestSettings)
type RequestOption func(*ScalewayRequest)
// WithContext request option sets the context of a ScalewayRequest
func WithContext(ctx context.Context) RequestOption {
return func(s *requestSettings) {
return func(s *ScalewayRequest) {
s.ctx = ctx
}
}
@@ -17,27 +19,14 @@ func WithContext(ctx context.Context) RequestOption {
// WithAllPages aggregate all pages in the response of a List request.
// Will error when pagination is not supported on the request.
func WithAllPages() RequestOption {
return func(s *requestSettings) {
return func(s *ScalewayRequest) {
s.allPages = true
}
}
type requestSettings struct {
ctx context.Context
allPages bool
}
func newRequestSettings() *requestSettings {
return &requestSettings{}
}
func (s *requestSettings) apply(opts []RequestOption) {
for _, opt := range opts {
opt(s)
// WithAuthRequest overwrites the client access key and secret key used in the request.
func WithAuthRequest(accessKey, secretKey string) RequestOption {
return func(s *ScalewayRequest) {
s.auth = auth.NewToken(accessKey, secretKey)
}
}
func (s *requestSettings) validate() SdkError {
// nothing so far
return nil
}

View File

@@ -1,64 +0,0 @@
package scw
import (
"fmt"
"net/url"
"github.com/scaleway/scaleway-sdk-go/internal/auth"
"github.com/scaleway/scaleway-sdk-go/utils"
)
type settings struct {
apiURL string
token auth.Auth
userAgent string
httpClient httpClient
insecure bool
defaultProjectID *string
defaultRegion *utils.Region
defaultZone *utils.Zone
defaultPageSize *int32
}
func newSettings() *settings {
return &settings{}
}
func (s *settings) apply(opts []ClientOption) {
for _, opt := range opts {
opt(s)
}
}
func (s *settings) validate() error {
var err error
if s.token == nil {
return fmt.Errorf("no credential option provided")
}
_, err = url.Parse(s.apiURL)
if err != nil {
return fmt.Errorf("invalid url %s: %s", s.apiURL, err)
}
// TODO: Check ProjectID format
if s.defaultProjectID != nil && *s.defaultProjectID == "" {
return fmt.Errorf("default project id cannot be empty")
}
// TODO: Check Region format
if s.defaultRegion != nil && *s.defaultRegion == "" {
return fmt.Errorf("default region cannot be empty")
}
// TODO: Check Zone format
if s.defaultZone != nil && *s.defaultZone == "" {
return fmt.Errorf("default zone cannot be empty")
}
if s.defaultPageSize != nil && *s.defaultPageSize <= 0 {
return fmt.Errorf("default page size cannot be <= 0")
}
return nil
}

View File

@@ -5,7 +5,7 @@ import (
"runtime"
)
// TODO: versionning process
const version = "0.0.0"
// TODO: versioning process
const version = "v1.0.0-beta.6"
var userAgent = fmt.Sprintf("scaleway-sdk-go/%s (%s; %s; %s)", version, runtime.Version(), runtime.GOOS, runtime.GOARCH)

View File

@@ -1,423 +0,0 @@
package scwconfig
import (
"encoding/json"
"fmt"
"os"
"strconv"
"github.com/scaleway/scaleway-sdk-go/logger"
"github.com/scaleway/scaleway-sdk-go/utils"
"gopkg.in/yaml.v2"
)
// Environment variables
const (
// Up-to-date
scwConfigPathEnv = "SCW_CONFIG_PATH"
scwAccessKeyEnv = "SCW_ACCESS_KEY"
scwSecretKeyEnv = "SCW_SECRET_KEY"
scwActiveProfileEnv = "SCW_PROFILE"
scwAPIURLEnv = "SCW_API_URL"
scwInsecureEnv = "SCW_INSECURE"
scwDefaultProjectIDEnv = "SCW_DEFAULT_PROJECT_ID"
scwDefaultRegionEnv = "SCW_DEFAULT_REGION"
scwDefaultZoneEnv = "SCW_DEFAULT_ZONE"
// All deprecated (cli&terraform)
terraformAccessKeyEnv = "SCALEWAY_ACCESS_KEY" // used both as access key and secret key
terraformSecretKeyEnv = "SCALEWAY_TOKEN"
terraformOrganizationEnv = "SCALEWAY_ORGANIZATION"
terraformRegionEnv = "SCALEWAY_REGION"
cliTLSVerifyEnv = "SCW_TLSVERIFY"
cliOrganizationEnv = "SCW_ORGANIZATION"
cliRegionEnv = "SCW_REGION"
cliSecretKeyEnv = "SCW_TOKEN"
// TBD
//cliVerboseEnv = "SCW_VERBOSE_API"
//cliDebugEnv = "DEBUG"
//cliNoCheckVersionEnv = "SCW_NOCHECKVERSION"
//cliTestWithRealAPIEnv = "TEST_WITH_REAL_API"
//cliSecureExecEnv = "SCW_SECURE_EXEC"
//cliGatewayEnv = "SCW_GATEWAY"
//cliSensitiveEnv = "SCW_SENSITIVE"
//cliAccountAPIEnv = "SCW_ACCOUNT_API"
//cliMetadataAPIEnv = "SCW_METADATA_API"
//cliMarketPlaceAPIEnv = "SCW_MARKETPLACE_API"
//cliComputePar1APIEnv = "SCW_COMPUTE_PAR1_API"
//cliComputeAms1APIEnv = "SCW_COMPUTE_AMS1_API"
//cliCommercialTypeEnv = "SCW_COMMERCIAL_TYPE"
//cliTargetArchEnv = "SCW_TARGET_ARCH"
)
// Config interface is made of getters to retrieve
// the config field by field.
type Config interface {
GetAccessKey() (accessKey string, exist bool)
GetSecretKey() (secretKey string, exist bool)
GetAPIURL() (apiURL string, exist bool)
GetInsecure() (insecure bool, exist bool)
GetDefaultProjectID() (defaultProjectID string, exist bool)
GetDefaultRegion() (defaultRegion utils.Region, exist bool)
GetDefaultZone() (defaultZone utils.Zone, exist bool)
}
type configV2 struct {
profile `yaml:",inline"`
ActiveProfile *string `yaml:"active_profile,omitempty"`
Profiles map[string]*profile `yaml:"profiles,omitempty"`
// withProfile is used by LoadWithProfile to handle the following priority order:
// c.withProfile > os.Getenv("SCW_PROFILE") > c.ActiveProfile
withProfile string
}
type profile struct {
AccessKey *string `yaml:"access_key,omitempty"`
SecretKey *string `yaml:"secret_key,omitempty"`
APIURL *string `yaml:"api_url,omitempty"`
Insecure *bool `yaml:"insecure,omitempty"`
DefaultProjectID *string `yaml:"default_project_id,omitempty"`
DefaultRegion *string `yaml:"default_region,omitempty"`
DefaultZone *string `yaml:"default_zone,omitempty"`
}
func unmarshalConfV2(content []byte) (*configV2, error) {
var config configV2
err := yaml.Unmarshal(content, &config)
if err != nil {
return nil, err
}
return &config, nil
}
func (c *configV2) catchInvalidProfile() (*configV2, error) {
activeProfile, err := c.getActiveProfile()
if err != nil {
return nil, err
}
if activeProfile == "" {
return c, nil
}
_, exist := c.Profiles[activeProfile]
if !exist {
return nil, fmt.Errorf("profile %s does not exist %s", activeProfile, inConfigFile())
}
return c, nil
}
func (c *configV2) getActiveProfile() (string, error) {
switch {
case c.withProfile != "":
return c.withProfile, nil
case os.Getenv(scwActiveProfileEnv) != "":
return os.Getenv(scwActiveProfileEnv), nil
case c.ActiveProfile != nil:
if *c.ActiveProfile == "" {
return "", fmt.Errorf("active_profile key cannot be empty %s", inConfigFile())
}
return *c.ActiveProfile, nil
default:
return "", nil
}
}
// GetAccessKey retrieve the access key from the config.
// It will check the following order:
// env, legacy env, active profile, default profile
//
// If the config is present in one of the above environment the
// value (which may be empty) is returned and the boolean is true.
// Otherwise the returned value will be empty and the boolean will
// be false.
func (c *configV2) GetAccessKey() (string, bool) {
envValue, _, envExist := getenv(scwAccessKeyEnv, terraformAccessKeyEnv)
activeProfile, _ := c.getActiveProfile()
var accessKey string
switch {
case envExist:
accessKey = envValue
case activeProfile != "" && c.Profiles[activeProfile].AccessKey != nil:
accessKey = *c.Profiles[activeProfile].AccessKey
case c.AccessKey != nil:
accessKey = *c.AccessKey
default:
logger.Warningf("no access key found")
return "", false
}
if accessKey == "" {
logger.Warningf("access key is empty")
}
return accessKey, true
}
// GetSecretKey retrieve the secret key from the config.
// It will check the following order:
// env, legacy env, active profile, default profile
//
// If the config is present in one of the above environment the
// value (which may be empty) is returned and the boolean is true.
// Otherwise the returned value will be empty and the boolean will
// be false.
func (c *configV2) GetSecretKey() (string, bool) {
envValue, _, envExist := getenv(scwSecretKeyEnv, cliSecretKeyEnv, terraformSecretKeyEnv, terraformAccessKeyEnv)
activeProfile, _ := c.getActiveProfile()
var secretKey string
switch {
case envExist:
secretKey = envValue
case activeProfile != "" && c.Profiles[activeProfile].SecretKey != nil:
secretKey = *c.Profiles[activeProfile].SecretKey
case c.SecretKey != nil:
secretKey = *c.SecretKey
default:
logger.Warningf("no secret key found")
return "", false
}
if secretKey == "" {
logger.Warningf("secret key is empty")
}
return secretKey, true
}
// GetAPIURL retrieve the api url from the config.
// It will check the following order:
// env, legacy env, active profile, default profile
//
// If the config is present in one of the above environment the
// value (which may be empty) is returned and the boolean is true.
// Otherwise the returned value will be empty and the boolean will
// be false.
func (c *configV2) GetAPIURL() (string, bool) {
envValue, _, envExist := getenv(scwAPIURLEnv)
activeProfile, _ := c.getActiveProfile()
var apiURL string
switch {
case envExist:
apiURL = envValue
case activeProfile != "" && c.Profiles[activeProfile].APIURL != nil:
apiURL = *c.Profiles[activeProfile].APIURL
case c.APIURL != nil:
apiURL = *c.APIURL
default:
return "", false
}
if apiURL == "" {
logger.Warningf("api URL is empty")
}
return apiURL, true
}
// GetInsecure retrieve the insecure flag from the config.
// It will check the following order:
// env, legacy env, active profile, default profile
//
// If the config is present in one of the above environment the
// value (which may be empty) is returned and the boolean is true.
// Otherwise the returned value will be empty and the boolean will
// be false.
func (c *configV2) GetInsecure() (bool, bool) {
envValue, envKey, envExist := getenv(scwInsecureEnv, cliTLSVerifyEnv)
activeProfile, _ := c.getActiveProfile()
var insecure bool
var err error
switch {
case envExist:
insecure, err = strconv.ParseBool(envValue)
if err != nil {
logger.Warningf("env variable %s cannot be parsed: %s is invalid boolean ", envKey, envValue)
return false, false
}
if envKey == cliTLSVerifyEnv {
insecure = !insecure // TLSVerify is the inverse of Insecure
}
case activeProfile != "" && c.Profiles[activeProfile].Insecure != nil:
insecure = *c.Profiles[activeProfile].Insecure
case c.Insecure != nil:
insecure = *c.Insecure
default:
return false, false
}
return insecure, true
}
// GetDefaultProjectID retrieve the default project ID
// from the config. Legacy configs used the name
// "organization ID" or "organization" for
// this field. It will check the following order:
// env, legacy env, active profile, default profile
//
// If the config is present in one of the above environment the
// value (which may be empty) is returned and the boolean is true.
// Otherwise the returned value will be empty and the boolean will
// be false.
func (c *configV2) GetDefaultProjectID() (string, bool) {
envValue, _, envExist := getenv(scwDefaultProjectIDEnv, cliOrganizationEnv, terraformOrganizationEnv)
activeProfile, _ := c.getActiveProfile()
var defaultProj string
switch {
case envExist:
defaultProj = envValue
case activeProfile != "" && c.Profiles[activeProfile].DefaultProjectID != nil:
defaultProj = *c.Profiles[activeProfile].DefaultProjectID
case c.DefaultProjectID != nil:
defaultProj = *c.DefaultProjectID
default:
return "", false
}
// todo: validate format
if defaultProj == "" {
logger.Warningf("default project ID is empty")
}
return defaultProj, true
}
// GetDefaultRegion retrieve the default region
// from the config. It will check the following order:
// env, legacy env, active profile, default profile
//
// If the config is present in one of the above environment the
// value (which may be empty) is returned and the boolean is true.
// Otherwise the returned value will be empty and the boolean will
// be false.
func (c *configV2) GetDefaultRegion() (utils.Region, bool) {
envValue, _, envExist := getenv(scwDefaultRegionEnv, cliRegionEnv, terraformRegionEnv)
activeProfile, _ := c.getActiveProfile()
var defaultRegion string
switch {
case envExist:
defaultRegion = v1RegionToV2(envValue)
case activeProfile != "" && c.Profiles[activeProfile].DefaultRegion != nil:
defaultRegion = *c.Profiles[activeProfile].DefaultRegion
case c.DefaultRegion != nil:
defaultRegion = *c.DefaultRegion
default:
return "", false
}
// todo: validate format
if defaultRegion == "" {
logger.Warningf("default region is empty")
}
return utils.Region(defaultRegion), true
}
// GetDefaultZone retrieve the default zone
// from the config. It will check the following order:
// env, legacy env, active profile, default profile
//
// If the config is present in one of the above environment the
// value (which may be empty) is returned and the boolean is true.
// Otherwise the returned value will be empty and the boolean will
// be false.
func (c *configV2) GetDefaultZone() (utils.Zone, bool) {
envValue, _, envExist := getenv(scwDefaultZoneEnv)
activeProfile, _ := c.getActiveProfile()
var defaultZone string
switch {
case envExist:
defaultZone = envValue
case activeProfile != "" && c.Profiles[activeProfile].DefaultZone != nil:
defaultZone = *c.Profiles[activeProfile].DefaultZone
case c.DefaultZone != nil:
defaultZone = *c.DefaultZone
default:
return "", false
}
// todo: validate format
if defaultZone == "" {
logger.Warningf("default zone is empty")
}
return utils.Zone(defaultZone), true
}
func getenv(upToDateKey string, deprecatedKeys ...string) (string, string, bool) {
value, exist := os.LookupEnv(upToDateKey)
if exist {
logger.Infof("reading value from %s", upToDateKey)
return value, upToDateKey, true
}
for _, key := range deprecatedKeys {
value, exist := os.LookupEnv(key)
if exist {
logger.Infof("reading value from %s", key)
logger.Warningf("%s is deprecated, please use %s instead", key, upToDateKey)
return value, key, true
}
}
return "", "", false
}
const (
v1RegionFrPar = "par1"
v1RegionNlAms = "ams1"
)
// configV1 is a Scaleway CLI configuration file
type configV1 struct {
// Organization is the identifier of the Scaleway organization
Organization string `json:"organization"`
// Token is the authentication token for the Scaleway organization
Token string `json:"token"`
// Version is the actual version of scw CLI
Version string `json:"version"`
}
func unmarshalConfV1(content []byte) (*configV1, error) {
var config configV1
err := json.Unmarshal(content, &config)
if err != nil {
return nil, err
}
return &config, err
}
func (v1 *configV1) toV2() *configV2 {
return &configV2{
profile: profile{
DefaultProjectID: &v1.Organization,
SecretKey: &v1.Token,
// ignore v1 version
},
}
}
func v1RegionToV2(region string) string {
switch region {
case v1RegionFrPar:
logger.Warningf("par1 is a deprecated name for region, use fr-par instead")
return "fr-par"
case v1RegionNlAms:
logger.Warningf("ams1 is a deprecated name for region, use nl-ams instead")
return "nl-ams"
default:
return region
}
}

View File

@@ -1,104 +0,0 @@
package scwconfig
import (
"fmt"
"io/ioutil"
"os"
"github.com/scaleway/scaleway-sdk-go/logger"
)
const (
documentationLink = "https://github.com/scaleway/scaleway-sdk-go/blob/master/scwconfig/README.md"
)
// LoadWithProfile call Load() and set withProfile with the profile name.
func LoadWithProfile(profileName string) (Config, error) {
config, err := Load()
if err != nil {
return nil, err
}
v2Loaded := config.(*configV2)
v2Loaded.withProfile = profileName
return v2Loaded.catchInvalidProfile()
}
// Load config in the following order:
// - config file from SCW_CONFIG_PATH (V2 or V1)
// - config file V2
// - config file V1
// When the latest is found it migrates the V1 config
// to a V2 config following the V2 config path.
func Load() (Config, error) {
// STEP 1: try to load config file from SCW_CONFIG_PATH
configPath := os.Getenv(scwConfigPathEnv)
if configPath != "" {
content, err := ioutil.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("cannot read config file %s: %s", scwConfigPathEnv, err)
}
confV1, err := unmarshalConfV1(content)
if err == nil {
// do not migrate automatically when using SCW_CONFIG_PATH
logger.Warningf("loaded config V1 from %s: config V1 is deprecated, please switch your config file to the V2: %s", configPath, documentationLink)
return confV1.toV2().catchInvalidProfile()
}
confV2, err := unmarshalConfV2(content)
if err != nil {
return nil, fmt.Errorf("content of config file %s is invalid: %s", configPath, err)
}
logger.Infof("successfully loaded config V2 from %s", configPath)
return confV2.catchInvalidProfile()
}
// STEP 2: try to load config file V2
v2Path, v2PathOk := GetConfigV2FilePath()
if v2PathOk && fileExist(v2Path) {
file, err := ioutil.ReadFile(v2Path)
if err != nil {
return nil, fmt.Errorf("cannot read config file: %s", err)
}
confV2, err := unmarshalConfV2(file)
if err != nil {
return nil, fmt.Errorf("content of config file %s is invalid: %s", v2Path, err)
}
logger.Infof("successfully loaded config V2 from %s", v2Path)
return confV2.catchInvalidProfile()
}
// STEP 3: try to load config file V1
logger.Debugf("no config V2 found, fall back to config V1")
v1Path, v1PathOk := GetConfigV1FilePath()
if !v1PathOk {
logger.Infof("config file not found: no home directory")
return (&configV2{}).catchInvalidProfile()
}
file, err := ioutil.ReadFile(v1Path)
if err != nil {
logger.Infof("cannot read config file: %s", err)
return (&configV2{}).catchInvalidProfile() // ignore if file doesn't exist
}
confV1, err := unmarshalConfV1(file)
if err != nil {
return nil, fmt.Errorf("content of config file %s is invalid json: %s", v1Path, err)
}
// STEP 4: migrate V1 config to V2 config file
if v2PathOk {
err = migrateV1toV2(confV1, v2Path)
if err != nil {
return nil, err
}
}
return confV1.toV2().catchInvalidProfile()
}
func fileExist(name string) bool {
_, err := os.Stat(name)
return err == nil
}

View File

@@ -1,47 +0,0 @@
package scwconfig
import (
"io/ioutil"
"os"
"path/filepath"
"github.com/scaleway/scaleway-sdk-go/logger"
"gopkg.in/yaml.v2"
)
const (
defaultConfigPermission = 0600
)
// migrateV1toV2 converts the V1 config to V2 config and save it in the target path
// use config.Save() when the method is public
func migrateV1toV2(configV1 *configV1, targetPath string) error {
// STEP 0: get absolute target path
targetPath = filepath.Clean(targetPath)
// STEP 1: create dir
err := os.MkdirAll(filepath.Dir(targetPath), 0700)
if err != nil {
logger.Debugf("mkdir did not work on %s: %s", filepath.Dir(targetPath), err)
return nil
}
// STEP 2: marshal yaml config
newConfig := configV1.toV2()
file, err := yaml.Marshal(newConfig)
if err != nil {
return err
}
// STEP 3: save config
err = ioutil.WriteFile(targetPath, file, defaultConfigPermission)
if err != nil {
logger.Debugf("cannot write file %s: %s", targetPath, err)
return nil
}
// STEP 4: log success
logger.Infof("config successfully migrated to %s", targetPath)
return nil
}

View File

@@ -1,71 +0,0 @@
package scwconfig
import (
"errors"
"os"
"path/filepath"
)
const (
unixHomeDirEnv = "HOME"
windowsHomeDirEnv = "USERPROFILE"
xdgConfigDirEnv = "XDG_CONFIG_HOME"
defaultConfigFileName = "config.yaml"
)
var (
// ErrNoHomeDir errors when no user directory is found
ErrNoHomeDir = errors.New("user home directory not found")
)
func inConfigFile() string {
v2path, exist := GetConfigV2FilePath()
if exist {
return "in config file " + v2path
}
return ""
}
// GetConfigV2FilePath returns the path to the Scaleway CLI config file
func GetConfigV2FilePath() (string, bool) {
configDir, err := GetScwConfigDir()
if err != nil {
return "", false
}
return filepath.Join(configDir, defaultConfigFileName), true
}
// GetConfigV1FilePath returns the path to the Scaleway CLI config file
func GetConfigV1FilePath() (string, bool) {
path, err := GetHomeDir()
if err != nil {
return "", false
}
return filepath.Join(path, ".scwrc"), true
}
// GetScwConfigDir returns the path to scw config folder
func GetScwConfigDir() (string, error) {
if xdgPath := os.Getenv(xdgConfigDirEnv); xdgPath != "" {
return filepath.Join(xdgPath, "scw"), nil
}
homeDir, err := GetHomeDir()
if err != nil {
return "", err
}
return filepath.Join(homeDir, ".config", "scw"), nil
}
// GetHomeDir returns the path to your home directory
func GetHomeDir() (string, error) {
switch {
case os.Getenv(unixHomeDirEnv) != "":
return os.Getenv(unixHomeDirEnv), nil
case os.Getenv(windowsHomeDirEnv) != "":
return os.Getenv(windowsHomeDirEnv), nil
default:
return "", ErrNoHomeDir
}
}

View File

@@ -1,158 +0,0 @@
package utils
import "time"
// String returns a pointer to the string value passed in.
func String(v string) *string {
return &v
}
// StringSlice converts a slice of string values into a slice of
// string pointers
func StringSlice(src []string) []*string {
dst := make([]*string, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Strings returns a pointer to the []string value passed in.
func Strings(v []string) *[]string {
return &v
}
// StringsSlice converts a slice of []string values into a slice of
// []string pointers
func StringsSlice(src [][]string) []*[]string {
dst := make([]*[]string, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Bytes returns a pointer to the []byte value passed in.
func Bytes(v []byte) *[]byte {
return &v
}
// BytesSlice converts a slice of []byte values into a slice of
// []byte pointers
func BytesSlice(src [][]byte) []*[]byte {
dst := make([]*[]byte, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Bool returns a pointer to the bool value passed in.
func Bool(v bool) *bool {
return &v
}
// BoolSlice converts a slice of bool values into a slice of
// bool pointers
func BoolSlice(src []bool) []*bool {
dst := make([]*bool, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Int32 returns a pointer to the int32 value passed in.
func Int32(v int32) *int32 {
return &v
}
// Int32Slice converts a slice of int32 values into a slice of
// int32 pointers
func Int32Slice(src []int32) []*int32 {
dst := make([]*int32, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Int64 returns a pointer to the int64 value passed in.
func Int64(v int64) *int64 {
return &v
}
// Int64Slice converts a slice of int64 values into a slice of
// int64 pointers
func Int64Slice(src []int64) []*int64 {
dst := make([]*int64, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Uint32 returns a pointer to the uint32 value passed in.
func Uint32(v uint32) *uint32 {
return &v
}
// Uint32Slice converts a slice of uint32 values into a slice of
// uint32 pointers
func Uint32Slice(src []uint32) []*uint32 {
dst := make([]*uint32, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Uint64 returns a pointer to the uint64 value passed in.
func Uint64(v uint64) *uint64 {
return &v
}
// Uint64Slice converts a slice of uint64 values into a slice of
// uint64 pointers
func Uint64Slice(src []uint64) []*uint64 {
dst := make([]*uint64, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Float32 returns a pointer to the float32 value passed in.
func Float32(v float32) *float32 {
return &v
}
// Float32Slice converts a slice of float32 values into a slice of
// float32 pointers
func Float32Slice(src []float32) []*float32 {
dst := make([]*float32, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Float64 returns a pointer to the float64 value passed in.
func Float64(v float64) *float64 {
return &v
}
// Float64Slice converts a slice of float64 values into a slice of
// float64 pointers
func Float64Slice(src []float64) []*float64 {
dst := make([]*float64, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Duration returns a pointer to the Duration value passed in.
func Duration(v time.Duration) *time.Duration {
return &v
}

View File

@@ -1,15 +0,0 @@
package utils
import "io"
// File is the structure used to receive / send a file from / to the API
type File struct {
// Name of the file
Name string `json:"name"`
// ContentType used in the HTTP header `Content-Type`
ContentType string `json:"content_type"`
// Content of the file
Content io.Reader `json:"content"`
}

View File

@@ -1,33 +0,0 @@
package utils
// Money represents an amount of money with its currency type.
type Money struct {
// CurrencyCode is the 3-letter currency code defined in ISO 4217.
CurrencyCode string `json:"currency_code,omitempty"`
// Units is the whole units of the amount.
// For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar.
Units int64 `json:"units,omitempty"`
// Nanos is the number of nano (10^-9) units of the amount.
// The value must be between -999,999,999 and +999,999,999 inclusive.
// If `units` is positive, `nanos` must be positive or zero.
// If `units` is zero, `nanos` can be positive, zero, or negative.
// If `units` is negative, `nanos` must be negative or zero.
// For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000.
Nanos int32 `json:"nanos,omitempty"`
}
// NewMoneyFromFloat conerts a float with currency to a Money object.
func NewMoneyFromFloat(value float64, currency string) *Money {
return &Money{
CurrencyCode: currency,
Units: int64(value),
Nanos: int32((value - float64(int64(value))) * 1000000000),
}
}
// ToFloat converts a Money object to a float.
func (m *Money) ToFloat() float64 {
return float64(m.Units) + float64(m.Nanos)/1000000000
}

View File

@@ -1,17 +0,0 @@
package utils
// ServiceInfo contains API metadata
// These metadata are only here for debugging. Do not rely on these values
type ServiceInfo struct {
// Name is the name of the API
Name string `json:"name"`
// Description is a human readable description for the API
Description string `json:"description"`
// Version is the version of the API
Version string `json:"version"`
// DocumentationUrl is the a web url where the documentation of the API can be found
DocumentationUrl *string `json:"documentation_url"`
}

View File

@@ -0,0 +1,56 @@
// Package validation provides format validation functions.
package validation
import (
"net/url"
"regexp"
)
var (
isUUIDRegexp = regexp.MustCompile("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")
isRegionRegex = regexp.MustCompile("^[a-z]{2}-[a-z]{3}$")
isZoneRegex = regexp.MustCompile("^[a-z]{2}-[a-z]{3}-[1-9]$")
isAccessKey = regexp.MustCompile("^SCW[A-Z0-9]{17}$")
isEmailRegexp = regexp.MustCompile("^.+@.+$")
)
// IsUUID returns true if the given string has a valid UUID format.
func IsUUID(s string) bool {
return isUUIDRegexp.MatchString(s)
}
// IsAccessKey returns true if the given string has a valid Scaleway access key format.
func IsAccessKey(s string) bool {
return isAccessKey.MatchString(s)
}
// IsSecretKey returns true if the given string has a valid Scaleway secret key format.
func IsSecretKey(s string) bool {
return IsUUID(s)
}
// IsOrganizationID returns true if the given string has a valid Scaleway organization ID format.
func IsOrganizationID(s string) bool {
return IsUUID(s)
}
// IsRegion returns true if the given string has a valid region format.
func IsRegion(s string) bool {
return isRegionRegex.MatchString(s)
}
// IsZone returns true if the given string has a valid zone format.
func IsZone(s string) bool {
return isZoneRegex.MatchString(s)
}
// IsURL returns true if the given string has a valid URL format.
func IsURL(s string) bool {
_, err := url.Parse(s)
return err == nil
}
// IsEmail returns true if the given string has an email format.
func IsEmail(v string) bool {
return isEmailRegexp.MatchString(v)
}