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) d.Set("ip", resp.IP.Address)
d.Set("server", resp.IP.Server.Identifier) if resp.IP.Server != nil {
d.Set("server", resp.IP.Server.Identifier)
}
return nil return nil
} }

View File

@ -112,7 +112,11 @@ func testAccCheckScalewayIPAttachment(n string, check func(string) bool, msg str
return err 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) 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 { func resourceScalewaySecurityGroupUpdate(d *schema.ResourceData, m interface{}) error {
scaleway := m.(*Client).scaleway scaleway := m.(*Client).scaleway
var req = api.ScalewayNewSecurityGroup{ var req = api.ScalewayUpdateSecurityGroup{
Organization: scaleway.Organization, Organization: scaleway.Organization,
Name: d.Get("name").(string), Name: d.Get("name").(string),
Description: d.Get("description").(string), Description: d.Get("description").(string),
@ -110,6 +110,15 @@ func resourceScalewaySecurityGroupDelete(d *schema.ResourceData, m interface{})
err := scaleway.DeleteSecurityGroup(d.Id()) err := scaleway.DeleteSecurityGroup(d.Id())
if err != nil { 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 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()) err := scaleway.DeleteSecurityGroupRule(d.Get("security_group").(string), d.Id())
if err != nil { 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 return err
} }

View File

@ -40,11 +40,24 @@ func resourceScalewayServer() *schema.Resource {
}, },
Optional: true, 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, Type: schema.TypeString,
Computed: true, Computed: true,
}, },
"ipv4_address_public": &schema.Schema{ "public_ip": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
}, },
@ -53,10 +66,6 @@ func resourceScalewayServer() *schema.Resource {
Optional: true, Optional: true,
Computed: true, Computed: true,
}, },
"dynamic_ip_required": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"state_detail": &schema.Schema{ "state_detail": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
@ -70,9 +79,11 @@ func resourceScalewayServerCreate(d *schema.ResourceData, m interface{}) error {
image := d.Get("image").(string) image := d.Get("image").(string)
var server = api.ScalewayServerDefinition{ var server = api.ScalewayServerDefinition{
Name: d.Get("name").(string), Name: d.Get("name").(string),
Image: String(image), Image: String(image),
Organization: scaleway.Organization, Organization: scaleway.Organization,
EnableIPV6: d.Get("enable_ipv6").(bool),
SecurityGroup: d.Get("security_group").(string),
} }
server.DynamicIPRequired = Bool(d.Get("dynamic_ip_required").(bool)) server.DynamicIPRequired = Bool(d.Get("dynamic_ip_required").(bool))
@ -127,8 +138,9 @@ func resourceScalewayServerRead(d *schema.ResourceData, m interface{}) error {
return err return err
} }
d.Set("ipv4_address_private", server.PrivateIP) d.Set("private_ip", server.PrivateIP)
d.Set("ipv4_address_public", server.PublicAddress.IP) d.Set("public_ip", server.PublicAddress.IP)
d.Set("state", server.State) d.Set("state", server.State)
d.Set("state_detail", server.StateDetail) d.Set("state_detail", server.StateDetail)
d.Set("tags", server.Tags) 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") { if d.HasChange("dynamic_ip_required") {
req.DynamicIPRequired = Bool(d.Get("dynamic_ip_required").(bool)) 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 { if err := scaleway.PatchServer(d.Id(), req); err != nil {
return fmt.Errorf("Failed patching scaleway server: %q", err) 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 { func testAccCheckScalewayServerDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*Client).scaleway 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 { func testAccCheckScalewayServerExists(n string) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n] rs, ok := s.RootModule().Resources[n]
@ -114,3 +160,43 @@ resource "scaleway_server" "base" {
type = "C1" type = "C1"
tags = [ "terraform-test" ] tags = [ "terraform-test" ]
}`, armImageIdentifier) }`, 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, "Message": e.Message,
"APIMessage": e.APIMessage, "APIMessage": e.APIMessage,
} { } {
fmt.Fprintf(&b, " %-30s %s", fmt.Sprintf("%s: ", k), v) fmt.Fprintf(&b, "%s: %v ", k, v)
} }
return b.String() return b.String()
} }
@ -419,13 +419,13 @@ type ScalewayGetSecurityGroup struct {
// ScalewayIPDefinition represents the IP's fields // ScalewayIPDefinition represents the IP's fields
type ScalewayIPDefinition struct { type ScalewayIPDefinition struct {
Organization string `json:"organization"` Organization string `json:"organization"`
Reverse string `json:"reverse"` Reverse *string `json:"reverse"`
ID string `json:"id"` ID string `json:"id"`
Server struct { Server *struct {
Identifier string `json:"id,omitempty"` Identifier string `json:"id,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
} `json:"server,omitempty"` } `json:"server"`
Address string `json:"address"` Address string `json:"address"`
} }
@ -448,11 +448,20 @@ type ScalewaySecurityGroup struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
} }
// ScalewayNewSecurityGroup definition POST/PUT request /security_groups // ScalewayNewSecurityGroup definition POST request /security_groups
type ScalewayNewSecurityGroup struct { type ScalewayNewSecurityGroup struct {
Organization string `json:"organization"` Organization string `json:"organization"`
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` 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 // ScalewayServer represents a Scaleway server
@ -584,6 +593,8 @@ type ScalewayServerDefinition struct {
PublicIP string `json:"public_ip,omitempty"` PublicIP string `json:"public_ip,omitempty"`
EnableIPV6 bool `json:"enable_ipv6,omitempty"` EnableIPV6 bool `json:"enable_ipv6,omitempty"`
SecurityGroup string `json:"security_group,omitempty"`
} }
// ScalewayOneServer represents the response of a GET /servers/UUID API call // 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 // NewScalewayAPI creates a ready-to-use ScalewayAPI client
func NewScalewayAPI(organization, token, userAgent string, options ...func(*ScalewayAPI)) (*ScalewayAPI, error) { func NewScalewayAPI(organization, token, userAgent string, options ...func(*ScalewayAPI)) (*ScalewayAPI, error) {
cache, err := NewScalewayCache()
if err != nil {
return nil, err
}
s := &ScalewayAPI{ s := &ScalewayAPI{
// exposed // exposed
Organization: organization, Organization: organization,
Token: token, Token: token,
Cache: cache,
Logger: NewDefaultLogger(), Logger: NewDefaultLogger(),
verbose: os.Getenv("SCW_VERBOSE_API") != "",
password: "",
userAgent: userAgent,
// internal // internal
client: &http.Client{}, client: &http.Client{},
verbose: os.Getenv("SCW_VERBOSE_API") != "",
password: "",
userAgent: userAgent,
} }
for _, option := range options { for _, option := range options {
option(s) 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" { if os.Getenv("SCW_TLSVERIFY") == "0" {
s.client.Transport = &http.Transport{ s.client.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 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 // ResolveServer attempts to find a matching Identifier for the input string
func (s *ScalewayAPI) ResolveServer(needle string) (ScalewayResolverResults, error) { 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 len(servers) == 0 {
if _, err := s.GetServers(true, 0); err != nil { if _, err = s.GetServers(true, 0); err != nil {
return nil, err 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 // ResolveVolume attempts to find a matching Identifier for the input string
func (s *ScalewayAPI) ResolveVolume(needle string) (ScalewayResolverResults, error) { 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 len(volumes) == 0 {
if _, err := s.GetVolumes(); err != nil { if _, err = s.GetVolumes(); err != nil {
return nil, err 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 // ResolveSnapshot attempts to find a matching Identifier for the input string
func (s *ScalewayAPI) ResolveSnapshot(needle string) (ScalewayResolverResults, error) { 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 len(snapshots) == 0 {
if _, err := s.GetSnapshots(); err != nil { if _, err = s.GetSnapshots(); err != nil {
return nil, err 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 // ResolveImage attempts to find a matching Identifier for the input string
func (s *ScalewayAPI) ResolveImage(needle string) (ScalewayResolverResults, error) { 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 len(images) == 0 {
if _, err := s.GetImages(); err != nil { if _, err = s.GetImages(); err != nil {
return nil, err 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 // ResolveBootscript attempts to find a matching Identifier for the input string
func (s *ScalewayAPI) ResolveBootscript(needle string) (ScalewayResolverResults, error) { 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 len(bootscripts) == 0 {
if _, err := s.GetBootscripts(); err != nil { if _, err = s.GetBootscripts(); err != nil {
return nil, err 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 // GetImages gets the list of images from the ScalewayAPI
@ -2154,7 +2179,7 @@ func (s *ScalewayAPI) DeleteSecurityGroup(securityGroupID string) error {
} }
// PutSecurityGroup updates a SecurityGroup // 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) resp, err := s.PutResponse(ComputeAPI, fmt.Sprintf("security_groups/%s", securityGroupID), group)
if resp != nil { if resp != nil {
defer resp.Body.Close() defer resp.Body.Close()
@ -2313,6 +2338,24 @@ func (s *ScalewayAPI) AttachIP(ipID, serverID string) error {
return err 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 // DeleteIP deletes an IP
func (s *ScalewayAPI) DeleteIP(ipID string) error { func (s *ScalewayAPI) DeleteIP(ipID string) error {
resp, err := s.DeleteResponse(ComputeAPI, fmt.Sprintf("ips/%s", ipID)) resp, err := s.DeleteResponse(ComputeAPI, fmt.Sprintf("ips/%s", ipID))
@ -2322,11 +2365,8 @@ func (s *ScalewayAPI) DeleteIP(ipID string) error {
if err != nil { if err != nil {
return err return err
} }
_, err = s.handleHTTPError([]int{204}, resp)
if _, err := s.handleHTTPError([]int{204}, resp); err != nil { return err
return err
}
return nil
} }
// GetIP returns a ScalewayGetIP // GetIP returns a ScalewayGetIP

View File

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

View File

@ -32,18 +32,46 @@ type defaultLogger struct {
} }
func (l *defaultLogger) LogHTTP(r *http.Request) { 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{}) { func (l *defaultLogger) Fatalf(format string, v ...interface{}) {
l.Printf("[FATAL] %s\n", fmt.Sprintf(format, v)) l.Printf("[FATAL] %s\n", fmt.Sprintf(format, v))
os.Exit(1) os.Exit(1)
} }
func (l *defaultLogger) Debugf(format string, v ...interface{}) { func (l *defaultLogger) Debugf(format string, v ...interface{}) {
l.Printf("[DEBUG] %s\n", fmt.Sprintf(format, v)) l.Printf("[DEBUG] %s\n", fmt.Sprintf(format, v))
} }
func (l *defaultLogger) Infof(format string, v ...interface{}) { func (l *defaultLogger) Infof(format string, v ...interface{}) {
l.Printf("[INFO ] %s\n", fmt.Sprintf(format, v)) l.Printf("[INFO ] %s\n", fmt.Sprintf(format, v))
} }
func (l *defaultLogger) Warnf(format string, v ...interface{}) { func (l *defaultLogger) Warnf(format string, v ...interface{}) {
l.Printf("[WARN ] %s\n", fmt.Sprintf(format, v)) 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 * `name` - (Required) name of ARM server
* `image` - (Required) base image of ARM server * `image` - (Required) base image of ARM server
* `type` - (Required) type 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 ## Attributes Reference
The following attributes are exported: The following attributes are exported:
* `id` - id of the new resource * `id` - id of the new resource
* `private_ip` - private ip of the new resource
* `public_ip` - public ip of the new resource