provider/scaleway: Expose IPv6 support, improve documentation (#7784)

* provider/scaleway: update api version

* provider/scaleway: expose ipv6 support, rename ip attributes

since it can be both ipv4 and ipv6, choose a more generic name.

* provider/scaleway: allow servers in different SGs

* provider/scaleway: update documentation

* provider/scaleway: Update docs with security group

* provider/scaleway: add testcase for server security groups

* provider/scaleway: make deleting of security rules more resilient

* provider/scaleway: make deletion of security group more resilient

* provider/scaleway: guard against missing server
This commit is contained in:
Raphael Randschau 2016-07-25 13:49:09 +02:00 committed by Paul Stack
parent f8575f1edd
commit 9f314a3c29
10 changed files with 417 additions and 126 deletions

View File

@ -54,7 +54,9 @@ func resourceScalewayIPRead(d *schema.ResourceData, m interface{}) error {
}
d.Set("ip", resp.IP.Address)
if resp.IP.Server != nil {
d.Set("server", resp.IP.Server.Identifier)
}
return nil
}

View File

@ -112,7 +112,11 @@ func testAccCheckScalewayIPAttachment(n string, check func(string) bool, msg str
return err
}
if !check(ip.IP.Server.Identifier) {
var serverID = ""
if ip.IP.Server != nil {
serverID = ip.IP.Server.Identifier
}
if !check(serverID) {
return fmt.Errorf("IP check failed: %q", msg)
}

View File

@ -90,7 +90,7 @@ func resourceScalewaySecurityGroupRead(d *schema.ResourceData, m interface{}) er
func resourceScalewaySecurityGroupUpdate(d *schema.ResourceData, m interface{}) error {
scaleway := m.(*Client).scaleway
var req = api.ScalewayNewSecurityGroup{
var req = api.ScalewayUpdateSecurityGroup{
Organization: scaleway.Organization,
Name: d.Get("name").(string),
Description: d.Get("description").(string),
@ -110,6 +110,15 @@ func resourceScalewaySecurityGroupDelete(d *schema.ResourceData, m interface{})
err := scaleway.DeleteSecurityGroup(d.Id())
if err != nil {
if serr, ok := err.(api.ScalewayAPIError); ok {
log.Printf("[DEBUG] error reading Security Group Rule: %q\n", serr.APIMessage)
if serr.StatusCode == 404 {
d.SetId("")
return nil
}
}
return err
}

View File

@ -154,6 +154,15 @@ func resourceScalewaySecurityGroupRuleDelete(d *schema.ResourceData, m interface
err := scaleway.DeleteSecurityGroupRule(d.Get("security_group").(string), d.Id())
if err != nil {
if serr, ok := err.(api.ScalewayAPIError); ok {
log.Printf("[DEBUG] error reading Security Group Rule: %q\n", serr.APIMessage)
if serr.StatusCode == 404 {
d.SetId("")
return nil
}
}
return err
}

View File

@ -40,11 +40,24 @@ func resourceScalewayServer() *schema.Resource {
},
Optional: true,
},
"ipv4_address_private": &schema.Schema{
"enable_ipv6": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"dynamic_ip_required": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"security_group": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"private_ip": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"ipv4_address_public": &schema.Schema{
"public_ip": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
@ -53,10 +66,6 @@ func resourceScalewayServer() *schema.Resource {
Optional: true,
Computed: true,
},
"dynamic_ip_required": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"state_detail": &schema.Schema{
Type: schema.TypeString,
Computed: true,
@ -73,6 +82,8 @@ func resourceScalewayServerCreate(d *schema.ResourceData, m interface{}) error {
Name: d.Get("name").(string),
Image: String(image),
Organization: scaleway.Organization,
EnableIPV6: d.Get("enable_ipv6").(bool),
SecurityGroup: d.Get("security_group").(string),
}
server.DynamicIPRequired = Bool(d.Get("dynamic_ip_required").(bool))
@ -127,8 +138,9 @@ func resourceScalewayServerRead(d *schema.ResourceData, m interface{}) error {
return err
}
d.Set("ipv4_address_private", server.PrivateIP)
d.Set("ipv4_address_public", server.PublicAddress.IP)
d.Set("private_ip", server.PrivateIP)
d.Set("public_ip", server.PublicAddress.IP)
d.Set("state", server.State)
d.Set("state_detail", server.StateDetail)
d.Set("tags", server.Tags)
@ -161,10 +173,20 @@ func resourceScalewayServerUpdate(d *schema.ResourceData, m interface{}) error {
}
}
if d.HasChange("enable_ipv6") {
req.EnableIPV6 = Bool(d.Get("enable_ipv6").(bool))
}
if d.HasChange("dynamic_ip_required") {
req.DynamicIPRequired = Bool(d.Get("dynamic_ip_required").(bool))
}
if d.HasChange("security_group") {
req.SecurityGroup = &api.ScalewaySecurityGroup{
Identifier: d.Get("security_group").(string),
}
}
if err := scaleway.PatchServer(d.Id(), req); err != nil {
return fmt.Errorf("Failed patching scaleway server: %q", err)
}

View File

@ -31,6 +31,30 @@ func TestAccScalewayServer_Basic(t *testing.T) {
})
}
func TestAccScalewayServer_SecurityGroup(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckScalewayServerDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCheckScalewayServerConfig_SecurityGroup,
Check: resource.ComposeTestCheckFunc(
testAccCheckScalewayServerExists("scaleway_server.base"),
testAccCheckScalewayServerSecurityGroup("scaleway_server.base", "blue"),
),
},
resource.TestStep{
Config: testAccCheckScalewayServerConfig_SecurityGroup_Update,
Check: resource.ComposeTestCheckFunc(
testAccCheckScalewayServerExists("scaleway_server.base"),
testAccCheckScalewayServerSecurityGroup("scaleway_server.base", "red"),
),
},
},
})
}
func testAccCheckScalewayServerDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*Client).scaleway
@ -77,6 +101,28 @@ func testAccCheckScalewayServerAttributes(n string) resource.TestCheckFunc {
}
}
func testAccCheckScalewayServerSecurityGroup(n, securityGroupName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Unknown resource: %s", n)
}
client := testAccProvider.Meta().(*Client).scaleway
server, err := client.GetServer(rs.Primary.ID)
if err != nil {
return err
}
if server.SecurityGroup.Name != securityGroupName {
return fmt.Errorf("Server has wrong security_group")
}
return nil
}
}
func testAccCheckScalewayServerExists(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
@ -114,3 +160,43 @@ resource "scaleway_server" "base" {
type = "C1"
tags = [ "terraform-test" ]
}`, armImageIdentifier)
var testAccCheckScalewayServerConfig_SecurityGroup = fmt.Sprintf(`
resource "scaleway_security_group" "blue" {
name = "blue"
description = "blue"
}
resource "scaleway_security_group" "red" {
name = "red"
description = "red"
}
resource "scaleway_server" "base" {
name = "test"
# ubuntu 14.04
image = "%s"
type = "C1"
tags = [ "terraform-test" ]
security_group = "${scaleway_security_group.blue.id}"
}`, armImageIdentifier)
var testAccCheckScalewayServerConfig_SecurityGroup_Update = fmt.Sprintf(`
resource "scaleway_security_group" "blue" {
name = "blue"
description = "blue"
}
resource "scaleway_security_group" "red" {
name = "red"
description = "red"
}
resource "scaleway_server" "base" {
name = "test"
# ubuntu 14.04
image = "%s"
type = "C1"
tags = [ "terraform-test" ]
security_group = "${scaleway_security_group.red.id}"
}`, armImageIdentifier)

View File

@ -98,7 +98,7 @@ func (e ScalewayAPIError) Error() string {
"Message": e.Message,
"APIMessage": e.APIMessage,
} {
fmt.Fprintf(&b, " %-30s %s", fmt.Sprintf("%s: ", k), v)
fmt.Fprintf(&b, "%s: %v ", k, v)
}
return b.String()
}
@ -420,12 +420,12 @@ type ScalewayGetSecurityGroup struct {
// ScalewayIPDefinition represents the IP's fields
type ScalewayIPDefinition struct {
Organization string `json:"organization"`
Reverse string `json:"reverse"`
Reverse *string `json:"reverse"`
ID string `json:"id"`
Server struct {
Server *struct {
Identifier string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
} `json:"server,omitempty"`
} `json:"server"`
Address string `json:"address"`
}
@ -448,11 +448,20 @@ type ScalewaySecurityGroup struct {
Name string `json:"name,omitempty"`
}
// ScalewayNewSecurityGroup definition POST/PUT request /security_groups
// ScalewayNewSecurityGroup definition POST request /security_groups
type ScalewayNewSecurityGroup struct {
Organization string `json:"organization"`
Name string `json:"name"`
Description string `json:"description"`
OrganizationDefault bool `json:"organization_default"`
}
// ScalewayUpdateSecurityGroup definition PUT request /security_groups
type ScalewayUpdateSecurityGroup struct {
Organization string `json:"organization"`
Name string `json:"name"`
Description string `json:"description"`
OrganizationDefault bool `json:"organization_default"`
}
// ScalewayServer represents a Scaleway server
@ -584,6 +593,8 @@ type ScalewayServerDefinition struct {
PublicIP string `json:"public_ip,omitempty"`
EnableIPV6 bool `json:"enable_ipv6,omitempty"`
SecurityGroup string `json:"security_group,omitempty"`
}
// ScalewayOneServer represents the response of a GET /servers/UUID API call
@ -832,27 +843,26 @@ type MarketImages struct {
// NewScalewayAPI creates a ready-to-use ScalewayAPI client
func NewScalewayAPI(organization, token, userAgent string, options ...func(*ScalewayAPI)) (*ScalewayAPI, error) {
cache, err := NewScalewayCache()
if err != nil {
return nil, err
}
s := &ScalewayAPI{
// exposed
Organization: organization,
Token: token,
Cache: cache,
Logger: NewDefaultLogger(),
verbose: os.Getenv("SCW_VERBOSE_API") != "",
password: "",
userAgent: userAgent,
// internal
client: &http.Client{},
verbose: os.Getenv("SCW_VERBOSE_API") != "",
password: "",
userAgent: userAgent,
}
for _, option := range options {
option(s)
}
cache, err := NewScalewayCache(func() { s.Logger.Debugf("Writing cache file to disk") })
if err != nil {
return nil, err
}
s.Cache = cache
if os.Getenv("SCW_TLSVERIFY") == "0" {
s.client.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
@ -1273,62 +1283,77 @@ func (s *ScalewayAPI) PutVolume(volumeID string, definition ScalewayVolumePutDef
// ResolveServer attempts to find a matching Identifier for the input string
func (s *ScalewayAPI) ResolveServer(needle string) (ScalewayResolverResults, error) {
servers := s.Cache.LookUpServers(needle, true)
servers, err := s.Cache.LookUpServers(needle, true)
if err != nil {
return servers, err
}
if len(servers) == 0 {
if _, err := s.GetServers(true, 0); err != nil {
if _, err = s.GetServers(true, 0); err != nil {
return nil, err
}
servers = s.Cache.LookUpServers(needle, true)
servers, err = s.Cache.LookUpServers(needle, true)
}
return servers, nil
return servers, err
}
// ResolveVolume attempts to find a matching Identifier for the input string
func (s *ScalewayAPI) ResolveVolume(needle string) (ScalewayResolverResults, error) {
volumes := s.Cache.LookUpVolumes(needle, true)
volumes, err := s.Cache.LookUpVolumes(needle, true)
if err != nil {
return volumes, err
}
if len(volumes) == 0 {
if _, err := s.GetVolumes(); err != nil {
if _, err = s.GetVolumes(); err != nil {
return nil, err
}
volumes = s.Cache.LookUpVolumes(needle, true)
volumes, err = s.Cache.LookUpVolumes(needle, true)
}
return volumes, nil
return volumes, err
}
// ResolveSnapshot attempts to find a matching Identifier for the input string
func (s *ScalewayAPI) ResolveSnapshot(needle string) (ScalewayResolverResults, error) {
snapshots := s.Cache.LookUpSnapshots(needle, true)
snapshots, err := s.Cache.LookUpSnapshots(needle, true)
if err != nil {
return snapshots, err
}
if len(snapshots) == 0 {
if _, err := s.GetSnapshots(); err != nil {
if _, err = s.GetSnapshots(); err != nil {
return nil, err
}
snapshots = s.Cache.LookUpSnapshots(needle, true)
snapshots, err = s.Cache.LookUpSnapshots(needle, true)
}
return snapshots, nil
return snapshots, err
}
// ResolveImage attempts to find a matching Identifier for the input string
func (s *ScalewayAPI) ResolveImage(needle string) (ScalewayResolverResults, error) {
images := s.Cache.LookUpImages(needle, true)
images, err := s.Cache.LookUpImages(needle, true)
if err != nil {
return images, err
}
if len(images) == 0 {
if _, err := s.GetImages(); err != nil {
if _, err = s.GetImages(); err != nil {
return nil, err
}
images = s.Cache.LookUpImages(needle, true)
images, err = s.Cache.LookUpImages(needle, true)
}
return images, nil
return images, err
}
// ResolveBootscript attempts to find a matching Identifier for the input string
func (s *ScalewayAPI) ResolveBootscript(needle string) (ScalewayResolverResults, error) {
bootscripts := s.Cache.LookUpBootscripts(needle, true)
bootscripts, err := s.Cache.LookUpBootscripts(needle, true)
if err != nil {
return bootscripts, err
}
if len(bootscripts) == 0 {
if _, err := s.GetBootscripts(); err != nil {
if _, err = s.GetBootscripts(); err != nil {
return nil, err
}
bootscripts = s.Cache.LookUpBootscripts(needle, true)
bootscripts, err = s.Cache.LookUpBootscripts(needle, true)
}
return bootscripts, nil
return bootscripts, err
}
// GetImages gets the list of images from the ScalewayAPI
@ -2154,7 +2179,7 @@ func (s *ScalewayAPI) DeleteSecurityGroup(securityGroupID string) error {
}
// PutSecurityGroup updates a SecurityGroup
func (s *ScalewayAPI) PutSecurityGroup(group ScalewayNewSecurityGroup, securityGroupID string) error {
func (s *ScalewayAPI) PutSecurityGroup(group ScalewayUpdateSecurityGroup, securityGroupID string) error {
resp, err := s.PutResponse(ComputeAPI, fmt.Sprintf("security_groups/%s", securityGroupID), group)
if resp != nil {
defer resp.Body.Close()
@ -2313,6 +2338,24 @@ func (s *ScalewayAPI) AttachIP(ipID, serverID string) error {
return err
}
// DetachIP detaches an IP from a server
func (s *ScalewayAPI) DetachIP(ipID string) error {
ip, err := s.GetIP(ipID)
if err != nil {
return err
}
ip.IP.Server = nil
resp, err := s.PutResponse(ComputeAPI, fmt.Sprintf("ips/%s", ipID), ip.IP)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return err
}
_, err = s.handleHTTPError([]int{200}, resp)
return err
}
// DeleteIP deletes an IP
func (s *ScalewayAPI) DeleteIP(ipID string) error {
resp, err := s.DeleteResponse(ComputeAPI, fmt.Sprintf("ips/%s", ipID))
@ -2322,11 +2365,8 @@ func (s *ScalewayAPI) DeleteIP(ipID string) error {
if err != nil {
return err
}
if _, err := s.handleHTTPError([]int{204}, resp); err != nil {
_, err = s.handleHTTPError([]int{204}, resp)
return err
}
return nil
}
// GetIP returns a ScalewayGetIP

View File

@ -8,7 +8,6 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
@ -60,6 +59,8 @@ type ScalewayCache struct {
// Lock allows ScalewayCache to be used concurrently
Lock sync.Mutex `json:"-"`
hookSave func()
}
const (
@ -92,16 +93,16 @@ type ScalewayResolverResult struct {
type ScalewayResolverResults []ScalewayResolverResult
// NewScalewayResolverResult returns a new ScalewayResolverResult
func NewScalewayResolverResult(Identifier, Name, Arch string, Type int) ScalewayResolverResult {
func NewScalewayResolverResult(Identifier, Name, Arch string, Type int) (ScalewayResolverResult, error) {
if err := anonuuid.IsUUID(Identifier); err != nil {
log.Fatal(err)
return ScalewayResolverResult{}, err
}
return ScalewayResolverResult{
Identifier: Identifier,
Type: Type,
Name: Name,
Arch: Arch,
}
}, nil
}
func (s ScalewayResolverResults) Len() int {
@ -160,7 +161,10 @@ REDO:
}
// NewScalewayCache loads a per-user cache
func NewScalewayCache() (*ScalewayCache, error) {
func NewScalewayCache(hookSave func()) (*ScalewayCache, error) {
var cache ScalewayCache
cache.hookSave = hookSave
homeDir := os.Getenv("HOME") // *nix
if homeDir == "" { // Windows
homeDir = os.Getenv("USERPROFILE")
@ -169,7 +173,6 @@ func NewScalewayCache() (*ScalewayCache, error) {
homeDir = "/tmp"
}
cachePath := filepath.Join(homeDir, ".scw-cache.db")
var cache ScalewayCache
cache.Path = cachePath
_, err := os.Stat(cachePath)
if os.IsNotExist(err) {
@ -210,13 +213,13 @@ func NewScalewayCache() (*ScalewayCache, error) {
}
// Clear removes all information from the cache
func (s *ScalewayCache) Clear() {
s.Images = make(map[string][CacheMaxfield]string)
s.Snapshots = make(map[string][CacheMaxfield]string)
s.Volumes = make(map[string][CacheMaxfield]string)
s.Bootscripts = make(map[string][CacheMaxfield]string)
s.Servers = make(map[string][CacheMaxfield]string)
s.Modified = true
func (c *ScalewayCache) Clear() {
c.Images = make(map[string][CacheMaxfield]string)
c.Snapshots = make(map[string][CacheMaxfield]string)
c.Volumes = make(map[string][CacheMaxfield]string)
c.Bootscripts = make(map[string][CacheMaxfield]string)
c.Servers = make(map[string][CacheMaxfield]string)
c.Modified = true
}
// Flush flushes the cache database
@ -229,8 +232,7 @@ func (c *ScalewayCache) Save() error {
c.Lock.Lock()
defer c.Lock.Unlock()
log.Printf("Writing cache file to disk")
c.hookSave()
if c.Modified {
file, err := ioutil.TempFile(filepath.Dir(c.Path), filepath.Base(c.Path))
if err != nil {
@ -259,15 +261,19 @@ func (s *ScalewayResolverResult) ComputeRankMatch(needle string) {
}
// LookUpImages attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpImages(needle string, acceptUUID bool) ScalewayResolverResults {
func (c *ScalewayCache) LookUpImages(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
c.Lock.Lock()
defer c.Lock.Unlock()
var res ScalewayResolverResults
var exactMatches ScalewayResolverResults
if acceptUUID && anonuuid.IsUUID(needle) == nil {
if fields, ok := c.Images[needle]; ok {
entry := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierImage)
entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierImage)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
res = append(res, entry)
}
@ -276,41 +282,53 @@ func (c *ScalewayCache) LookUpImages(needle string, acceptUUID bool) ScalewayRes
needle = regexp.MustCompile(`^user/`).ReplaceAllString(needle, "")
// FIXME: if 'user/' is in needle, only watch for a user image
nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
var exactMatches ScalewayResolverResults
for identifier, fields := range c.Images {
if fields[CacheTitle] == needle {
entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierImage)
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierImage)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
exactMatches = append(exactMatches, entry)
}
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierImage)
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierImage)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
res = append(res, entry)
} else if strings.HasPrefix(fields[CacheMarketPlaceUUID], needle) || nameRegex.MatchString(fields[CacheMarketPlaceUUID]) {
entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierImage)
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierImage)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
res = append(res, entry)
}
}
if len(exactMatches) == 1 {
return exactMatches
return exactMatches, nil
}
return removeDuplicatesResults(res)
return removeDuplicatesResults(res), nil
}
// LookUpSnapshots attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpSnapshots(needle string, acceptUUID bool) ScalewayResolverResults {
func (c *ScalewayCache) LookUpSnapshots(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
c.Lock.Lock()
defer c.Lock.Unlock()
var res ScalewayResolverResults
var exactMatches ScalewayResolverResults
if acceptUUID && anonuuid.IsUUID(needle) == nil {
if fields, ok := c.Snapshots[needle]; ok {
entry := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierSnapshot)
entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierSnapshot)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
res = append(res, entry)
}
@ -318,136 +336,168 @@ func (c *ScalewayCache) LookUpSnapshots(needle string, acceptUUID bool) Scaleway
needle = regexp.MustCompile(`^user/`).ReplaceAllString(needle, "")
nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
var exactMatches ScalewayResolverResults
for identifier, fields := range c.Snapshots {
if fields[CacheTitle] == needle {
entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierSnapshot)
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierSnapshot)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
exactMatches = append(exactMatches, entry)
}
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierSnapshot)
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierSnapshot)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
res = append(res, entry)
}
}
if len(exactMatches) == 1 {
return exactMatches
return exactMatches, nil
}
return removeDuplicatesResults(res)
return removeDuplicatesResults(res), nil
}
// LookUpVolumes attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpVolumes(needle string, acceptUUID bool) ScalewayResolverResults {
func (c *ScalewayCache) LookUpVolumes(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
c.Lock.Lock()
defer c.Lock.Unlock()
var res ScalewayResolverResults
var exactMatches ScalewayResolverResults
if acceptUUID && anonuuid.IsUUID(needle) == nil {
if fields, ok := c.Volumes[needle]; ok {
entry := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierVolume)
entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierVolume)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
res = append(res, entry)
}
}
nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
var exactMatches ScalewayResolverResults
for identifier, fields := range c.Volumes {
if fields[CacheTitle] == needle {
entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierVolume)
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierVolume)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
exactMatches = append(exactMatches, entry)
}
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierVolume)
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierVolume)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
res = append(res, entry)
}
}
if len(exactMatches) == 1 {
return exactMatches
return exactMatches, nil
}
return removeDuplicatesResults(res)
return removeDuplicatesResults(res), nil
}
// LookUpBootscripts attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpBootscripts(needle string, acceptUUID bool) ScalewayResolverResults {
func (c *ScalewayCache) LookUpBootscripts(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
c.Lock.Lock()
defer c.Lock.Unlock()
var res ScalewayResolverResults
var exactMatches ScalewayResolverResults
if acceptUUID && anonuuid.IsUUID(needle) == nil {
if fields, ok := c.Bootscripts[needle]; ok {
entry := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierBootscript)
entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierBootscript)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
res = append(res, entry)
}
}
nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
var exactMatches ScalewayResolverResults
for identifier, fields := range c.Bootscripts {
if fields[CacheTitle] == needle {
entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierBootscript)
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierBootscript)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
exactMatches = append(exactMatches, entry)
}
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierBootscript)
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierBootscript)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
res = append(res, entry)
}
}
if len(exactMatches) == 1 {
return exactMatches
return exactMatches, nil
}
return removeDuplicatesResults(res)
return removeDuplicatesResults(res), nil
}
// LookUpServers attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpServers(needle string, acceptUUID bool) ScalewayResolverResults {
func (c *ScalewayCache) LookUpServers(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
c.Lock.Lock()
defer c.Lock.Unlock()
var res ScalewayResolverResults
var exactMatches ScalewayResolverResults
if acceptUUID && anonuuid.IsUUID(needle) == nil {
if fields, ok := c.Servers[needle]; ok {
entry := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierServer)
entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierServer)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
res = append(res, entry)
}
}
nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
var exactMatches ScalewayResolverResults
for identifier, fields := range c.Servers {
if fields[CacheTitle] == needle {
entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierServer)
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierServer)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
exactMatches = append(exactMatches, entry)
}
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierServer)
entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierServer)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
res = append(res, entry)
}
}
if len(exactMatches) == 1 {
return exactMatches
return exactMatches, nil
}
return removeDuplicatesResults(res)
return removeDuplicatesResults(res), nil
}
// removeDuplicatesResults transforms an array into a unique array
@ -492,52 +542,86 @@ func parseNeedle(input string) (identifierType int, needle string) {
}
// LookUpIdentifiers attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpIdentifiers(needle string) ScalewayResolverResults {
func (c *ScalewayCache) LookUpIdentifiers(needle string) (ScalewayResolverResults, error) {
results := ScalewayResolverResults{}
identifierType, needle := parseNeedle(needle)
if identifierType&(IdentifierUnknown|IdentifierServer) > 0 {
for _, result := range c.LookUpServers(needle, false) {
entry := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, IdentifierServer)
servers, err := c.LookUpServers(needle, false)
if err != nil {
return ScalewayResolverResults{}, err
}
for _, result := range servers {
entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, IdentifierServer)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
results = append(results, entry)
}
}
if identifierType&(IdentifierUnknown|IdentifierImage) > 0 {
for _, result := range c.LookUpImages(needle, false) {
entry := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, IdentifierImage)
images, err := c.LookUpImages(needle, false)
if err != nil {
return ScalewayResolverResults{}, err
}
for _, result := range images {
entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, IdentifierImage)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
results = append(results, entry)
}
}
if identifierType&(IdentifierUnknown|IdentifierSnapshot) > 0 {
for _, result := range c.LookUpSnapshots(needle, false) {
entry := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, IdentifierSnapshot)
snapshots, err := c.LookUpSnapshots(needle, false)
if err != nil {
return ScalewayResolverResults{}, err
}
for _, result := range snapshots {
entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, IdentifierSnapshot)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
results = append(results, entry)
}
}
if identifierType&(IdentifierUnknown|IdentifierVolume) > 0 {
for _, result := range c.LookUpVolumes(needle, false) {
entry := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, IdentifierVolume)
volumes, err := c.LookUpVolumes(needle, false)
if err != nil {
return ScalewayResolverResults{}, err
}
for _, result := range volumes {
entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, IdentifierVolume)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
results = append(results, entry)
}
}
if identifierType&(IdentifierUnknown|IdentifierBootscript) > 0 {
for _, result := range c.LookUpBootscripts(needle, false) {
entry := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, IdentifierBootscript)
bootscripts, err := c.LookUpBootscripts(needle, false)
if err != nil {
return ScalewayResolverResults{}, err
}
for _, result := range bootscripts {
entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, IdentifierBootscript)
if err != nil {
return ScalewayResolverResults{}, err
}
entry.ComputeRankMatch(needle)
results = append(results, entry)
}
}
return results
return results, nil
}
// InsertServer registers a server in the cache

View File

@ -32,18 +32,46 @@ type defaultLogger struct {
}
func (l *defaultLogger) LogHTTP(r *http.Request) {
l.Printf("%s %s\n", r.Method, r.URL.Path)
l.Printf("%s %s\n", r.Method, r.URL.RawPath)
}
func (l *defaultLogger) Fatalf(format string, v ...interface{}) {
l.Printf("[FATAL] %s\n", fmt.Sprintf(format, v))
os.Exit(1)
}
func (l *defaultLogger) Debugf(format string, v ...interface{}) {
l.Printf("[DEBUG] %s\n", fmt.Sprintf(format, v))
}
func (l *defaultLogger) Infof(format string, v ...interface{}) {
l.Printf("[INFO ] %s\n", fmt.Sprintf(format, v))
}
func (l *defaultLogger) Warnf(format string, v ...interface{}) {
l.Printf("[WARN ] %s\n", fmt.Sprintf(format, v))
}
type disableLogger struct {
}
// NewDisableLogger returns a logger which is configured to do nothing
func NewDisableLogger() Logger {
return &disableLogger{}
}
func (d *disableLogger) LogHTTP(r *http.Request) {
}
func (d *disableLogger) Fatalf(format string, v ...interface{}) {
panic(fmt.Sprintf(format, v))
}
func (d *disableLogger) Debugf(format string, v ...interface{}) {
}
func (d *disableLogger) Infof(format string, v ...interface{}) {
}
func (d *disableLogger) Warnf(format string, v ...interface{}) {
}

View File

@ -28,11 +28,18 @@ The following arguments are supported:
* `name` - (Required) name of ARM server
* `image` - (Required) base image of ARM server
* `type` - (Required) type of ARM server
* `bootscript` - (Optional) server bootscript
* `tags` - (Optional) list of tags for server
* `enable_ipv6` - (Optional) enable ipv6
* `dynamic_ip_required` - (Optional) make server publicly available
* `security_group` - (Optional) assign security group to server
Field `name`, `type` are editable.
Field `name`, `type`, `tags`, `dynamic_ip_required`, `security_group` are editable.
## Attributes Reference
The following attributes are exported:
* `id` - id of the new resource
* `private_ip` - private ip of the new resource
* `public_ip` - public ip of the new resource