From c8cc3a14027ffb531c7316f48ba5f624081372b1 Mon Sep 17 00:00:00 2001 From: Lucas Serven Date: Fri, 20 Apr 2018 17:27:55 +0200 Subject: [PATCH] vultr: add reserved IP --- examples/reserved_ip/example.tf | 56 ++++++++++++ vultr/provider.go | 1 + vultr/resource_reserved_ip.go | 152 ++++++++++++++++++++++++++++++++ vultr/validators.go | 10 +++ 4 files changed, 219 insertions(+) create mode 100644 examples/reserved_ip/example.tf create mode 100644 vultr/resource_reserved_ip.go diff --git a/examples/reserved_ip/example.tf b/examples/reserved_ip/example.tf new file mode 100644 index 00000000..6eb46c57 --- /dev/null +++ b/examples/reserved_ip/example.tf @@ -0,0 +1,56 @@ +// Configure the Vultr provider. +// Alternatively, export the API key as an environment variable: `export VULTR_API_KEY=`. +#provider "vultr" { +#api_key = "" +#} + +// Find the ID of the Silicon Valley region. +data "vultr_region" "silicon_valley" { + filter { + name = "name" + values = ["Silicon Valley"] + } +} + +// Find the ID for CoreOS Container Linux. +data "vultr_os" "container_linux" { + filter { + name = "family" + values = ["coreos"] + } +} + +// Find the ID for a starter plan. +data "vultr_plan" "starter" { + filter { + name = "price_per_month" + values = ["5.00"] + } + + filter { + name = "ram" + values = ["1024"] + } +} + +// Create a Vultr virtual machine. +resource "vultr_instance" "example" { + name = "example" + region_id = "${data.vultr_region.silicon_valley.id}" + plan_id = "${data.vultr_plan.starter.id}" + os_id = "${data.vultr_os.container_linux.id}" + ssh_key_ids = ["${vultr_ssh_key.squat.id}"] +} + +// Create a new SSH key. +resource "vultr_ssh_key" "squat" { + name = "squat" + public_key = "${file("~/lserven.ssh")}" +} + +// Create a reserved IP. +resource "vultr_reserved_ip" "example" { + name = "example" + attached_id = "${vultr_instance.example.id}" + region_id = "${data.vultr_region.silicon_valley.id}" +} diff --git a/vultr/provider.go b/vultr/provider.go index 2fbc7aa6..d22ab164 100644 --- a/vultr/provider.go +++ b/vultr/provider.go @@ -38,6 +38,7 @@ func Provider() terraform.ResourceProvider { "vultr_firewall_rule": resourceFirewallRule(), "vultr_instance": resourceInstance(), "vultr_ipv4": resourceIPV4(), + "vultr_reserved_ip": resourceReservedIP(), "vultr_startup_script": resourceStartupScript(), "vultr_ssh_key": resourceSSHKey(), }, diff --git a/vultr/resource_reserved_ip.go b/vultr/resource_reserved_ip.go new file mode 100644 index 00000000..3c227d60 --- /dev/null +++ b/vultr/resource_reserved_ip.go @@ -0,0 +1,152 @@ +package vultr + +import ( + "fmt" + "log" + + "github.com/JamesClonk/vultr/lib" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceReservedIP() *schema.Resource { + return &schema.Resource{ + Create: resourceReservedIPCreate, + Read: resourceReservedIPRead, + Update: resourceReservedIPUpdate, + Delete: resourceReservedIPDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "attached_id": { + Type: schema.TypeString, + Optional: true, + }, + + "cidr": { + Type: schema.TypeString, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "region_id": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "type": { + Type: schema.TypeString, + Default: "v4", + Optional: true, + ForceNew: true, + ValidateFunc: validateReservedIPType, + }, + }, + } +} + +func resourceReservedIPCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Client) + + aid := d.Get("attached_id").(string) + name := d.Get("name").(string) + regionID := d.Get("region_id").(int) + ipType := d.Get("type").(string) + + log.Printf("[INFO] Creating new reserved ip") + rid, err := client.CreateReservedIP(regionID, ipType, name) + if err != nil { + return fmt.Errorf("Error creating reserved ip: %v", err) + } + + d.SetId(rid) + + if aid != "" { + rip, err := client.GetReservedIP(rid) + if err != nil { + return fmt.Errorf("Error getting address for reserved ip (%s): %v", d.Id(), err) + } + err = client.AttachReservedIP(reservedIPtoCIDR(rip), aid) + if err != nil { + return fmt.Errorf("Error attaching reserved ip (%s) to instance (%s): %v", d.Id(), aid, err) + } + } + + return resourceReservedIPRead(d, meta) +} + +func resourceReservedIPRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Client) + + rip, err := client.GetReservedIP(d.Id()) + if err != nil { + return fmt.Errorf("Error getting reserved ip (%s): %v", d.Id(), err) + } + + if rip == (lib.IP{}) { + log.Printf("[WARN] Removing reserved ip (%s) because it is gone", d.Id()) + d.SetId("") + return nil + } + + d.Set("attached_id", rip.AttachedTo) + d.Set("cidr", reservedIPtoCIDR(rip)) + d.Set("name", rip.Label) + d.Set("region_id", rip.RegionID) + d.Set("type", rip.IPType) + + return nil +} + +func resourceReservedIPUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Client) + + if d.HasChange("attached_id") { + ip := d.Get("cidr").(string) + log.Printf("[INFO] Updating reserved IP (%s) attachment", d.Id()) + old, new := d.GetChange("attached_id") + if old.(string) != "" { + if err := client.DetachReservedIP(old.(string), ip); err != nil { + return fmt.Errorf("Error detaching reserved IP (%s) from instance (%s): %v", d.Id(), old.(string), err) + } + } + if new.(string) != "" { + if err := client.AttachReservedIP(ip, new.(string)); err != nil { + return fmt.Errorf("Error attaching reserved IP (%s) to instance (%s): %v", d.Id(), new.(string), err) + } + } + } + + return resourceReservedIPRead(d, meta) +} + +func resourceReservedIPDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Client) + + log.Printf("[INFO] Destroying reserved ip (%s)", d.Id()) + + aid := d.Get("attached_id").(string) + if aid != "" { + ip := d.Get("cidr").(string) + if err := client.DetachReservedIP(aid, ip); err != nil { + return fmt.Errorf("Error detaching reserved IP (%s) from instance (%s): %v", d.Id(), aid, err) + } + } + + if err := client.DestroyReservedIP(d.Id()); err != nil { + return fmt.Errorf("Error destroying reserved ip (%s): %v", d.Id(), err) + } + + return nil +} + +func reservedIPtoCIDR(rip lib.IP) string { + return fmt.Sprintf("%s/%d", rip.Subnet, rip.SubnetSize) +} diff --git a/vultr/validators.go b/vultr/validators.go index 643e8ade..0cc20391 100644 --- a/vultr/validators.go +++ b/vultr/validators.go @@ -34,6 +34,16 @@ func validateIPAddress(v interface{}, k string) (ws []string, errors []error) { return } +// validateReservedIPType ensures that the string value is either "v4" or "v6". +func validateReservedIPType(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "v6" && value != "v4" { + errors = append(errors, fmt.Errorf("%q must be either 'v4' or 'v6'", k)) + return + } + return +} + // validateFirewallRuleProtocol ensures that the string value is a valid // firewall rule protocol and returns an error otherwise. func validateFirewallRuleProtocol(v interface{}, k string) (ws []string, errors []error) {