Add scaleway provider (#7331)
* Add scaleway provider this PR allows the entire scaleway stack to be managed with terraform example usage looks like this: ``` provider "scaleway" { api_key = "snap" organization = "snip" } resource "scaleway_ip" "base" { server = "${scaleway_server.base.id}" } resource "scaleway_server" "base" { name = "test" # ubuntu 14.04 image = "aecaed73-51a5-4439-a127-6d8229847145" type = "C2S" } resource "scaleway_volume" "test" { name = "test" size_in_gb = 20 type = "l_ssd" } resource "scaleway_volume_attachment" "test" { server = "${scaleway_server.base.id}" volume = "${scaleway_volume.test.id}" } resource "scaleway_security_group" "base" { name = "public" description = "public gateway" } resource "scaleway_security_group_rule" "http-ingress" { security_group = "${scaleway_security_group.base.id}" action = "accept" direction = "inbound" ip_range = "0.0.0.0/0" protocol = "TCP" port = 80 } resource "scaleway_security_group_rule" "http-egress" { security_group = "${scaleway_security_group.base.id}" action = "accept" direction = "outbound" ip_range = "0.0.0.0/0" protocol = "TCP" port = 80 } ``` Note that volume attachments require the server to be stopped, which can lead to downtimes of you attach new volumes to already used servers * Update IP read to handle 404 gracefully * Read back resource on update * Ensure IP detachment works as expected Sadly this is not part of the official scaleway api just yet * Adjust detachIP helper based on feedback from @QuentinPerez in https://github.com/scaleway/scaleway-cli/pull/378 * Cleanup documentation * Rename api_key to access_key following @stack72 suggestion and rename the provider api_key for more clarity * Make tests less chatty by using custom logger
This commit is contained in:
parent
0ce6337a2a
commit
9081cabd6e
|
@ -0,0 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/builtin/providers/scaleway"
|
||||
"github.com/hashicorp/terraform/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.Serve(&plugin.ServeOpts{
|
||||
ProviderFunc: scaleway.Provider,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
"github.com/scaleway/scaleway-cli/pkg/scwversion"
|
||||
)
|
||||
|
||||
// Config contains scaleway configuration values
|
||||
type Config struct {
|
||||
Organization string
|
||||
APIKey string
|
||||
}
|
||||
|
||||
// Client contains scaleway api clients
|
||||
type Client struct {
|
||||
scaleway *api.ScalewayAPI
|
||||
}
|
||||
|
||||
// Client configures and returns a fully initialized Scaleway client
|
||||
func (c *Config) Client() (*Client, error) {
|
||||
api, err := api.NewScalewayAPI(
|
||||
c.Organization,
|
||||
c.APIKey,
|
||||
scwversion.UserAgent(),
|
||||
func(s *api.ScalewayAPI) {
|
||||
s.Logger = newTerraformLogger()
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Client{api}, nil
|
||||
}
|
||||
|
||||
func newTerraformLogger() api.Logger {
|
||||
return &terraformLogger{}
|
||||
}
|
||||
|
||||
type terraformLogger struct {
|
||||
}
|
||||
|
||||
func (l *terraformLogger) LogHTTP(r *http.Request) {
|
||||
log.Printf("[DEBUG] %s %s\n", r.Method, r.URL.Path)
|
||||
}
|
||||
func (l *terraformLogger) Fatalf(format string, v ...interface{}) {
|
||||
log.Printf("[FATAL] %s\n", fmt.Sprintf(format, v))
|
||||
os.Exit(1)
|
||||
}
|
||||
func (l *terraformLogger) Debugf(format string, v ...interface{}) {
|
||||
log.Printf("[DEBUG] %s\n", fmt.Sprintf(format, v))
|
||||
}
|
||||
func (l *terraformLogger) Infof(format string, v ...interface{}) {
|
||||
log.Printf("[INFO ] %s\n", fmt.Sprintf(format, v))
|
||||
}
|
||||
func (l *terraformLogger) Warnf(format string, v ...interface{}) {
|
||||
log.Printf("[WARN ] %s\n", fmt.Sprintf(format, v))
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
)
|
||||
|
||||
// Bool returns a pointer to of the bool value passed in.
|
||||
func Bool(val bool) *bool {
|
||||
return &val
|
||||
}
|
||||
|
||||
// String returns a pointer to of the string value passed in.
|
||||
func String(val string) *string {
|
||||
return &val
|
||||
}
|
||||
|
||||
// DetachIP detaches an IP from a server
|
||||
func DetachIP(s *api.ScalewayAPI, ipID string) error {
|
||||
var update struct {
|
||||
Address string `json:"address"`
|
||||
ID string `json:"id"`
|
||||
Organization string `json:"organization"`
|
||||
}
|
||||
|
||||
ip, err := s.GetIP(ipID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
update.Address = ip.IP.Address
|
||||
update.ID = ip.IP.ID
|
||||
update.Organization = ip.IP.Organization
|
||||
|
||||
resp, err := s.PutResponse(api.ComputeAPI, fmt.Sprintf("ips/%s", ipID), update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// NOTE copied from github.com/scaleway/scaleway-cli/pkg/api/helpers.go
|
||||
// the helpers.go file pulls in quite a lot dependencies, and they're just convenience wrappers anyway
|
||||
|
||||
func deleteServerSafe(s *api.ScalewayAPI, serverID string) error {
|
||||
server, err := s.GetServer(serverID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if server.State != "stopped" {
|
||||
if err := s.PostServerAction(serverID, "poweroff"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := waitForServerState(s, serverID, "stopped"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.DeleteServer(serverID); err != nil {
|
||||
return err
|
||||
}
|
||||
if rootVolume, ok := server.Volumes["0"]; ok {
|
||||
if err := s.DeleteVolume(rootVolume.Identifier); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForServerState(s *api.ScalewayAPI, serverID string, targetState string) error {
|
||||
var server *api.ScalewayServer
|
||||
var err error
|
||||
|
||||
var currentState string
|
||||
|
||||
for {
|
||||
server, err = s.GetServer(serverID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if currentState != server.State {
|
||||
log.Printf("[DEBUG] Server changed state to %q\n", server.State)
|
||||
currentState = server.State
|
||||
}
|
||||
if server.State == targetState {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
// Provider returns a terraform.ResourceProvider.
|
||||
func Provider() terraform.ResourceProvider {
|
||||
return &schema.Provider{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"access_key": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("SCALEWAY_ACCESS_KEY", nil),
|
||||
Description: "The API key for Scaleway API operations.",
|
||||
},
|
||||
"organization": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("SCALEWAY_ORGANIZATION", nil),
|
||||
Description: "The Organization ID for Scaleway API operations.",
|
||||
},
|
||||
},
|
||||
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
"scaleway_server": resourceScalewayServer(),
|
||||
"scaleway_ip": resourceScalewayIP(),
|
||||
"scaleway_security_group": resourceScalewaySecurityGroup(),
|
||||
"scaleway_security_group_rule": resourceScalewaySecurityGroupRule(),
|
||||
"scaleway_volume": resourceScalewayVolume(),
|
||||
"scaleway_volume_attachment": resourceScalewayVolumeAttachment(),
|
||||
},
|
||||
|
||||
ConfigureFunc: providerConfigure,
|
||||
}
|
||||
}
|
||||
|
||||
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
||||
config := Config{
|
||||
Organization: d.Get("organization").(string),
|
||||
APIKey: d.Get("access_key").(string),
|
||||
}
|
||||
|
||||
return config.Client()
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
var testAccProviders map[string]terraform.ResourceProvider
|
||||
var testAccProvider *schema.Provider
|
||||
|
||||
func init() {
|
||||
testAccProvider = Provider().(*schema.Provider)
|
||||
testAccProviders = map[string]terraform.ResourceProvider{
|
||||
"scaleway": testAccProvider,
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider(t *testing.T) {
|
||||
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider_impl(t *testing.T) {
|
||||
var _ terraform.ResourceProvider = Provider()
|
||||
}
|
||||
|
||||
func testAccPreCheck(t *testing.T) {
|
||||
if v := os.Getenv("SCALEWAY_ORGANIZATION"); v == "" {
|
||||
t.Fatal("SCALEWAY_ORGANIZATION must be set for acceptance tests")
|
||||
}
|
||||
if v := os.Getenv("SCALEWAY_ACCESS_KEY"); v == "" {
|
||||
t.Fatal("SCALEWAY_ACCESS_KEY must be set for acceptance tests")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
)
|
||||
|
||||
func resourceScalewayIP() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceScalewayIPCreate,
|
||||
Read: resourceScalewayIPRead,
|
||||
Update: resourceScalewayIPUpdate,
|
||||
Delete: resourceScalewayIPDelete,
|
||||
Schema: map[string]*schema.Schema{
|
||||
"server": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"ip": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceScalewayIPCreate(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
resp, err := scaleway.NewIP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetId(resp.IP.ID)
|
||||
return resourceScalewayIPUpdate(d, m)
|
||||
}
|
||||
|
||||
func resourceScalewayIPRead(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
log.Printf("[DEBUG] Reading IP\n")
|
||||
|
||||
resp, err := scaleway.GetIP(d.Id())
|
||||
if err != nil {
|
||||
log.Printf("[DEBUG] Error reading ip: %q\n", err)
|
||||
if serr, ok := err.(api.ScalewayAPIError); ok {
|
||||
if serr.StatusCode == 404 {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
d.Set("ip", resp.IP.Address)
|
||||
d.Set("server", resp.IP.Server.Identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceScalewayIPUpdate(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
if d.HasChange("server") {
|
||||
if d.Get("server").(string) != "" {
|
||||
log.Printf("[DEBUG] Attaching IP %q to server %q\n", d.Id(), d.Get("server").(string))
|
||||
if err := scaleway.AttachIP(d.Id(), d.Get("server").(string)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
log.Printf("[DEBUG] Detaching IP %q\n", d.Id())
|
||||
return DetachIP(scaleway, d.Id())
|
||||
}
|
||||
}
|
||||
|
||||
return resourceScalewayIPRead(d, m)
|
||||
}
|
||||
|
||||
func resourceScalewayIPDelete(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
err := scaleway.DeleteIP(d.Id())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccScalewayIP_Basic(t *testing.T) {
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckScalewayIPDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCheckScalewayIPConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckScalewayIPExists("scaleway_ip.base"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: testAccCheckScalewayIPAttachConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckScalewayIPExists("scaleway_ip.base"),
|
||||
testAccCheckScalewayIPAttachment("scaleway_ip.base", func(serverID string) bool {
|
||||
return serverID != ""
|
||||
}, "attachment failed"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: testAccCheckScalewayIPConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckScalewayIPExists("scaleway_ip.base"),
|
||||
testAccCheckScalewayIPAttachment("scaleway_ip.base", func(serverID string) bool {
|
||||
return serverID == ""
|
||||
}, "detachment failed"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckScalewayIPDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*Client).scaleway
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "scaleway" {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := client.GetIP(rs.Primary.ID)
|
||||
|
||||
if err == nil {
|
||||
return fmt.Errorf("IP still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckScalewayIPAttributes() resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckScalewayIPExists(n string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No IP ID is set")
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*Client).scaleway
|
||||
ip, err := client.GetIP(rs.Primary.ID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ip.IP.ID != rs.Primary.ID {
|
||||
return fmt.Errorf("Record not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckScalewayIPAttachment(n string, check func(string) bool, msg string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No IP ID is set")
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*Client).scaleway
|
||||
ip, err := client.GetIP(rs.Primary.ID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !check(ip.IP.Server.Identifier) {
|
||||
return fmt.Errorf("IP check failed: %q", msg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var testAccCheckScalewayIPConfig = `
|
||||
resource "scaleway_ip" "base" {
|
||||
}
|
||||
`
|
||||
|
||||
var testAccCheckScalewayIPAttachConfig = fmt.Sprintf(`
|
||||
resource "scaleway_server" "base" {
|
||||
name = "test"
|
||||
# ubuntu 14.04
|
||||
image = "%s"
|
||||
type = "C1"
|
||||
state = "stopped"
|
||||
}
|
||||
|
||||
resource "scaleway_ip" "base" {
|
||||
server = "${scaleway_server.base.id}"
|
||||
}
|
||||
`, armImageIdentifier)
|
|
@ -0,0 +1,118 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
)
|
||||
|
||||
func resourceScalewaySecurityGroup() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceScalewaySecurityGroupCreate,
|
||||
Read: resourceScalewaySecurityGroupRead,
|
||||
Update: resourceScalewaySecurityGroupUpdate,
|
||||
Delete: resourceScalewaySecurityGroupDelete,
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"description": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceScalewaySecurityGroupCreate(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
|
||||
req := api.ScalewayNewSecurityGroup{
|
||||
Name: d.Get("name").(string),
|
||||
Description: d.Get("description").(string),
|
||||
Organization: scaleway.Organization,
|
||||
}
|
||||
|
||||
err := scaleway.PostSecurityGroup(req)
|
||||
if err != nil {
|
||||
if serr, ok := err.(api.ScalewayAPIError); ok {
|
||||
log.Printf("[DEBUG] Error creating security group: %q\n", serr.APIMessage)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := scaleway.GetSecurityGroups()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, group := range resp.SecurityGroups {
|
||||
if group.Name == req.Name {
|
||||
d.SetId(group.ID)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if d.Id() == "" {
|
||||
return fmt.Errorf("Failed to find created security group.")
|
||||
}
|
||||
|
||||
return resourceScalewaySecurityGroupRead(d, m)
|
||||
}
|
||||
|
||||
func resourceScalewaySecurityGroupRead(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
resp, err := scaleway.GetASecurityGroup(d.Id())
|
||||
|
||||
if err != nil {
|
||||
if serr, ok := err.(api.ScalewayAPIError); ok {
|
||||
log.Printf("[DEBUG] Error reading security group: %q\n", serr.APIMessage)
|
||||
|
||||
if serr.StatusCode == 404 {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
d.Set("name", resp.SecurityGroups.Name)
|
||||
d.Set("description", resp.SecurityGroups.Description)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceScalewaySecurityGroupUpdate(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
|
||||
var req = api.ScalewayNewSecurityGroup{
|
||||
Organization: scaleway.Organization,
|
||||
Name: d.Get("name").(string),
|
||||
Description: d.Get("description").(string),
|
||||
}
|
||||
|
||||
if err := scaleway.PutSecurityGroup(req, d.Id()); err != nil {
|
||||
log.Printf("[DEBUG] Error reading security group: %q\n", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return resourceScalewaySecurityGroupRead(d, m)
|
||||
}
|
||||
|
||||
func resourceScalewaySecurityGroupDelete(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
|
||||
err := scaleway.DeleteSecurityGroup(d.Id())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
)
|
||||
|
||||
func resourceScalewaySecurityGroupRule() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceScalewaySecurityGroupRuleCreate,
|
||||
Read: resourceScalewaySecurityGroupRuleRead,
|
||||
Update: resourceScalewaySecurityGroupRuleUpdate,
|
||||
Delete: resourceScalewaySecurityGroupRuleDelete,
|
||||
Schema: map[string]*schema.Schema{
|
||||
"security_group": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"action": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
|
||||
value := v.(string)
|
||||
if value != "accept" && value != "drop" {
|
||||
errors = append(errors, fmt.Errorf("%q must be one of 'accept', 'drop'", k))
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
"direction": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
|
||||
value := v.(string)
|
||||
if value != "inbound" && value != "outbound" {
|
||||
errors = append(errors, fmt.Errorf("%q must be one of 'inbound', 'outbound'", k))
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
"ip_range": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"protocol": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
|
||||
value := v.(string)
|
||||
if value != "ICMP" && value != "TCP" && value != "UDP" {
|
||||
errors = append(errors, fmt.Errorf("%q must be one of 'ICMP', 'TCP', 'UDP", k))
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
"port": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceScalewaySecurityGroupRuleCreate(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
|
||||
req := api.ScalewayNewSecurityGroupRule{
|
||||
Action: d.Get("action").(string),
|
||||
Direction: d.Get("direction").(string),
|
||||
IPRange: d.Get("ip_range").(string),
|
||||
Protocol: d.Get("protocol").(string),
|
||||
DestPortFrom: d.Get("port").(int),
|
||||
}
|
||||
|
||||
err := scaleway.PostSecurityGroupRule(d.Get("security_group").(string), req)
|
||||
if err != nil {
|
||||
if serr, ok := err.(api.ScalewayAPIError); ok {
|
||||
log.Printf("[DEBUG] Error creating Security Group Rule: %q\n", serr.APIMessage)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := scaleway.GetSecurityGroupRules(d.Get("security_group").(string))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, rule := range resp.Rules {
|
||||
if rule.Action == req.Action && rule.Direction == req.Direction && rule.IPRange == req.IPRange && rule.Protocol == req.Protocol {
|
||||
d.SetId(rule.ID)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if d.Id() == "" {
|
||||
return fmt.Errorf("Failed to find created security group rule")
|
||||
}
|
||||
|
||||
return resourceScalewaySecurityGroupRuleRead(d, m)
|
||||
}
|
||||
|
||||
func resourceScalewaySecurityGroupRuleRead(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
rule, err := scaleway.GetASecurityGroupRule(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
|
||||
}
|
||||
|
||||
d.Set("action", rule.Rules.Action)
|
||||
d.Set("direction", rule.Rules.Direction)
|
||||
d.Set("ip_range", rule.Rules.IPRange)
|
||||
d.Set("protocol", rule.Rules.Protocol)
|
||||
d.Set("port", rule.Rules.DestPortFrom)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceScalewaySecurityGroupRuleUpdate(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
|
||||
var req = api.ScalewayNewSecurityGroupRule{
|
||||
Action: d.Get("action").(string),
|
||||
Direction: d.Get("direction").(string),
|
||||
IPRange: d.Get("ip_range").(string),
|
||||
Protocol: d.Get("protocol").(string),
|
||||
DestPortFrom: d.Get("port").(int),
|
||||
}
|
||||
|
||||
if err := scaleway.PutSecurityGroupRule(req, d.Get("security_group").(string), d.Id()); err != nil {
|
||||
log.Printf("[DEBUG] error updating Security Group Rule: %q", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return resourceScalewaySecurityGroupRuleRead(d, m)
|
||||
}
|
||||
|
||||
func resourceScalewaySecurityGroupRuleDelete(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
|
||||
err := scaleway.DeleteSecurityGroupRule(d.Get("security_group").(string), d.Id())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
)
|
||||
|
||||
func TestAccScalewaySecurityGroupRule_Basic(t *testing.T) {
|
||||
var group api.ScalewaySecurityGroups
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckScalewaySecurityGroupRuleDestroy(&group),
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCheckScalewaySecurityGroupRuleConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckScalewaySecurityGroupsExists("scaleway_security_group.base", &group),
|
||||
resource.TestCheckResourceAttr("scaleway_security_group_rule.http", "action", "drop"),
|
||||
resource.TestCheckResourceAttr("scaleway_security_group_rule.http", "direction", "inbound"),
|
||||
resource.TestCheckResourceAttr("scaleway_security_group_rule.http", "ip_range", "0.0.0.0/0"),
|
||||
resource.TestCheckResourceAttr("scaleway_security_group_rule.http", "protocol", "TCP"),
|
||||
testAccCheckScalewaySecurityGroupRuleExists("scaleway_security_group_rule.http", &group),
|
||||
testAccCheckScalewaySecurityGroupRuleAttributes("scaleway_security_group_rule.http", &group),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckScalewaySecurityGroupsExists(n string, group *api.ScalewaySecurityGroups) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("Security Group Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No Security Group is set")
|
||||
}
|
||||
|
||||
conn := testAccProvider.Meta().(*Client).scaleway
|
||||
resp, err := conn.GetASecurityGroup(rs.Primary.ID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.SecurityGroups.ID == rs.Primary.ID {
|
||||
*group = resp.SecurityGroups
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("Security Group not found")
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckScalewaySecurityGroupRuleDestroy(group *api.ScalewaySecurityGroups) func(*terraform.State) error {
|
||||
return func(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*Client).scaleway
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "scaleway" {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := client.GetASecurityGroupRule(group.ID, rs.Primary.ID)
|
||||
|
||||
if err == nil {
|
||||
return fmt.Errorf("Security Group still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckScalewaySecurityGroupRuleAttributes(n string, group *api.ScalewaySecurityGroups) 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
|
||||
rule, err := client.GetASecurityGroupRule(group.ID, rs.Primary.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rule.Rules.Action != "drop" {
|
||||
return fmt.Errorf("Wrong rule action")
|
||||
}
|
||||
if rule.Rules.Direction != "inbound" {
|
||||
return fmt.Errorf("wrong rule direction")
|
||||
}
|
||||
if rule.Rules.IPRange != "0.0.0.0/0" {
|
||||
return fmt.Errorf("wrong rule IP Range")
|
||||
}
|
||||
if rule.Rules.Protocol != "TCP" {
|
||||
return fmt.Errorf("wrong rule protocol")
|
||||
}
|
||||
if rule.Rules.DestPortFrom != 80 {
|
||||
return fmt.Errorf("Wrong port")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckScalewaySecurityGroupRuleExists(n string, group *api.ScalewaySecurityGroups) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("Security Group Rule Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No Security Group Rule ID is set")
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*Client).scaleway
|
||||
rule, err := client.GetASecurityGroupRule(group.ID, rs.Primary.ID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rule.Rules.ID != rs.Primary.ID {
|
||||
return fmt.Errorf("Record not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var testAccCheckScalewaySecurityGroupRuleConfig = `
|
||||
resource "scaleway_security_group" "base" {
|
||||
name = "public"
|
||||
description = "public gateway"
|
||||
}
|
||||
|
||||
resource "scaleway_security_group_rule" "http" {
|
||||
security_group = "${scaleway_security_group.base.id}"
|
||||
|
||||
action = "drop"
|
||||
direction = "inbound"
|
||||
ip_range = "0.0.0.0/0"
|
||||
protocol = "TCP"
|
||||
port = 80
|
||||
}
|
||||
`
|
|
@ -0,0 +1,104 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccScalewaySecurityGroup_Basic(t *testing.T) {
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckScalewaySecurityGroupDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCheckScalewaySecurityGroupConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckScalewaySecurityGroupExists("scaleway_security_group.base"),
|
||||
testAccCheckScalewaySecurityGroupAttributes("scaleway_security_group.base"),
|
||||
resource.TestCheckResourceAttr("scaleway_security_group.base", "name", "public"),
|
||||
resource.TestCheckResourceAttr("scaleway_security_group.base", "description", "public gateway"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckScalewaySecurityGroupDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*Client).scaleway
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "scaleway" {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := client.GetASecurityGroup(rs.Primary.ID)
|
||||
|
||||
if err == nil {
|
||||
return fmt.Errorf("Security Group still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckScalewaySecurityGroupAttributes(n 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
|
||||
group, err := client.GetASecurityGroup(rs.Primary.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if group.SecurityGroups.Name != "public" {
|
||||
return fmt.Errorf("Security Group has wrong name")
|
||||
}
|
||||
if group.SecurityGroups.Description != "public gateway" {
|
||||
return fmt.Errorf("Security Group has wrong description")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckScalewaySecurityGroupExists(n string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No Security Group ID is set")
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*Client).scaleway
|
||||
group, err := client.GetASecurityGroup(rs.Primary.ID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if group.SecurityGroups.ID != rs.Primary.ID {
|
||||
return fmt.Errorf("Record not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var testAccCheckScalewaySecurityGroupConfig = `
|
||||
resource "scaleway_security_group" "base" {
|
||||
name = "public"
|
||||
description = "public gateway"
|
||||
}
|
||||
`
|
|
@ -0,0 +1,183 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
)
|
||||
|
||||
func resourceScalewayServer() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceScalewayServerCreate,
|
||||
Read: resourceScalewayServerRead,
|
||||
Update: resourceScalewayServerUpdate,
|
||||
Delete: resourceScalewayServerDelete,
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"image": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"type": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"bootscript": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"tags": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
"ipv4_address_private": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
"ipv4_address_public": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
"state": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"dynamic_ip_required": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
},
|
||||
"state_detail": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceScalewayServerCreate(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
|
||||
image := d.Get("image").(string)
|
||||
var server = api.ScalewayServerDefinition{
|
||||
Name: d.Get("name").(string),
|
||||
Image: String(image),
|
||||
Organization: scaleway.Organization,
|
||||
}
|
||||
|
||||
server.DynamicIPRequired = Bool(d.Get("dynamic_ip_required").(bool))
|
||||
server.CommercialType = d.Get("type").(string)
|
||||
|
||||
if bootscript, ok := d.GetOk("bootscript"); ok {
|
||||
server.Bootscript = String(bootscript.(string))
|
||||
}
|
||||
|
||||
if tags, ok := d.GetOk("tags"); ok {
|
||||
server.Tags = tags.([]string)
|
||||
}
|
||||
|
||||
id, err := scaleway.PostServer(server)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetId(id)
|
||||
if d.Get("state").(string) != "stopped" {
|
||||
err = scaleway.PostServerAction(id, "poweron")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = waitForServerState(scaleway, id, "running")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return resourceScalewayServerRead(d, m)
|
||||
}
|
||||
|
||||
func resourceScalewayServerRead(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
server, err := scaleway.GetServer(d.Id())
|
||||
|
||||
if err != nil {
|
||||
if serr, ok := err.(api.ScalewayAPIError); ok {
|
||||
log.Printf("[DEBUG] Error reading server: %q\n", serr.APIMessage)
|
||||
|
||||
if serr.StatusCode == 404 {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
d.Set("ipv4_address_private", server.PrivateIP)
|
||||
d.Set("ipv4_address_public", server.PublicAddress.IP)
|
||||
d.Set("state", server.State)
|
||||
d.Set("state_detail", server.StateDetail)
|
||||
|
||||
d.SetConnInfo(map[string]string{
|
||||
"type": "ssh",
|
||||
"host": server.PublicAddress.IP,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceScalewayServerUpdate(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
|
||||
var req api.ScalewayServerPatchDefinition
|
||||
|
||||
if d.HasChange("name") {
|
||||
name := d.Get("name").(string)
|
||||
req.Name = &name
|
||||
}
|
||||
|
||||
if d.HasChange("dynamic_ip_required") {
|
||||
req.DynamicIPRequired = Bool(d.Get("dynamic_ip_required").(bool))
|
||||
}
|
||||
|
||||
if err := scaleway.PatchServer(d.Id(), req); err != nil {
|
||||
return fmt.Errorf("Failed patching scaleway server: %q", err)
|
||||
}
|
||||
|
||||
return resourceScalewayServerRead(d, m)
|
||||
}
|
||||
|
||||
func resourceScalewayServerDelete(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
|
||||
def, err := scaleway.GetServer(d.Id())
|
||||
if err != nil {
|
||||
if serr, ok := err.(api.ScalewayAPIError); ok {
|
||||
if serr.StatusCode == 404 {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
err = deleteServerSafe(scaleway, def.Identifier)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccScalewayServer_Basic(t *testing.T) {
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckScalewayServerDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCheckScalewayServerConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckScalewayServerExists("scaleway_server.base"),
|
||||
testAccCheckScalewayServerAttributes("scaleway_server.base"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"scaleway_server.base", "type", "C1"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"scaleway_server.base", "name", "test"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckScalewayServerDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*Client).scaleway
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "scaleway" {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := client.GetServer(rs.Primary.ID)
|
||||
|
||||
if err == nil {
|
||||
return fmt.Errorf("Server still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckScalewayServerAttributes(n 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.Name != "test" {
|
||||
return fmt.Errorf("Server has wrong name")
|
||||
}
|
||||
if server.Image.Identifier != armImageIdentifier {
|
||||
return fmt.Errorf("Wrong server image")
|
||||
}
|
||||
if server.CommercialType != "C1" {
|
||||
return fmt.Errorf("Wrong server type")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckScalewayServerExists(n string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No Server ID is set")
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*Client).scaleway
|
||||
server, err := client.GetServer(rs.Primary.ID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if server.Identifier != rs.Primary.ID {
|
||||
return fmt.Errorf("Record not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var armImageIdentifier = "5faef9cd-ea9b-4a63-9171-9e26bec03dbc"
|
||||
|
||||
var testAccCheckScalewayServerConfig = fmt.Sprintf(`
|
||||
resource "scaleway_server" "base" {
|
||||
name = "test"
|
||||
# ubuntu 14.04
|
||||
image = "%s"
|
||||
type = "C1"
|
||||
}`, armImageIdentifier)
|
|
@ -0,0 +1,127 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
)
|
||||
|
||||
const gb uint64 = 1000 * 1000 * 1000
|
||||
|
||||
func resourceScalewayVolume() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceScalewayVolumeCreate,
|
||||
Read: resourceScalewayVolumeRead,
|
||||
Update: resourceScalewayVolumeUpdate,
|
||||
Delete: resourceScalewayVolumeDelete,
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"size_in_gb": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
|
||||
value := v.(int)
|
||||
if value < 1 || value > 150 {
|
||||
errors = append(errors, fmt.Errorf("%q be more than 1 and less than 150", k))
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
"type": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
|
||||
value := v.(string)
|
||||
if value != "l_ssd" {
|
||||
errors = append(errors, fmt.Errorf("%q must be l_ssd", k))
|
||||
}
|
||||
return
|
||||
},
|
||||
},
|
||||
"server": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceScalewayVolumeCreate(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
size := uint64(d.Get("size_in_gb").(int)) * gb
|
||||
req := api.ScalewayVolumeDefinition{
|
||||
Name: d.Get("name").(string),
|
||||
Size: size,
|
||||
Type: d.Get("type").(string),
|
||||
Organization: scaleway.Organization,
|
||||
}
|
||||
volumeID, err := scaleway.PostVolume(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error Creating volume: %q", err)
|
||||
}
|
||||
d.SetId(volumeID)
|
||||
return resourceScalewayVolumeRead(d, m)
|
||||
}
|
||||
|
||||
func resourceScalewayVolumeRead(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
volume, err := scaleway.GetVolume(d.Id())
|
||||
if err != nil {
|
||||
if serr, ok := err.(api.ScalewayAPIError); ok {
|
||||
log.Printf("[DEBUG] Error reading volume: %q\n", serr.APIMessage)
|
||||
|
||||
if serr.StatusCode == 404 {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
d.Set("name", volume.Name)
|
||||
d.Set("size_in_gb", volume.Size/gb)
|
||||
d.Set("type", volume.VolumeType)
|
||||
d.Set("server", "")
|
||||
if volume.Server != nil {
|
||||
d.Set("server", volume.Server.Identifier)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceScalewayVolumeUpdate(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
|
||||
var req api.ScalewayVolumePutDefinition
|
||||
if d.HasChange("name") {
|
||||
req.Name = String(d.Get("name").(string))
|
||||
}
|
||||
|
||||
if d.HasChange("size_in_gb") {
|
||||
size := uint64(d.Get("size_in_gb").(int)) * gb
|
||||
req.Size = &size
|
||||
}
|
||||
|
||||
scaleway.PutVolume(d.Id(), req)
|
||||
return resourceScalewayVolumeRead(d, m)
|
||||
}
|
||||
|
||||
func resourceScalewayVolumeDelete(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
err := scaleway.DeleteVolume(d.Id())
|
||||
if err != nil {
|
||||
if serr, ok := err.(api.ScalewayAPIError); ok {
|
||||
if serr.StatusCode == 404 {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||
)
|
||||
|
||||
func resourceScalewayVolumeAttachment() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceScalewayVolumeAttachmentCreate,
|
||||
Read: resourceScalewayVolumeAttachmentRead,
|
||||
Delete: resourceScalewayVolumeAttachmentDelete,
|
||||
Schema: map[string]*schema.Schema{
|
||||
"server": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"volume": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceScalewayVolumeAttachmentCreate(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
|
||||
var startServerAgain = false
|
||||
server, err := scaleway.GetServer(d.Get("server").(string))
|
||||
if err != nil {
|
||||
fmt.Printf("Failed getting server: %q", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// volumes can only be modified when the server is powered off
|
||||
if server.State != "stopped" {
|
||||
startServerAgain = true
|
||||
|
||||
if err := scaleway.PostServerAction(server.Identifier, "poweroff"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := waitForServerState(scaleway, server.Identifier, "stopped"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
volumes := make(map[string]api.ScalewayVolume)
|
||||
for i, volume := range server.Volumes {
|
||||
volumes[i] = volume
|
||||
}
|
||||
|
||||
vol, err := scaleway.GetVolume(d.Get("volume").(string))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
volumes[fmt.Sprintf("%d", len(volumes)+1)] = *vol
|
||||
|
||||
// the API request requires most volume attributes to be unset to succeed
|
||||
for k, v := range volumes {
|
||||
v.Size = 0
|
||||
v.CreationDate = ""
|
||||
v.Organization = ""
|
||||
v.ModificationDate = ""
|
||||
v.VolumeType = ""
|
||||
v.Server = nil
|
||||
v.ExportURI = ""
|
||||
|
||||
volumes[k] = v
|
||||
}
|
||||
|
||||
var req = api.ScalewayServerPatchDefinition{
|
||||
Volumes: &volumes,
|
||||
}
|
||||
if err := scaleway.PatchServer(d.Get("server").(string), req); err != nil {
|
||||
return fmt.Errorf("Failed attaching volume to server: %q", err)
|
||||
}
|
||||
|
||||
if startServerAgain {
|
||||
if err := scaleway.PostServerAction(d.Get("server").(string), "poweron"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := waitForServerState(scaleway, d.Get("server").(string), "running"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(fmt.Sprintf("scaleway-server:%s/volume/%s", d.Get("server").(string), d.Get("volume").(string)))
|
||||
|
||||
return resourceScalewayVolumeAttachmentRead(d, m)
|
||||
}
|
||||
|
||||
func resourceScalewayVolumeAttachmentRead(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
|
||||
server, err := scaleway.GetServer(d.Get("server").(string))
|
||||
if err != nil {
|
||||
if serr, ok := err.(api.ScalewayAPIError); ok {
|
||||
log.Printf("[DEBUG] Error reading server: %q\n", serr.APIMessage)
|
||||
|
||||
if serr.StatusCode == 404 {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := scaleway.GetVolume(d.Get("volume").(string)); err != nil {
|
||||
if serr, ok := err.(api.ScalewayAPIError); ok {
|
||||
log.Printf("[DEBUG] Error reading volume: %q\n", serr.APIMessage)
|
||||
|
||||
if serr.StatusCode == 404 {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for _, volume := range server.Volumes {
|
||||
if volume.Identifier == d.Get("volume").(string) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Volume %q not attached to server %q\n", d.Get("volume").(string), d.Get("server").(string))
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceScalewayVolumeAttachmentDelete(d *schema.ResourceData, m interface{}) error {
|
||||
scaleway := m.(*Client).scaleway
|
||||
var startServerAgain = false
|
||||
|
||||
server, err := scaleway.GetServer(d.Get("server").(string))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// volumes can only be modified when the server is powered off
|
||||
if server.State != "stopped" {
|
||||
startServerAgain = true
|
||||
|
||||
if err := scaleway.PostServerAction(server.Identifier, "poweroff"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := waitForServerState(scaleway, server.Identifier, "stopped"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
volumes := make(map[string]api.ScalewayVolume)
|
||||
for _, volume := range server.Volumes {
|
||||
if volume.Identifier != d.Get("volume").(string) {
|
||||
volumes[fmt.Sprintf("%d", len(volumes))] = volume
|
||||
}
|
||||
}
|
||||
|
||||
// the API request requires most volume attributes to be unset to succeed
|
||||
for k, v := range volumes {
|
||||
v.Size = 0
|
||||
v.CreationDate = ""
|
||||
v.Organization = ""
|
||||
v.ModificationDate = ""
|
||||
v.VolumeType = ""
|
||||
v.Server = nil
|
||||
v.ExportURI = ""
|
||||
|
||||
volumes[k] = v
|
||||
}
|
||||
|
||||
var req = api.ScalewayServerPatchDefinition{
|
||||
Volumes: &volumes,
|
||||
}
|
||||
if err := scaleway.PatchServer(d.Get("server").(string), req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if startServerAgain {
|
||||
if err := scaleway.PostServerAction(d.Get("server").(string), "poweron"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := waitForServerState(scaleway, d.Get("server").(string), "running"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId("")
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccScalewayVolumeAttachment_Basic(t *testing.T) {
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckScalewayVolumeAttachmentDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCheckScalewayVolumeAttachmentConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckScalewayVolumeAttachmentExists("scaleway_volume_attachment.test"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckScalewayVolumeAttachmentDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*Client).scaleway
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "scaleway" {
|
||||
continue
|
||||
}
|
||||
|
||||
s, err := client.GetServer(rs.Primary.Attributes["server"])
|
||||
if err != nil {
|
||||
fmt.Printf("Failed getting server: %q", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, volume := range s.Volumes {
|
||||
if volume.Identifier == rs.Primary.Attributes["volume"] {
|
||||
return fmt.Errorf("Attachment still exists")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckScalewayVolumeAttachmentExists(n string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*Client).scaleway
|
||||
|
||||
rs, _ := s.RootModule().Resources[n]
|
||||
|
||||
server, err := client.GetServer(rs.Primary.Attributes["server"])
|
||||
if err != nil {
|
||||
fmt.Printf("Failed getting server: %q", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, volume := range server.Volumes {
|
||||
if volume.Identifier == rs.Primary.Attributes["volume"] {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Attachment does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
var x86_64ImageIdentifier = "aecaed73-51a5-4439-a127-6d8229847145"
|
||||
|
||||
var testAccCheckScalewayVolumeAttachmentConfig = fmt.Sprintf(`
|
||||
resource "scaleway_server" "base" {
|
||||
name = "test"
|
||||
# ubuntu 14.04
|
||||
image = "%s"
|
||||
type = "C2S"
|
||||
# state = "stopped"
|
||||
}
|
||||
|
||||
resource "scaleway_volume" "test" {
|
||||
name = "test"
|
||||
size_in_gb = 20
|
||||
type = "l_ssd"
|
||||
}
|
||||
|
||||
resource "scaleway_volume_attachment" "test" {
|
||||
server = "${scaleway_server.base.id}"
|
||||
volume = "${scaleway_volume.test.id}"
|
||||
}`, x86_64ImageIdentifier)
|
|
@ -0,0 +1,107 @@
|
|||
package scaleway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccScalewayVolume_Basic(t *testing.T) {
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckScalewayVolumeDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCheckScalewayVolumeConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckScalewayVolumeExists("scaleway_volume.test"),
|
||||
testAccCheckScalewayVolumeAttributes("scaleway_volume.test"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckScalewayVolumeDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*Client).scaleway
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "scaleway" {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := client.GetVolume(rs.Primary.ID)
|
||||
|
||||
if err == nil {
|
||||
return fmt.Errorf("Volume still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckScalewayVolumeAttributes(n 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
|
||||
volume, err := client.GetVolume(rs.Primary.ID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if volume.Name != "test" {
|
||||
return fmt.Errorf("volume has wrong name: %q", volume.Name)
|
||||
}
|
||||
if volume.Size != 2000000000 {
|
||||
return fmt.Errorf("volume has wrong size: %d", volume.Size)
|
||||
}
|
||||
if volume.VolumeType != "l_ssd" {
|
||||
return fmt.Errorf("volume has volume type: %q", volume.VolumeType)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckScalewayVolumeExists(n string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No Volume ID is set")
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*Client).scaleway
|
||||
volume, err := client.GetVolume(rs.Primary.ID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if volume.Identifier != rs.Primary.ID {
|
||||
return fmt.Errorf("Record not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var testAccCheckScalewayVolumeConfig = `
|
||||
resource "scaleway_volume" "test" {
|
||||
name = "test"
|
||||
size_in_gb = 2
|
||||
type = "l_ssd"
|
||||
}
|
||||
`
|
|
@ -39,6 +39,7 @@ import (
|
|||
powerdnsprovider "github.com/hashicorp/terraform/builtin/providers/powerdns"
|
||||
randomprovider "github.com/hashicorp/terraform/builtin/providers/random"
|
||||
rundeckprovider "github.com/hashicorp/terraform/builtin/providers/rundeck"
|
||||
scalewayprovider "github.com/hashicorp/terraform/builtin/providers/scaleway"
|
||||
softlayerprovider "github.com/hashicorp/terraform/builtin/providers/softlayer"
|
||||
statuscakeprovider "github.com/hashicorp/terraform/builtin/providers/statuscake"
|
||||
templateprovider "github.com/hashicorp/terraform/builtin/providers/template"
|
||||
|
@ -92,6 +93,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{
|
|||
"powerdns": powerdnsprovider.Provider,
|
||||
"random": randomprovider.Provider,
|
||||
"rundeck": rundeckprovider.Provider,
|
||||
"scaleway": scalewayprovider.Provider,
|
||||
"softlayer": softlayerprovider.Provider,
|
||||
"statuscake": statuscakeprovider.Provider,
|
||||
"template": templateprovider.Provider,
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Manfred Touron
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
# AnonUUID
|
||||
|
||||
[![Build Status](https://travis-ci.org/moul/anonuuid.svg)](https://travis-ci.org/moul/anonuuid)
|
||||
[![GoDoc](https://godoc.org/github.com/moul/anonuuid?status.svg)](https://godoc.org/github.com/moul/anonuuid)
|
||||
[![Coverage Status](https://coveralls.io/repos/moul/anonuuid/badge.svg?branch=master&service=github)](https://coveralls.io/github/moul/anonuuid?branch=master)
|
||||
|
||||
:wrench: Anonymize UUIDs outputs (written in Golang)
|
||||
|
||||
![AnonUUID Logo](https://raw.githubusercontent.com/moul/anonuuid/master/assets/anonuuid.png)
|
||||
|
||||
**anonuuid** anonymize an input string by replacing all UUIDs by an anonymized
|
||||
new one.
|
||||
|
||||
The fake UUIDs are cached, so if AnonUUID encounter the same real UUIDs multiple
|
||||
times, the translation will be the same.
|
||||
|
||||
## Usage
|
||||
|
||||
```console
|
||||
$ anonuuid --help
|
||||
NAME:
|
||||
anonuuid - Anonymize UUIDs outputs
|
||||
|
||||
USAGE:
|
||||
anonuuid [global options] command [command options] [arguments...]
|
||||
|
||||
VERSION:
|
||||
1.0.0-dev
|
||||
|
||||
AUTHOR(S):
|
||||
Manfred Touron <https://github.com/moul>
|
||||
|
||||
COMMANDS:
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
--hexspeak Generate hexspeak style fake UUIDs
|
||||
--random, -r Generate random fake UUIDs
|
||||
--keep-beginning Keep first part of the UUID unchanged
|
||||
--keep-end Keep last part of the UUID unchanged
|
||||
--prefix, -p Prefix generated UUIDs
|
||||
--suffix Suffix generated UUIDs
|
||||
--help, -h show help
|
||||
--version, -v print the version
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
Replace all UUIDs and cache the correspondance.
|
||||
|
||||
```command
|
||||
$ anonuuid git:(master) ✗ cat <<EOF | anonuuid
|
||||
VOLUMES_0_SERVER_ID=15573749-c89d-41dd-a655-16e79bed52e0
|
||||
VOLUMES_0_SERVER_NAME=hello
|
||||
VOLUMES_0_ID=c245c3cb-3336-4567-ada1-70cb1fe4eefe
|
||||
VOLUMES_0_SIZE=50000000000
|
||||
ORGANIZATION=fe1e54e8-d69d-4f7c-a9f1-42069e03da31
|
||||
TEST=15573749-c89d-41dd-a655-16e79bed52e0
|
||||
EOF
|
||||
VOLUMES_0_SERVER_ID=00000000-0000-0000-0000-000000000000
|
||||
VOLUMES_0_SERVER_NAME=hello
|
||||
VOLUMES_0_ID=11111111-1111-1111-1111-111111111111
|
||||
VOLUMES_0_SIZE=50000000000
|
||||
ORGANIZATION=22222222-2222-2222-2222-222222222222
|
||||
TEST=00000000-0000-0000-0000-000000000000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Inline
|
||||
|
||||
```command
|
||||
$ echo 'VOLUMES_0_SERVER_ID=15573749-c89d-41dd-a655-16e79bed52e0 VOLUMES_0_SERVER_NAME=bitrig1 VOLUMES_0_ID=c245c3cb-3336-4567-ada1-70cb1fe4eefe VOLUMES_0_SIZE=50000000000 ORGANIZATION=fe1e54e8-d69d-4f7c-a9f1-42069e03da31 TEST=15573749-c89d-41dd-a655-16e79bed52e0' | ./anonuuid
|
||||
VOLUMES_0_SERVER_ID=00000000-0000-0000-0000-000000000000 VOLUMES_0_SERVER_NAME=bitrig1 VOLUMES_0_ID=11111111-1111-1111-1111-111111111111 VOLUMES_0_SIZE=50000000000 ORGANIZATION=22222222-2222-2222-2222-222222222222 TEST=00000000-0000-0000-0000-000000000000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```command
|
||||
$ curl -s https://api.pathwar.net/achievements\?max_results\=2 | anonuuid | jq .
|
||||
{
|
||||
"_items": [
|
||||
{
|
||||
"_updated": "Thu, 30 Apr 2015 13:00:58 GMT",
|
||||
"description": "You",
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "achievements/00000000-0000-0000-0000-000000000000",
|
||||
"title": "achievement"
|
||||
}
|
||||
},
|
||||
"_created": "Thu, 30 Apr 2015 13:00:58 GMT",
|
||||
"_id": "00000000-0000-0000-0000-000000000000",
|
||||
"_etag": "b1e9f850accfcb952c58384db41d89728890a69f",
|
||||
"name": "finish-20-levels"
|
||||
},
|
||||
{
|
||||
"_updated": "Thu, 30 Apr 2015 13:01:07 GMT",
|
||||
"description": "You",
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "achievements/11111111-1111-1111-1111-111111111111",
|
||||
"title": "achievement"
|
||||
}
|
||||
},
|
||||
"_created": "Thu, 30 Apr 2015 13:01:07 GMT",
|
||||
"_id": "11111111-1111-1111-1111-111111111111",
|
||||
"_etag": "c346f5e1c4f7658f2dfc4124efa87aba909a9821",
|
||||
"name": "buy-30-levels"
|
||||
}
|
||||
],
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "achievements?max_results=2",
|
||||
"title": "achievements"
|
||||
},
|
||||
"last": {
|
||||
"href": "achievements?max_results=2&page=23",
|
||||
"title": "last page"
|
||||
},
|
||||
"parent": {
|
||||
"href": "/",
|
||||
"title": "home"
|
||||
},
|
||||
"next": {
|
||||
"href": "achievements?max_results=2&page=2",
|
||||
"title": "next page"
|
||||
}
|
||||
},
|
||||
"_meta": {
|
||||
"max_results": 2,
|
||||
"total": 46,
|
||||
"page": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
Using go
|
||||
|
||||
- `go get github.com/moul/anonuuid/...`
|
||||
|
||||
## Changelog
|
||||
|
||||
### master (unreleased)
|
||||
|
||||
* Add mutex to protect the cache field ([@QuentinPerez](https://github.com/QuentinPerez))
|
||||
* Switch from `Party` to `Godep`
|
||||
* Support of `--suffix=xxx`, `--keep-beginning` and `--keep-end` options ([#4](https://github.com/moul/anonuuid/issues/4))
|
||||
* Using **party** to stabilize vendor package versions ([#8](https://github.com/moul/anonuuid/issues/8))
|
||||
* Add homebrew package ([#6](https://github.com/moul/anonuuid/issues/6))
|
||||
|
||||
[full commits list](https://github.com/moul/anonuuid/compare/v1.0.0...master)
|
||||
|
||||
### [v1.0.0](https://github.com/moul/anonuuid/releases/tag/v1.0.0) (2015-10-07)
|
||||
|
||||
**Initial release**
|
||||
|
||||
#### Features
|
||||
|
||||
* Support of `--hexspeak` option
|
||||
* Support of `--random` option
|
||||
* Support of `--prefix` option
|
||||
* Anonymize input stream
|
||||
* Anonymize files
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
|
@ -0,0 +1,229 @@
|
|||
package anonuuid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// UUIDRegex is the regex used to find UUIDs in texts
|
||||
UUIDRegex = "[a-z0-9]{8}-[a-z0-9]{4}-[1-5][a-z0-9]{3}-[a-z0-9]{4}-[a-z0-9]{12}"
|
||||
)
|
||||
|
||||
// AnonUUID is the main structure, it contains the cache map and helpers
|
||||
type AnonUUID struct {
|
||||
cache map[string]string
|
||||
|
||||
guard sync.Mutex // cache guard
|
||||
|
||||
// Hexspeak flag will generate hexspeak style fake UUIDs
|
||||
Hexspeak bool
|
||||
|
||||
// Random flag will generate random fake UUIDs
|
||||
Random bool
|
||||
|
||||
// Prefix will be the beginning of all the generated UUIDs
|
||||
Prefix string
|
||||
|
||||
// Suffix will be the end of all the generated UUIDs
|
||||
Suffix string
|
||||
|
||||
// AllowNonUUIDInput tells FakeUUID to accept non UUID input string
|
||||
AllowNonUUIDInput bool
|
||||
|
||||
// KeepBeginning tells FakeUUID to let the beginning of the UUID as it is
|
||||
KeepBeginning bool
|
||||
|
||||
// KeepEnd tells FakeUUID to let the last part of the UUID as it is
|
||||
KeepEnd bool
|
||||
}
|
||||
|
||||
// Sanitize takes a string as input and return sanitized string
|
||||
func (a *AnonUUID) Sanitize(input string) string {
|
||||
r := regexp.MustCompile(UUIDRegex)
|
||||
|
||||
return r.ReplaceAllStringFunc(input, func(m string) string {
|
||||
parts := r.FindStringSubmatch(m)
|
||||
return a.FakeUUID(parts[0])
|
||||
})
|
||||
}
|
||||
|
||||
// FakeUUID takes a word (real UUID or standard string) and returns its corresponding (mapped) fakeUUID
|
||||
func (a *AnonUUID) FakeUUID(input string) string {
|
||||
if !a.AllowNonUUIDInput {
|
||||
err := IsUUID(input)
|
||||
if err != nil {
|
||||
return "invaliduuid"
|
||||
}
|
||||
}
|
||||
a.guard.Lock()
|
||||
defer a.guard.Unlock()
|
||||
if _, ok := a.cache[input]; !ok {
|
||||
|
||||
if a.KeepBeginning {
|
||||
a.Prefix = input[:8]
|
||||
}
|
||||
|
||||
if a.KeepEnd {
|
||||
a.Suffix = input[36-12:]
|
||||
}
|
||||
|
||||
if a.Prefix != "" {
|
||||
matched, err := regexp.MatchString("^[a-z0-9]+$", a.Prefix)
|
||||
if err != nil || !matched {
|
||||
a.Prefix = "invalidprefix"
|
||||
}
|
||||
}
|
||||
|
||||
if a.Suffix != "" {
|
||||
matched, err := regexp.MatchString("^[a-z0-9]+$", a.Suffix)
|
||||
if err != nil || !matched {
|
||||
a.Suffix = "invalsuffix"
|
||||
}
|
||||
}
|
||||
|
||||
var fakeUUID string
|
||||
var err error
|
||||
if a.Hexspeak {
|
||||
fakeUUID, err = GenerateHexspeakUUID(len(a.cache))
|
||||
} else if a.Random {
|
||||
fakeUUID, err = GenerateRandomUUID(10)
|
||||
} else {
|
||||
fakeUUID, err = GenerateLenUUID(len(a.cache))
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate an UUID: %v", err)
|
||||
}
|
||||
|
||||
if a.Prefix != "" {
|
||||
fakeUUID, err = PrefixUUID(a.Prefix, fakeUUID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
if a.Suffix != "" {
|
||||
fakeUUID, err = SuffixUUID(a.Suffix, fakeUUID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: check for duplicates and retry
|
||||
|
||||
a.cache[input] = fakeUUID
|
||||
}
|
||||
return a.cache[input]
|
||||
}
|
||||
|
||||
// New returns a prepared AnonUUID structure
|
||||
func New() *AnonUUID {
|
||||
return &AnonUUID{
|
||||
cache: make(map[string]string),
|
||||
Hexspeak: false,
|
||||
Random: false,
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
}
|
||||
|
||||
// PrefixUUID returns a prefixed UUID
|
||||
func PrefixUUID(prefix string, uuid string) (string, error) {
|
||||
uuidLetters := uuid[:8] + uuid[9:13] + uuid[14:18] + uuid[19:23] + uuid[24:36]
|
||||
prefixedUUID, err := FormatUUID(prefix + uuidLetters)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return prefixedUUID, nil
|
||||
}
|
||||
|
||||
// SuffixUUID returns a suffixed UUID
|
||||
func SuffixUUID(suffix string, uuid string) (string, error) {
|
||||
uuidLetters := uuid[:8] + uuid[9:13] + uuid[14:18] + uuid[19:23] + uuid[24:36]
|
||||
uuidLetters = uuidLetters[:32-len(suffix)] + suffix
|
||||
suffixedUUID, err := FormatUUID(uuidLetters)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return suffixedUUID, nil
|
||||
}
|
||||
|
||||
// IsUUID returns nil if the input is an UUID, else it returns an error
|
||||
func IsUUID(input string) error {
|
||||
matched, err := regexp.MatchString("^"+UUIDRegex+"$", input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !matched {
|
||||
return fmt.Errorf("String '%s' is not a valid UUID", input)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FormatUUID takes a string in input and return an UUID formatted string by repeating the string and placing dashes if necessary
|
||||
func FormatUUID(part string) (string, error) {
|
||||
if len(part) < 1 {
|
||||
return "", fmt.Errorf("Empty UUID")
|
||||
}
|
||||
if len(part) < 32 {
|
||||
part = strings.Repeat(part, 32)
|
||||
}
|
||||
if len(part) > 32 {
|
||||
part = part[:32]
|
||||
}
|
||||
uuid := part[:8] + "-" + part[8:12] + "-1" + part[13:16] + "-" + part[16:20] + "-" + part[20:32]
|
||||
|
||||
err := IsUUID(uuid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// GenerateRandomUUID returns an UUID based on random strings
|
||||
func GenerateRandomUUID(length int) (string, error) {
|
||||
var letters = []rune("abcdef0123456789")
|
||||
|
||||
b := make([]rune, length)
|
||||
for i := range b {
|
||||
b[i] = letters[rand.Intn(len(letters))]
|
||||
}
|
||||
return FormatUUID(string(b))
|
||||
}
|
||||
|
||||
// GenerateHexspeakUUID returns an UUID formatted string containing hexspeak words
|
||||
func GenerateHexspeakUUID(i int) (string, error) {
|
||||
if i < 0 {
|
||||
i = -i
|
||||
}
|
||||
hexspeaks := []string{
|
||||
"0ff1ce",
|
||||
"31337",
|
||||
"4b1d",
|
||||
"badc0de",
|
||||
"badcafe",
|
||||
"badf00d",
|
||||
"deadbabe",
|
||||
"deadbeef",
|
||||
"deadc0de",
|
||||
"deadfeed",
|
||||
"fee1bad",
|
||||
}
|
||||
return FormatUUID(hexspeaks[i%len(hexspeaks)])
|
||||
}
|
||||
|
||||
// GenerateLenUUID returns an UUID formatted string based on an index number
|
||||
func GenerateLenUUID(i int) (string, error) {
|
||||
if i < 0 {
|
||||
i = 2<<29 + i
|
||||
}
|
||||
return FormatUUID(fmt.Sprintf("%x", i))
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Peter Renström
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,167 @@
|
|||
// Fuzzy searching allows for flexibly matching a string with partial input,
|
||||
// useful for filtering data very quickly based on lightweight user input.
|
||||
package fuzzy
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var noop = func(r rune) rune { return r }
|
||||
|
||||
// Match returns true if source matches target using a fuzzy-searching
|
||||
// algorithm. Note that it doesn't implement Levenshtein distance (see
|
||||
// RankMatch instead), but rather a simplified version where there's no
|
||||
// approximation. The method will return true only if each character in the
|
||||
// source can be found in the target and occurs after the preceding matches.
|
||||
func Match(source, target string) bool {
|
||||
return match(source, target, noop)
|
||||
}
|
||||
|
||||
// MatchFold is a case-insensitive version of Match.
|
||||
func MatchFold(source, target string) bool {
|
||||
return match(source, target, unicode.ToLower)
|
||||
}
|
||||
|
||||
func match(source, target string, fn func(rune) rune) bool {
|
||||
lenDiff := len(target) - len(source)
|
||||
|
||||
if lenDiff < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if lenDiff == 0 && source == target {
|
||||
return true
|
||||
}
|
||||
|
||||
Outer:
|
||||
for _, r1 := range source {
|
||||
for i, r2 := range target {
|
||||
if fn(r1) == fn(r2) {
|
||||
target = target[i+utf8.RuneLen(r2):]
|
||||
continue Outer
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Find will return a list of strings in targets that fuzzy matches source.
|
||||
func Find(source string, targets []string) []string {
|
||||
return find(source, targets, noop)
|
||||
}
|
||||
|
||||
// FindFold is a case-insensitive version of Find.
|
||||
func FindFold(source string, targets []string) []string {
|
||||
return find(source, targets, unicode.ToLower)
|
||||
}
|
||||
|
||||
func find(source string, targets []string, fn func(rune) rune) []string {
|
||||
var matches []string
|
||||
|
||||
for _, target := range targets {
|
||||
if match(source, target, fn) {
|
||||
matches = append(matches, target)
|
||||
}
|
||||
}
|
||||
|
||||
return matches
|
||||
}
|
||||
|
||||
// RankMatch is similar to Match except it will measure the Levenshtein
|
||||
// distance between the source and the target and return its result. If there
|
||||
// was no match, it will return -1.
|
||||
// Given the requirements of match, RankMatch only needs to perform a subset of
|
||||
// the Levenshtein calculation, only deletions need be considered, required
|
||||
// additions and substitutions would fail the match test.
|
||||
func RankMatch(source, target string) int {
|
||||
return rank(source, target, noop)
|
||||
}
|
||||
|
||||
// RankMatchFold is a case-insensitive version of RankMatch.
|
||||
func RankMatchFold(source, target string) int {
|
||||
return rank(source, target, unicode.ToLower)
|
||||
}
|
||||
|
||||
func rank(source, target string, fn func(rune) rune) int {
|
||||
lenDiff := len(target) - len(source)
|
||||
|
||||
if lenDiff < 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
if lenDiff == 0 && source == target {
|
||||
return 0
|
||||
}
|
||||
|
||||
runeDiff := 0
|
||||
|
||||
Outer:
|
||||
for _, r1 := range source {
|
||||
for i, r2 := range target {
|
||||
if fn(r1) == fn(r2) {
|
||||
target = target[i+utf8.RuneLen(r2):]
|
||||
continue Outer
|
||||
} else {
|
||||
runeDiff++
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Count up remaining char
|
||||
for len(target) > 0 {
|
||||
target = target[utf8.RuneLen(rune(target[0])):]
|
||||
runeDiff++
|
||||
}
|
||||
|
||||
return runeDiff
|
||||
}
|
||||
|
||||
// RankFind is similar to Find, except it will also rank all matches using
|
||||
// Levenshtein distance.
|
||||
func RankFind(source string, targets []string) Ranks {
|
||||
var r Ranks
|
||||
for _, target := range find(source, targets, noop) {
|
||||
distance := LevenshteinDistance(source, target)
|
||||
r = append(r, Rank{source, target, distance})
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// RankFindFold is a case-insensitive version of RankFind.
|
||||
func RankFindFold(source string, targets []string) Ranks {
|
||||
var r Ranks
|
||||
for _, target := range find(source, targets, unicode.ToLower) {
|
||||
distance := LevenshteinDistance(source, target)
|
||||
r = append(r, Rank{source, target, distance})
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
type Rank struct {
|
||||
// Source is used as the source for matching.
|
||||
Source string
|
||||
|
||||
// Target is the word matched against.
|
||||
Target string
|
||||
|
||||
// Distance is the Levenshtein distance between Source and Target.
|
||||
Distance int
|
||||
}
|
||||
|
||||
type Ranks []Rank
|
||||
|
||||
func (r Ranks) Len() int {
|
||||
return len(r)
|
||||
}
|
||||
|
||||
func (r Ranks) Swap(i, j int) {
|
||||
r[i], r[j] = r[j], r[i]
|
||||
}
|
||||
|
||||
func (r Ranks) Less(i, j int) bool {
|
||||
return r[i].Distance < r[j].Distance
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package fuzzy
|
||||
|
||||
// LevenshteinDistance measures the difference between two strings.
|
||||
// The Levenshtein distance between two words is the minimum number of
|
||||
// single-character edits (i.e. insertions, deletions or substitutions)
|
||||
// required to change one word into the other.
|
||||
//
|
||||
// This implemention is optimized to use O(min(m,n)) space and is based on the
|
||||
// optimized C version found here:
|
||||
// http://en.wikibooks.org/wiki/Algorithm_implementation/Strings/Levenshtein_distance#C
|
||||
func LevenshteinDistance(s, t string) int {
|
||||
r1, r2 := []rune(s), []rune(t)
|
||||
column := make([]int, len(r1)+1)
|
||||
|
||||
for y := 1; y <= len(r1); y++ {
|
||||
column[y] = y
|
||||
}
|
||||
|
||||
for x := 1; x <= len(r2); x++ {
|
||||
column[0] = x
|
||||
|
||||
for y, lastDiag := 1, x-1; y <= len(r1); y++ {
|
||||
oldDiag := column[y]
|
||||
cost := 0
|
||||
if r1[y-1] != r2[x-1] {
|
||||
cost = 1
|
||||
}
|
||||
column[y] = min(column[y]+1, column[y-1]+1, lastDiag+cost)
|
||||
lastDiag = oldDiag
|
||||
}
|
||||
}
|
||||
|
||||
return column[len(r1)]
|
||||
}
|
||||
|
||||
func min(a, b, c int) int {
|
||||
if a < b && a < c {
|
||||
return a
|
||||
} else if b < c {
|
||||
return b
|
||||
}
|
||||
return c
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
The MIT License
|
||||
===============
|
||||
|
||||
Copyright (c) **2014-2016 Scaleway <opensource@scaleway.com> ([@scaleway](https://twitter.com/scaleway))**
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,25 @@
|
|||
# Scaleway's API
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/scaleway/scaleway-cli/pkg/api?status.svg)](https://godoc.org/github.com/scaleway/scaleway-cli/pkg/api)
|
||||
|
||||
This package contains facilities to play with the Scaleway API, it includes the following features:
|
||||
|
||||
- dedicated configuration file containing credentials to deal with the API
|
||||
- caching to resolve UUIDs without contacting the API
|
||||
|
||||
## Links
|
||||
|
||||
- [API documentation](https://developer.scaleway.com)
|
||||
- [Official Python SDK](https://github.com/scaleway/python-scaleway)
|
||||
- Projects using this SDK
|
||||
- https://github.com/scaleway/devhub
|
||||
- https://github.com/scaleway/docker-machine-driver-scaleway
|
||||
- https://github.com/scaleway-community/scaleway-ubuntu-coreos/blob/master/overlay/usr/local/update-firewall/scw-api/cache.go
|
||||
- https://github.com/pulcy/quark
|
||||
- https://github.com/hex-sh/terraform-provider-scaleway
|
||||
- https://github.com/tscolari/bosh-scaleway-cpi
|
||||
- Other **golang** clients
|
||||
- https://github.com/lalyos/onlabs
|
||||
- https://github.com/meatballhat/packer-builder-onlinelabs
|
||||
- https://github.com/nlamirault/go-scaleway
|
||||
- https://github.com/golang/build/blob/master/cmd/scaleway/scaleway.go
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,21 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/scaleway/scaleway-cli/pkg/scwversion"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestNewScalewayAPI(t *testing.T) {
|
||||
Convey("Testing NewScalewayAPI()", t, func() {
|
||||
api, err := NewScalewayAPI("my-organization", "my-token", scwversion.UserAgent())
|
||||
So(err, ShouldBeNil)
|
||||
So(api, ShouldNotBeNil)
|
||||
So(api.Token, ShouldEqual, "my-token")
|
||||
So(api.Organization, ShouldEqual, "my-organization")
|
||||
So(api.Cache, ShouldNotBeNil)
|
||||
So(api.client, ShouldNotBeNil)
|
||||
So(api.Logger, ShouldNotBeNil)
|
||||
})
|
||||
}
|
|
@ -0,0 +1,731 @@
|
|||
// Copyright (C) 2015 Scaleway. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/moul/anonuuid"
|
||||
"github.com/renstrom/fuzzysearch/fuzzy"
|
||||
)
|
||||
|
||||
const (
|
||||
// CacheRegion permits to access at the region field
|
||||
CacheRegion = iota
|
||||
// CacheArch permits to access at the arch field
|
||||
CacheArch
|
||||
// CacheOwner permits to access at the owner field
|
||||
CacheOwner
|
||||
// CacheTitle permits to access at the title field
|
||||
CacheTitle
|
||||
// CacheMarketPlaceUUID is used to determine the UUID of local images
|
||||
CacheMarketPlaceUUID
|
||||
|
||||
// CacheMaxfield is used to determine the size of array
|
||||
CacheMaxfield
|
||||
)
|
||||
|
||||
// ScalewayCache is used not to query the API to resolve full identifiers
|
||||
type ScalewayCache struct {
|
||||
// Images contains names of Scaleway images indexed by identifier
|
||||
Images map[string][CacheMaxfield]string `json:"images"`
|
||||
|
||||
// Snapshots contains names of Scaleway snapshots indexed by identifier
|
||||
Snapshots map[string][CacheMaxfield]string `json:"snapshots"`
|
||||
|
||||
// Volumes contains names of Scaleway volumes indexed by identifier
|
||||
Volumes map[string][CacheMaxfield]string `json:"volumes"`
|
||||
|
||||
// Bootscripts contains names of Scaleway bootscripts indexed by identifier
|
||||
Bootscripts map[string][CacheMaxfield]string `json:"bootscripts"`
|
||||
|
||||
// Servers contains names of Scaleway servers indexed by identifier
|
||||
Servers map[string][CacheMaxfield]string `json:"servers"`
|
||||
|
||||
// Path is the path to the cache file
|
||||
Path string `json:"-"`
|
||||
|
||||
// Modified tells if the cache needs to be overwritten or not
|
||||
Modified bool `json:"-"`
|
||||
|
||||
// Lock allows ScalewayCache to be used concurrently
|
||||
Lock sync.Mutex `json:"-"`
|
||||
}
|
||||
|
||||
const (
|
||||
// IdentifierUnknown is used when we don't know explicitely the type key of the object (used for nil comparison)
|
||||
IdentifierUnknown = 1 << iota
|
||||
// IdentifierServer is the type key of cached server objects
|
||||
IdentifierServer
|
||||
// IdentifierImage is the type key of cached image objects
|
||||
IdentifierImage
|
||||
// IdentifierSnapshot is the type key of cached snapshot objects
|
||||
IdentifierSnapshot
|
||||
// IdentifierBootscript is the type key of cached bootscript objects
|
||||
IdentifierBootscript
|
||||
// IdentifierVolume is the type key of cached volume objects
|
||||
IdentifierVolume
|
||||
)
|
||||
|
||||
// ScalewayResolverResult is a structure containing human-readable information
|
||||
// about resolver results. This structure is used to display the user choices.
|
||||
type ScalewayResolverResult struct {
|
||||
Identifier string
|
||||
Type int
|
||||
Name string
|
||||
Arch string
|
||||
Needle string
|
||||
RankMatch int
|
||||
}
|
||||
|
||||
// ScalewayResolverResults is a list of `ScalewayResolverResult`
|
||||
type ScalewayResolverResults []ScalewayResolverResult
|
||||
|
||||
// NewScalewayResolverResult returns a new ScalewayResolverResult
|
||||
func NewScalewayResolverResult(Identifier, Name, Arch string, Type int) ScalewayResolverResult {
|
||||
if err := anonuuid.IsUUID(Identifier); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return ScalewayResolverResult{
|
||||
Identifier: Identifier,
|
||||
Type: Type,
|
||||
Name: Name,
|
||||
Arch: Arch,
|
||||
}
|
||||
}
|
||||
|
||||
func (s ScalewayResolverResults) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s ScalewayResolverResults) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s ScalewayResolverResults) Less(i, j int) bool {
|
||||
return s[i].RankMatch < s[j].RankMatch
|
||||
}
|
||||
|
||||
// TruncIdentifier returns first 8 characters of an Identifier (UUID)
|
||||
func (s *ScalewayResolverResult) TruncIdentifier() string {
|
||||
return s.Identifier[:8]
|
||||
}
|
||||
|
||||
func identifierTypeName(kind int) string {
|
||||
switch kind {
|
||||
case IdentifierServer:
|
||||
return "Server"
|
||||
case IdentifierImage:
|
||||
return "Image"
|
||||
case IdentifierSnapshot:
|
||||
return "Snapshot"
|
||||
case IdentifierVolume:
|
||||
return "Volume"
|
||||
case IdentifierBootscript:
|
||||
return "Bootscript"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// CodeName returns a full resource name with typed prefix
|
||||
func (s *ScalewayResolverResult) CodeName() string {
|
||||
name := strings.ToLower(s.Name)
|
||||
name = regexp.MustCompile(`[^a-z0-9-]`).ReplaceAllString(name, "-")
|
||||
name = regexp.MustCompile(`--+`).ReplaceAllString(name, "-")
|
||||
name = strings.Trim(name, "-")
|
||||
|
||||
return fmt.Sprintf("%s:%s", strings.ToLower(identifierTypeName(s.Type)), name)
|
||||
}
|
||||
|
||||
// FilterByArch deletes the elements which not match with arch
|
||||
func (s *ScalewayResolverResults) FilterByArch(arch string) {
|
||||
REDO:
|
||||
for i := range *s {
|
||||
if (*s)[i].Arch != arch {
|
||||
(*s)[i] = (*s)[len(*s)-1]
|
||||
*s = (*s)[:len(*s)-1]
|
||||
goto REDO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewScalewayCache loads a per-user cache
|
||||
func NewScalewayCache() (*ScalewayCache, error) {
|
||||
homeDir := os.Getenv("HOME") // *nix
|
||||
if homeDir == "" { // Windows
|
||||
homeDir = os.Getenv("USERPROFILE")
|
||||
}
|
||||
if homeDir == "" {
|
||||
homeDir = "/tmp"
|
||||
}
|
||||
cachePath := filepath.Join(homeDir, ".scw-cache.db")
|
||||
var cache ScalewayCache
|
||||
cache.Path = cachePath
|
||||
_, err := os.Stat(cachePath)
|
||||
if os.IsNotExist(err) {
|
||||
cache.Clear()
|
||||
return &cache, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file, err := ioutil.ReadFile(cachePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(file, &cache)
|
||||
if err != nil {
|
||||
// fix compatibility with older version
|
||||
if err = os.Remove(cachePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cache.Clear()
|
||||
return &cache, nil
|
||||
}
|
||||
if cache.Images == nil {
|
||||
cache.Images = make(map[string][CacheMaxfield]string)
|
||||
}
|
||||
if cache.Snapshots == nil {
|
||||
cache.Snapshots = make(map[string][CacheMaxfield]string)
|
||||
}
|
||||
if cache.Volumes == nil {
|
||||
cache.Volumes = make(map[string][CacheMaxfield]string)
|
||||
}
|
||||
if cache.Servers == nil {
|
||||
cache.Servers = make(map[string][CacheMaxfield]string)
|
||||
}
|
||||
if cache.Bootscripts == nil {
|
||||
cache.Bootscripts = make(map[string][CacheMaxfield]string)
|
||||
}
|
||||
return &cache, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Flush flushes the cache database
|
||||
func (c *ScalewayCache) Flush() error {
|
||||
return os.Remove(c.Path)
|
||||
}
|
||||
|
||||
// Save atomically overwrites the current cache database
|
||||
func (c *ScalewayCache) Save() error {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
log.Printf("Writing cache file to disk")
|
||||
|
||||
if c.Modified {
|
||||
file, err := ioutil.TempFile(filepath.Dir(c.Path), filepath.Base(c.Path))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
encoder := json.NewEncoder(file)
|
||||
err = encoder.Encode(*c)
|
||||
if err != nil {
|
||||
os.Remove(file.Name())
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Rename(file.Name(), c.Path); err != nil {
|
||||
os.Remove(file.Name())
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ComputeRankMatch fills `ScalewayResolverResult.RankMatch` with its `fuzzy` score
|
||||
func (s *ScalewayResolverResult) ComputeRankMatch(needle string) {
|
||||
s.Needle = needle
|
||||
s.RankMatch = fuzzy.RankMatch(needle, s.Name)
|
||||
}
|
||||
|
||||
// LookUpImages attempts to return identifiers matching a pattern
|
||||
func (c *ScalewayCache) LookUpImages(needle string, acceptUUID bool) ScalewayResolverResults {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
var res ScalewayResolverResults
|
||||
|
||||
if acceptUUID && anonuuid.IsUUID(needle) == nil {
|
||||
if fields, ok := c.Images[needle]; ok {
|
||||
entry := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierImage)
|
||||
entry.ComputeRankMatch(needle)
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
|
||||
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.ComputeRankMatch(needle)
|
||||
exactMatches = append(exactMatches, entry)
|
||||
}
|
||||
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
|
||||
entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierImage)
|
||||
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.ComputeRankMatch(needle)
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
|
||||
if len(exactMatches) == 1 {
|
||||
return exactMatches
|
||||
}
|
||||
|
||||
return removeDuplicatesResults(res)
|
||||
}
|
||||
|
||||
// LookUpSnapshots attempts to return identifiers matching a pattern
|
||||
func (c *ScalewayCache) LookUpSnapshots(needle string, acceptUUID bool) ScalewayResolverResults {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
var res ScalewayResolverResults
|
||||
|
||||
if acceptUUID && anonuuid.IsUUID(needle) == nil {
|
||||
if fields, ok := c.Snapshots[needle]; ok {
|
||||
entry := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierSnapshot)
|
||||
entry.ComputeRankMatch(needle)
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
|
||||
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.ComputeRankMatch(needle)
|
||||
exactMatches = append(exactMatches, entry)
|
||||
}
|
||||
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
|
||||
entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierSnapshot)
|
||||
entry.ComputeRankMatch(needle)
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
|
||||
if len(exactMatches) == 1 {
|
||||
return exactMatches
|
||||
}
|
||||
|
||||
return removeDuplicatesResults(res)
|
||||
}
|
||||
|
||||
// LookUpVolumes attempts to return identifiers matching a pattern
|
||||
func (c *ScalewayCache) LookUpVolumes(needle string, acceptUUID bool) ScalewayResolverResults {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
var res ScalewayResolverResults
|
||||
|
||||
if acceptUUID && anonuuid.IsUUID(needle) == nil {
|
||||
if fields, ok := c.Volumes[needle]; ok {
|
||||
entry := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierVolume)
|
||||
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.ComputeRankMatch(needle)
|
||||
exactMatches = append(exactMatches, entry)
|
||||
}
|
||||
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
|
||||
entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierVolume)
|
||||
entry.ComputeRankMatch(needle)
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
|
||||
if len(exactMatches) == 1 {
|
||||
return exactMatches
|
||||
}
|
||||
|
||||
return removeDuplicatesResults(res)
|
||||
}
|
||||
|
||||
// LookUpBootscripts attempts to return identifiers matching a pattern
|
||||
func (c *ScalewayCache) LookUpBootscripts(needle string, acceptUUID bool) ScalewayResolverResults {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
var res ScalewayResolverResults
|
||||
|
||||
if acceptUUID && anonuuid.IsUUID(needle) == nil {
|
||||
if fields, ok := c.Bootscripts[needle]; ok {
|
||||
entry := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierBootscript)
|
||||
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.ComputeRankMatch(needle)
|
||||
exactMatches = append(exactMatches, entry)
|
||||
}
|
||||
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
|
||||
entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierBootscript)
|
||||
entry.ComputeRankMatch(needle)
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
|
||||
if len(exactMatches) == 1 {
|
||||
return exactMatches
|
||||
}
|
||||
|
||||
return removeDuplicatesResults(res)
|
||||
}
|
||||
|
||||
// LookUpServers attempts to return identifiers matching a pattern
|
||||
func (c *ScalewayCache) LookUpServers(needle string, acceptUUID bool) ScalewayResolverResults {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
var res ScalewayResolverResults
|
||||
|
||||
if acceptUUID && anonuuid.IsUUID(needle) == nil {
|
||||
if fields, ok := c.Servers[needle]; ok {
|
||||
entry := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], IdentifierServer)
|
||||
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.ComputeRankMatch(needle)
|
||||
exactMatches = append(exactMatches, entry)
|
||||
}
|
||||
if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
|
||||
entry := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], IdentifierServer)
|
||||
entry.ComputeRankMatch(needle)
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
|
||||
if len(exactMatches) == 1 {
|
||||
return exactMatches
|
||||
}
|
||||
|
||||
return removeDuplicatesResults(res)
|
||||
}
|
||||
|
||||
// removeDuplicatesResults transforms an array into a unique array
|
||||
func removeDuplicatesResults(elements ScalewayResolverResults) ScalewayResolverResults {
|
||||
encountered := map[string]ScalewayResolverResult{}
|
||||
|
||||
// Create a map of all unique elements.
|
||||
for v := range elements {
|
||||
encountered[elements[v].Identifier] = elements[v]
|
||||
}
|
||||
|
||||
// Place all keys from the map into a slice.
|
||||
results := ScalewayResolverResults{}
|
||||
for _, result := range encountered {
|
||||
results = append(results, result)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// parseNeedle parses a user needle and try to extract a forced object type
|
||||
// i.e:
|
||||
// - server:blah-blah -> kind=server, needle=blah-blah
|
||||
// - blah-blah -> kind="", needle=blah-blah
|
||||
// - not-existing-type:blah-blah
|
||||
func parseNeedle(input string) (identifierType int, needle string) {
|
||||
parts := strings.Split(input, ":")
|
||||
if len(parts) == 2 {
|
||||
switch parts[0] {
|
||||
case "server":
|
||||
return IdentifierServer, parts[1]
|
||||
case "image":
|
||||
return IdentifierImage, parts[1]
|
||||
case "snapshot":
|
||||
return IdentifierSnapshot, parts[1]
|
||||
case "bootscript":
|
||||
return IdentifierBootscript, parts[1]
|
||||
case "volume":
|
||||
return IdentifierVolume, parts[1]
|
||||
}
|
||||
}
|
||||
return IdentifierUnknown, input
|
||||
}
|
||||
|
||||
// LookUpIdentifiers attempts to return identifiers matching a pattern
|
||||
func (c *ScalewayCache) LookUpIdentifiers(needle string) ScalewayResolverResults {
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
entry.ComputeRankMatch(needle)
|
||||
results = append(results, entry)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// InsertServer registers a server in the cache
|
||||
func (c *ScalewayCache) InsertServer(identifier, region, arch, owner, name string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
fields, exists := c.Servers[identifier]
|
||||
if !exists || fields[CacheTitle] != name {
|
||||
c.Servers[identifier] = [CacheMaxfield]string{region, arch, owner, name}
|
||||
c.Modified = true
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveServer removes a server from the cache
|
||||
func (c *ScalewayCache) RemoveServer(identifier string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
delete(c.Servers, identifier)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// ClearServers removes all servers from the cache
|
||||
func (c *ScalewayCache) ClearServers() {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
c.Servers = make(map[string][CacheMaxfield]string)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// InsertImage registers an image in the cache
|
||||
func (c *ScalewayCache) InsertImage(identifier, region, arch, owner, name, marketPlaceUUID string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
fields, exists := c.Images[identifier]
|
||||
if !exists || fields[CacheTitle] != name {
|
||||
c.Images[identifier] = [CacheMaxfield]string{region, arch, owner, name, marketPlaceUUID}
|
||||
c.Modified = true
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveImage removes a server from the cache
|
||||
func (c *ScalewayCache) RemoveImage(identifier string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
delete(c.Images, identifier)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// ClearImages removes all images from the cache
|
||||
func (c *ScalewayCache) ClearImages() {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
c.Images = make(map[string][CacheMaxfield]string)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// InsertSnapshot registers an snapshot in the cache
|
||||
func (c *ScalewayCache) InsertSnapshot(identifier, region, arch, owner, name string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
fields, exists := c.Snapshots[identifier]
|
||||
if !exists || fields[CacheTitle] != name {
|
||||
c.Snapshots[identifier] = [CacheMaxfield]string{region, arch, owner, name}
|
||||
c.Modified = true
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveSnapshot removes a server from the cache
|
||||
func (c *ScalewayCache) RemoveSnapshot(identifier string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
delete(c.Snapshots, identifier)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// ClearSnapshots removes all snapshots from the cache
|
||||
func (c *ScalewayCache) ClearSnapshots() {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
c.Snapshots = make(map[string][CacheMaxfield]string)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// InsertVolume registers an volume in the cache
|
||||
func (c *ScalewayCache) InsertVolume(identifier, region, arch, owner, name string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
fields, exists := c.Volumes[identifier]
|
||||
if !exists || fields[CacheTitle] != name {
|
||||
c.Volumes[identifier] = [CacheMaxfield]string{region, arch, owner, name}
|
||||
c.Modified = true
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveVolume removes a server from the cache
|
||||
func (c *ScalewayCache) RemoveVolume(identifier string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
delete(c.Volumes, identifier)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// ClearVolumes removes all volumes from the cache
|
||||
func (c *ScalewayCache) ClearVolumes() {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
c.Volumes = make(map[string][CacheMaxfield]string)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// InsertBootscript registers an bootscript in the cache
|
||||
func (c *ScalewayCache) InsertBootscript(identifier, region, arch, owner, name string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
fields, exists := c.Bootscripts[identifier]
|
||||
if !exists || fields[CacheTitle] != name {
|
||||
c.Bootscripts[identifier] = [CacheMaxfield]string{region, arch, owner, name}
|
||||
c.Modified = true
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveBootscript removes a bootscript from the cache
|
||||
func (c *ScalewayCache) RemoveBootscript(identifier string) {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
delete(c.Bootscripts, identifier)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// ClearBootscripts removes all bootscripts from the cache
|
||||
func (c *ScalewayCache) ClearBootscripts() {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
c.Bootscripts = make(map[string][CacheMaxfield]string)
|
||||
c.Modified = true
|
||||
}
|
||||
|
||||
// GetNbServers returns the number of servers in the cache
|
||||
func (c *ScalewayCache) GetNbServers() int {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
return len(c.Servers)
|
||||
}
|
||||
|
||||
// GetNbImages returns the number of images in the cache
|
||||
func (c *ScalewayCache) GetNbImages() int {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
return len(c.Images)
|
||||
}
|
||||
|
||||
// GetNbSnapshots returns the number of snapshots in the cache
|
||||
func (c *ScalewayCache) GetNbSnapshots() int {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
return len(c.Snapshots)
|
||||
}
|
||||
|
||||
// GetNbVolumes returns the number of volumes in the cache
|
||||
func (c *ScalewayCache) GetNbVolumes() int {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
return len(c.Volumes)
|
||||
}
|
||||
|
||||
// GetNbBootscripts returns the number of bootscripts in the cache
|
||||
func (c *ScalewayCache) GetNbBootscripts() int {
|
||||
c.Lock.Lock()
|
||||
defer c.Lock.Unlock()
|
||||
|
||||
return len(c.Bootscripts)
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (C) 2015 Scaleway. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Logger handles logging concerns for the Scaleway API SDK
|
||||
type Logger interface {
|
||||
LogHTTP(*http.Request)
|
||||
Fatalf(format string, v ...interface{})
|
||||
Debugf(format string, v ...interface{})
|
||||
Infof(format string, v ...interface{})
|
||||
Warnf(format string, v ...interface{})
|
||||
}
|
||||
|
||||
// NewDefaultLogger returns a logger which is configured for stdout
|
||||
func NewDefaultLogger() Logger {
|
||||
return &defaultLogger{
|
||||
Logger: log.New(os.Stdout, "", log.LstdFlags),
|
||||
}
|
||||
}
|
||||
|
||||
type defaultLogger struct {
|
||||
*log.Logger
|
||||
}
|
||||
|
||||
func (l *defaultLogger) LogHTTP(r *http.Request) {
|
||||
l.Printf("%s %s\n", r.Method, r.URL.Path)
|
||||
}
|
||||
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))
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package scwversion
|
||||
|
||||
import "fmt"
|
||||
|
||||
var (
|
||||
// VERSION represents the semver version of the package
|
||||
VERSION = "v1.9.0+dev"
|
||||
|
||||
// GITCOMMIT represents the git commit hash of the package, it is configured at build time
|
||||
GITCOMMIT string
|
||||
)
|
||||
|
||||
// UserAgent returns a string to be used by API
|
||||
func UserAgent() string {
|
||||
return fmt.Sprintf("scw/%v", VERSION)
|
||||
}
|
14
vendor/github.com/scaleway/scaleway-cli/pkg/scwversion/version_test.go
generated
vendored
Normal file
14
vendor/github.com/scaleway/scaleway-cli/pkg/scwversion/version_test.go
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
package scwversion
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
Convey("Testing init()", t, func() {
|
||||
So(VERSION, ShouldNotEqual, "")
|
||||
// So(GITCOMMIT, ShouldNotEqual, "")
|
||||
})
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
layout: "scaleway"
|
||||
page_title: "Provider: Scaleway"
|
||||
sidebar_current: "docs-scaleway-index"
|
||||
description: |-
|
||||
The Scaleway provider is used to interact with Scaleway ARM cloud provider.
|
||||
---
|
||||
|
||||
# Scaleway Provider
|
||||
|
||||
The Scaleway provider is used to manage Scaleway resources.
|
||||
|
||||
Use the navigation to the left to read about the available resources.
|
||||
|
||||
## Example Usage
|
||||
|
||||
Here is an example that will setup the following:
|
||||
+ An ARM Server.
|
||||
+ An IP Address.
|
||||
+ A security group.
|
||||
|
||||
(create this as sl.tf and run terraform commands from this directory):
|
||||
|
||||
```hcl
|
||||
provider "scaleway" {
|
||||
access_key = ""
|
||||
organization = ""
|
||||
}
|
||||
|
||||
resource "scaleway_ip" "ip" {
|
||||
server = "${scaleway_server.test.id}"
|
||||
}
|
||||
|
||||
resource "scaleway_server" "test" {
|
||||
name = "test"
|
||||
image = "aecaed73-51a5-4439-a127-6d8229847145"
|
||||
type = "C2S"
|
||||
}
|
||||
|
||||
resource "scaleway_volume" "test" {
|
||||
name = "test"
|
||||
size_in_gb = 20
|
||||
type = "l_ssd"
|
||||
}
|
||||
|
||||
resource "scaleway_volume_attachment" "test" {
|
||||
server = "${scaleway_server.test.id}"
|
||||
volume = "${scaleway_volume.test.id}"
|
||||
}
|
||||
|
||||
resource "scaleway_security_group" "http" {
|
||||
name = "http"
|
||||
description = "allow HTTP and HTTPS traffic"
|
||||
}
|
||||
|
||||
resource "scaleway_security_group_rule" "http_accept" {
|
||||
security_group = "${scaleway_security_group.http.id}"
|
||||
|
||||
action = "accept"
|
||||
direction = "inbound"
|
||||
ip_range = "0.0.0.0/0"
|
||||
protocol = "TCP"
|
||||
dest_port_from = 80
|
||||
}
|
||||
|
||||
resource "scaleway_security_group_rule" "https_accept" {
|
||||
security_group = "${scaleway_security_group.http.id}"
|
||||
|
||||
action = "accept"
|
||||
direction = "inbound"
|
||||
ip_range = "0.0.0.0/0"
|
||||
protocol = "TCP"
|
||||
dest_port_from = 443
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
You'll need to provide your Scaleway organization and access key,
|
||||
so that Terraform can connect. If you don't want to put
|
||||
credentials in your configuration file, you can leave them
|
||||
out:
|
||||
|
||||
```
|
||||
provider "scaleway" {}
|
||||
```
|
||||
|
||||
...and instead set these environment variables:
|
||||
|
||||
- **SCALEWAY_ORGANIZATION**: Your Scaleway organization
|
||||
- **SCALEWAY_ACCESS_KEY**: Your API Access key
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
layout: "scaleway"
|
||||
page_title: "Scaleway: ip"
|
||||
sidebar_current: "docs-scaleway-resource-ip"
|
||||
description: |-
|
||||
Manages Scaleway IPs.
|
||||
---
|
||||
|
||||
# scaleway\ip
|
||||
|
||||
Provides IPs for ARM servers. This allows IPs to be created, updated and deleted.
|
||||
For additional details please refer to [API documentation](https://developer.scaleway.com/#ips).
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "scaleway_ip" "test_ip" {
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `server` - (Optional) ID of ARM server to associate IP with
|
||||
|
||||
Field `server` are editable.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `id` - id of the new resource
|
||||
* `ip` - IP of the new resource
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
layout: "scaleway"
|
||||
page_title: "Scaleway: security_group"
|
||||
sidebar_current: "docs-scaleway-resource-security_group"
|
||||
description: |-
|
||||
Manages Scaleway security groups.
|
||||
---
|
||||
|
||||
# scaleway\security_group
|
||||
|
||||
Provides security groups. This allows security groups to be created, updated and deleted.
|
||||
For additional details please refer to [API documentation](https://developer.scaleway.com/#security-groups).
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "scaleway_security_group" "test" {
|
||||
name = "test"
|
||||
description = "test"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `name` - (Required) name of security group
|
||||
* `description` - (Required) description of security group
|
||||
|
||||
Field `name`, `description` are editable.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `id` - id of the new resource
|
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
layout: "scaleway"
|
||||
page_title: "Scaleway: security_group_rule"
|
||||
sidebar_current: "docs-scaleway-resource-security_group_rule"
|
||||
description: |-
|
||||
Manages Scaleway security group rules.
|
||||
---
|
||||
|
||||
# scaleway\security_group_rule
|
||||
|
||||
Provides security group rules. This allows security group rules to be created, updated and deleted.
|
||||
For additional details please refer to [API documentation](https://developer.scaleway.com/#security-groups-manage-rules).
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "scaleway_security_group" "test" {
|
||||
name = "test"
|
||||
description = "test"
|
||||
}
|
||||
|
||||
resource "scaleway_security_group_rule" "smtp_drop_1" {
|
||||
security_group = "${scaleway_security_group.test.id}"
|
||||
|
||||
action = "accept"
|
||||
direction = "inbound"
|
||||
ip_range = "0.0.0.0/0"
|
||||
protocol = "TCP"
|
||||
dest_port_from = 25
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `action` - (Required) action of rule (`accept`, `drop`)
|
||||
* `direction` - (Required) direction of rule (`inbound`, `outbound`)
|
||||
* `ip_range` - (Required) ip_range of rule
|
||||
* `protocol` - (Required) protocol of rule (`ICMP`, `TCP`, `UDP`)
|
||||
* `dest_port_from` - (Optional) port range from
|
||||
* `dest_port_to` - (Optional) port from to
|
||||
|
||||
Field `action`, `direction`, `ip_range`, `protocol`, `dest_port_from`, `dest_port_to` are editable.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `id` - id of the new resource
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
layout: "scaleway"
|
||||
page_title: "Scaleway: server"
|
||||
sidebar_current: "docs-scaleway-resource-server"
|
||||
description: |-
|
||||
Manages Scaleway servers.
|
||||
---
|
||||
|
||||
# scaleway\server
|
||||
|
||||
Provides ARM servers. This allows servers to be created, updated and deleted.
|
||||
For additional details please refer to [API documentation](https://developer.scaleway.com/#servers).
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "scaleway_server" "test" {
|
||||
name = "test"
|
||||
image = "5faef9cd-ea9b-4a63-9171-9e26bec03dbc"
|
||||
type = "C1"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `name` - (Required) name of ARM server
|
||||
* `image` - (Required) base image of ARM server
|
||||
* `type` - (Required) type of ARM server
|
||||
|
||||
Field `name`, `type` are editable.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `id` - id of the new resource
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
layout: "scaleway"
|
||||
page_title: "Scaleway: volume"
|
||||
sidebar_current: "docs-scaleway-resource-volume"
|
||||
description: |-
|
||||
Manages Scaleway Volumes.
|
||||
---
|
||||
|
||||
# scaleway\volume
|
||||
|
||||
Provides ARM volumes. This allows volumes to be created, updated and deleted.
|
||||
For additional details please refer to [API documentation](https://developer.scaleway.com/#volumes).
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "scaleway_volume" "test" {
|
||||
name = "test"
|
||||
image = "aecaed73-51a5-4439-a127-6d8229847145"
|
||||
type = "C2S"
|
||||
volumes = ["${scaleway_volume.test.id}"]
|
||||
}
|
||||
|
||||
resource "scaleway_volume" "test" {
|
||||
name = "test"
|
||||
size_in_gb = 20
|
||||
type = "l_ssd"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `name` - (Required) name of volume
|
||||
* `size_in_gb` - (Required) size of the volume in GB
|
||||
* `type` - (Required) type of volume
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `id` - id of the new resource
|
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
layout: "scaleway"
|
||||
page_title: "Scaleway: volume attachment"
|
||||
sidebar_current: "docs-scaleway-resource-volume attachment"
|
||||
description: |-
|
||||
Manages Scaleway Volume attachments for servers.
|
||||
---
|
||||
|
||||
# scaleway\volume\_attachment
|
||||
|
||||
This allows volumes to be attached to servers.
|
||||
|
||||
**Warning:** Attaching volumes requires the servers to be powered off. This will lead
|
||||
to downtime if the server is already in use.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "scaleway_server" "test" {
|
||||
name = "test"
|
||||
image = "aecaed73-51a5-4439-a127-6d8229847145"
|
||||
type = "C2S"
|
||||
}
|
||||
|
||||
resource "scaleway_volume" "test" {
|
||||
name = "test"
|
||||
size_in_gb = 20
|
||||
type = "l_ssd"
|
||||
}
|
||||
|
||||
resource "scaleway_volume_attachment" "test" {
|
||||
server = "${scaleway_server.test.id}"
|
||||
volume = "${scaleway_volume.test.id}"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `server` - (Required) id of the server
|
||||
* `volume` - (Required) id of the volume to be attached
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `id` - id of the new resource
|
|
@ -298,6 +298,10 @@
|
|||
<a href="/docs/providers/softlayer/index.html">SoftLayer</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-providers-scaleway") %>>
|
||||
<a href="/docs/providers/scaleway/index.html">Scaleway</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-providers-template") %>>
|
||||
<a href="/docs/providers/template/index.html">Template</a>
|
||||
</li>
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
<% wrap_layout :inner do %>
|
||||
<% content_for :sidebar do %>
|
||||
<div class="docs-sidebar hidden-print affix-top" role="complementary">
|
||||
<ul class="nav docs-sidenav">
|
||||
<li<%= sidebar_current("docs-home") %>>
|
||||
<a href="/docs/providers/index.html">« Documentation Home</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-scaleway-index") %>>
|
||||
<a href="/docs/providers/scaleway/index.html">Scaleway Provider</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current(/^docs-scaleway-resource/) %>>
|
||||
<a href="#">Resources</a>
|
||||
<ul class="nav nav-visible">
|
||||
<li<%= sidebar_current("docs-scaleway-resource-ip") %>>
|
||||
<a href="/docs/providers/scaleway/r/ip.html">IP</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-scaleway-resource-server") %>>
|
||||
<a href="/docs/providers/scaleway/r/server.html">server</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-scaleway-resource-security_group") %>>
|
||||
<a href="/docs/providers/scaleway/r/security_group.html">security_group</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-scaleway-resource-security_group_rule") %>>
|
||||
<a href="/docs/providers/scaleway/r/security_group_rule.html">security_group_rule</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-scaleway-resource-volume") %>>
|
||||
<a href="/docs/providers/scaleway/r/volume.html">volume</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-scaleway-resource-volume_attachment") %>>
|
||||
<a href="/docs/providers/scaleway/r/volume_attachment.html">volume_attachment</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= yield %>
|
||||
<% end %>
|
Loading…
Reference in New Issue