From c5d1f8f88f26b63d1164bbe79f6399e43c393dcf Mon Sep 17 00:00:00 2001 From: Stephen Cross Date: Wed, 29 Mar 2017 16:30:08 +0000 Subject: [PATCH] Initial Oracle Compute Cloud provider --- builtin/providers/oracleopc/config.go | 47 +++ builtin/providers/oracleopc/provider.go | 75 +++++ builtin/providers/oracleopc/provider_test.go | 61 ++++ .../providers/oracleopc/resource_instance.go | 306 ++++++++++++++++++ .../oracleopc/resource_instance_test.go | 156 +++++++++ .../oracleopc/resource_ip_association.go | 103 ++++++ .../oracleopc/resource_ip_association_test.go | 74 +++++ .../oracleopc/resource_ip_reservation.go | 122 +++++++ .../resource_security_application.go | 124 +++++++ .../resource_security_association.go | 103 ++++++ .../resource_security_association_test.go | 75 +++++ .../oracleopc/resource_security_ip_list.go | 117 +++++++ .../oracleopc/resource_security_list.go | 119 +++++++ .../oracleopc/resource_security_rule.go | 143 ++++++++ .../oracleopc/resource_security_rule_test.go | 85 +++++ .../providers/oracleopc/resource_ssh_key.go | 117 +++++++ .../oracleopc/resource_storage_volume.go | 301 +++++++++++++++++ .../oracleopc/resource_storage_volume_test.go | 70 ++++ 18 files changed, 2198 insertions(+) create mode 100644 builtin/providers/oracleopc/config.go create mode 100644 builtin/providers/oracleopc/provider.go create mode 100644 builtin/providers/oracleopc/provider_test.go create mode 100644 builtin/providers/oracleopc/resource_instance.go create mode 100644 builtin/providers/oracleopc/resource_instance_test.go create mode 100644 builtin/providers/oracleopc/resource_ip_association.go create mode 100644 builtin/providers/oracleopc/resource_ip_association_test.go create mode 100644 builtin/providers/oracleopc/resource_ip_reservation.go create mode 100644 builtin/providers/oracleopc/resource_security_application.go create mode 100644 builtin/providers/oracleopc/resource_security_association.go create mode 100644 builtin/providers/oracleopc/resource_security_association_test.go create mode 100644 builtin/providers/oracleopc/resource_security_ip_list.go create mode 100644 builtin/providers/oracleopc/resource_security_list.go create mode 100644 builtin/providers/oracleopc/resource_security_rule.go create mode 100644 builtin/providers/oracleopc/resource_security_rule_test.go create mode 100644 builtin/providers/oracleopc/resource_ssh_key.go create mode 100644 builtin/providers/oracleopc/resource_storage_volume.go create mode 100644 builtin/providers/oracleopc/resource_storage_volume_test.go diff --git a/builtin/providers/oracleopc/config.go b/builtin/providers/oracleopc/config.go new file mode 100644 index 000000000..fbae3b5d5 --- /dev/null +++ b/builtin/providers/oracleopc/config.go @@ -0,0 +1,47 @@ +package opc + +import ( + "fmt" + "github.com/oracle/terraform-provider-compute/sdk/compute" + "net/url" +) + +type Config struct { + User string + Password string + IdentityDomain string + Endpoint string + MaxRetryTimeout int +} + +type storageAttachment struct { + index int + instanceName *compute.InstanceName +} + +type OPCClient struct { + *compute.AuthenticatedClient + MaxRetryTimeout int + storageAttachmentsByVolumeCache map[string][]storageAttachment +} + +func (c *Config) Client() (*OPCClient, error) { + u, err := url.ParseRequestURI(c.Endpoint) + if err != nil { + return nil, fmt.Errorf("Invalid endpoint URI: %s", err) + } + + client := compute.NewComputeClient(c.IdentityDomain, c.User, c.Password, u) + authenticatedClient, err := client.Authenticate() + if err != nil { + return nil, fmt.Errorf("Authentication failed: %s", err) + } + + opcClient := &OPCClient{ + AuthenticatedClient: authenticatedClient, + MaxRetryTimeout: c.MaxRetryTimeout, + storageAttachmentsByVolumeCache: make(map[string][]storageAttachment), + } + + return opcClient, nil +} diff --git a/builtin/providers/oracleopc/provider.go b/builtin/providers/oracleopc/provider.go new file mode 100644 index 000000000..a6d0d3fb5 --- /dev/null +++ b/builtin/providers/oracleopc/provider.go @@ -0,0 +1,75 @@ +package opc + +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{ + "user": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("OPC_USERNAME", nil), + Description: "The user name for OPC API operations.", + }, + + "password": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("OPC_PASSWORD", nil), + Description: "The user password for OPC API operations.", + }, + + "identityDomain": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("OPC_IDENTITY_DOMAIN", nil), + Description: "The OPC identity domain for API operations", + }, + + "endpoint": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("OPC_ENDPOINT", nil), + Description: "The HTTP endpoint for OPC API operations.", + }, + + "maxRetryTimeout": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("OPC_MAX_RETRY_TIMEOUT", 3000), + Description: "Max num seconds to wait for successful response when operating on resources within OPC (defaults to 3000)", + }, + }, + + ResourcesMap: map[string]*schema.Resource{ + "opc_compute_storage_volume": resourceStorageVolume(), + "opc_compute_instance": resourceInstance(), + "opc_compute_ssh_key": resourceSSHKey(), + "opc_compute_security_application": resourceSecurityApplication(), + "opc_compute_security_list": resourceSecurityList(), + "opc_compute_security_ip_list": resourceSecurityIPList(), + "opc_compute_ip_reservation": resourceIPReservation(), + "opc_compute_ip_association": resourceIPAssociation(), + "opc_compute_security_rule": resourceSecurityRule(), + "opc_compute_security_association": resourceSecurityAssociation(), + }, + + ConfigureFunc: providerConfigure, + } +} + +func providerConfigure(d *schema.ResourceData) (interface{}, error) { + config := Config{ + User: d.Get("user").(string), + Password: d.Get("password").(string), + IdentityDomain: d.Get("identityDomain").(string), + Endpoint: d.Get("endpoint").(string), + MaxRetryTimeout: d.Get("maxRetryTimeout").(int), + } + + return config.Client() +} diff --git a/builtin/providers/oracleopc/provider_test.go b/builtin/providers/oracleopc/provider_test.go new file mode 100644 index 000000000..c60076b06 --- /dev/null +++ b/builtin/providers/oracleopc/provider_test.go @@ -0,0 +1,61 @@ +package opc + +import ( + "os" + "testing" + + "fmt" + "github.com/hashicorp/terraform/helper/resource" + "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{ + "opc": 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) { + required := []string{"OPC_USERNAME", "OPC_PASSWORD", "OPC_IDENTITY_DOMAIN", "OPC_ENDPOINT"} + for _, prop := range required { + if os.Getenv(prop) == "" { + t.Fatalf("%s must be set for acceptance test", prop) + } + } +} + +type OPCResourceState struct { + *OPCClient + *terraform.InstanceState +} + +func opcResourceCheck(resourceName string, f func(checker *OPCResourceState) error) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Resource not found: %s", resourceName) + } + + state := &OPCResourceState{ + OPCClient: testAccProvider.Meta().(*OPCClient), + InstanceState: rs.Primary, + } + + return f(state) + } +} diff --git a/builtin/providers/oracleopc/resource_instance.go b/builtin/providers/oracleopc/resource_instance.go new file mode 100644 index 000000000..70f3b99c8 --- /dev/null +++ b/builtin/providers/oracleopc/resource_instance.go @@ -0,0 +1,306 @@ +package opc + +import ( + "encoding/json" + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/oracle/terraform-provider-compute/sdk/compute" + "log" +) + +func resourceInstance() *schema.Resource { + return &schema.Resource{ + Create: resourceInstanceCreate, + Read: resourceInstanceRead, + Delete: resourceInstanceDelete, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "shape": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "imageList": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "label": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "ip": { + Type: schema.TypeString, + Optional: false, + Computed: true, + }, + + "opcId": { + Type: schema.TypeString, + Optional: false, + Computed: true, + }, + + "sshKeys": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "attributes": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "vcable": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "storage": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "index": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "volume": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "bootOrder": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeInt}, + }, + }, + } +} + +func getAttrs(d *schema.ResourceData) (*map[string]interface{}, error) { + var attrs map[string]interface{} + + attrString := d.Get("attributes").(string) + if attrString == "" { + return &attrs, nil + } + if err := json.Unmarshal([]byte(attrString), &attrs); err != nil { + return &attrs, fmt.Errorf("Cannot parse '%s' as json", attrString) + } + return &attrs, nil +} + +func resourceInstanceCreate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource data: %#v", d.State()) + + client := meta.(*OPCClient).Instances() + name := d.Get("name").(string) + shape := d.Get("shape").(string) + imageList := d.Get("imageList").(string) + label := d.Get("label").(string) + storage := getStorageAttachments(d) + sshKeys := getSSHKeys(d) + bootOrder := getBootOrder(d) + + attrs, err := getAttrs(d) + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating instance with name %s, shape %s, imageList %s, storage %s, bootOrder %s, label %s, sshKeys %s, attrs %#v", + name, shape, imageList, storage, bootOrder, label, sshKeys, attrs) + + id, err := client.LaunchInstance(name, label, shape, imageList, storage, bootOrder, sshKeys, *attrs) + if err != nil { + return fmt.Errorf("Error creating instance %s: %s", name, err) + } + + log.Printf("[DEBUG] Waiting for instance %s to come online", id.String()) + info, err := client.WaitForInstanceRunning(id, meta.(*OPCClient).MaxRetryTimeout) + if err != nil { + return fmt.Errorf("Error waiting for instance %s to come online: %s", id, err) + } + + log.Printf("[DEBUG] Created instance %s: %#v", id, info) + + attachStorage( + &compute.InstanceName{ + Name: info.Name, + ID: info.ID, + }, + d, meta) + + d.SetId(info.Name) + updateInstanceResourceData(d, info) + return nil +} + +func attachStorage(name *compute.InstanceName, d *schema.ResourceData, meta interface{}) error { + storageClient := meta.(*OPCClient).StorageAttachments() + storage := d.Get("storage").(*schema.Set) + updatedStorage := schema.NewSet(storage.F, []interface{}{}) + + for _, i := range storage.List() { + attrs := i.(map[string]interface{}) + attachmentInfo, err := storageClient.CreateStorageAttachment( + attrs["index"].(int), + name, + attrs["volume"].(string)) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Waiting for storage attachment %#v to come online", attachmentInfo) + storageClient.WaitForStorageAttachmentCreated(attachmentInfo.Name, meta.(*OPCClient).MaxRetryTimeout) + log.Printf("[DEBUG] Storage attachment %s: %s-%s created", + attachmentInfo.Name, attachmentInfo.InstanceName, attachmentInfo.StorageVolumeName) + attrs["name"] = attachmentInfo.Name + updatedStorage.Add(attrs) + } + + d.Set("storage", updatedStorage) + return nil +} + +func getSSHKeys(d *schema.ResourceData) []string { + sshKeys := []string{} + for _, i := range d.Get("sshKeys").([]interface{}) { + sshKeys = append(sshKeys, i.(string)) + } + return sshKeys +} + +func getBootOrder(d *schema.ResourceData) []int { + bootOrder := []int{} + for _, i := range d.Get("bootOrder").([]interface{}) { + bootOrder = append(bootOrder, i.(int)) + } + return bootOrder +} + +func getStorageAttachments(d *schema.ResourceData) []compute.LaunchPlanStorageAttachmentSpec { + storageAttachments := []compute.LaunchPlanStorageAttachmentSpec{} + storage := d.Get("storage").(*schema.Set) + for _, i := range storage.List() { + attrs := i.(map[string]interface{}) + storageAttachments = append(storageAttachments, compute.LaunchPlanStorageAttachmentSpec{ + Index: attrs["index"].(int), + Volume: attrs["volume"].(string), + }) + } + return storageAttachments +} + +func updateInstanceResourceData(d *schema.ResourceData, info *compute.InstanceInfo) error { + d.Set("name", info.Name) + d.Set("opcId", info.ID) + d.Set("imageList", info.ImageList) + d.Set("bootOrder", info.BootOrder) + d.Set("sshKeys", info.SSHKeys) + d.Set("label", info.Label) + d.Set("ip", info.IPAddress) + d.Set("vcable", info.VCableID) + + return nil +} + +func resourceInstanceRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource data: %#v", d.State()) + client := meta.(*OPCClient).Instances() + name := d.Get("name").(string) + instanceName := &compute.InstanceName{ + Name: name, + ID: d.Get("opcId").(string), + } + + log.Printf("[DEBUG] Reading state of instance %s", instanceName) + result, err := client.GetInstance(instanceName) + if err != nil { + // Instance doesn't exist + if compute.WasNotFoundError(err) { + log.Printf("[DEBUG] Instance %s not found", instanceName) + d.SetId("") + return nil + } + return fmt.Errorf("Error reading instance %s: %s", instanceName, err) + } + + log.Printf("[DEBUG] Read state of instance %s: %#v", instanceName, result) + + attachments, err := meta.(*OPCClient).StorageAttachments().GetStorageAttachmentsForInstance(instanceName) + if err != nil { + return fmt.Errorf("Error reading storage attachments for instance %s: %s", instanceName, err) + } + updateInstanceResourceData(d, result) + updateAttachmentResourceData(d, attachments) + return nil +} + +func updateAttachmentResourceData(d *schema.ResourceData, attachments *[]compute.StorageAttachmentInfo) { + attachmentSet := schema.NewSet(d.Get("storage").(*schema.Set).F, []interface{}{}) + for _, attachment := range *attachments { + properties := map[string]interface{}{ + "index": attachment.Index, + "volume": attachment.StorageVolumeName, + "name": attachment.Name, + } + attachmentSet.Add(properties) + } + d.Set("storage", attachmentSet) +} + +func resourceInstanceDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource data: %#v", d.State()) + client := meta.(*OPCClient).Instances() + name := d.Get("name").(string) + + instanceName := &compute.InstanceName{ + Name: name, + ID: d.Get("opcId").(string), + } + + log.Printf("[DEBUG] Deleting instance %s", instanceName) + if err := client.DeleteInstance(instanceName); err != nil { + return fmt.Errorf("Error deleting instance %s: %s", instanceName, err) + } + if err := client.WaitForInstanceDeleted(instanceName, meta.(*OPCClient).MaxRetryTimeout); err != nil { + return fmt.Errorf("Error deleting instance %s: %s", instanceName, err) + } + + for _, attachment := range d.Get("storage").(*schema.Set).List() { + name := attachment.(map[string]interface{})["name"].(string) + log.Printf("[DEBUG] Deleting storage attachment %s", name) + client.StorageAttachments().DeleteStorageAttachment(name) + client.StorageAttachments().WaitForStorageAttachmentDeleted(name, meta.(*OPCClient).MaxRetryTimeout) + } + + return nil +} diff --git a/builtin/providers/oracleopc/resource_instance_test.go b/builtin/providers/oracleopc/resource_instance_test.go new file mode 100644 index 000000000..6f386af84 --- /dev/null +++ b/builtin/providers/oracleopc/resource_instance_test.go @@ -0,0 +1,156 @@ +package opc + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/resource" + "github.com/oracle/terraform-provider-compute/sdk/compute" + "testing" +) + +func TestAccOPCInstance_Basic(t *testing.T) { + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: opcResourceCheck( + instanceResourceName, + testAccCheckInstanceDestroyed), + Steps: []resource.TestStep{ + { + Config: testAccInstanceBasic, + Check: resource.ComposeTestCheckFunc( + opcResourceCheck( + instanceResourceName, + testAccCheckInstanceExists), + opcResourceCheck( + keyResourceName, + testAccCheckSSHKeyExists), + ), + }, + { + Config: modifySSHKey, + Check: resource.ComposeTestCheckFunc( + opcResourceCheck( + instanceResourceName, + testAccCheckInstanceExists), + opcResourceCheck( + keyResourceName, + testAccCheckSSHKeyUpdated), + ), + }, + }, + }) +} + +func testAccCheckInstanceExists(state *OPCResourceState) error { + instanceName := getInstanceName(state) + + if _, err := state.Instances().GetInstance(instanceName); err != nil { + return fmt.Errorf("Error retrieving state of instance %s: %s", instanceName, err) + } + + return nil +} + +func testAccCheckSSHKeyExists(state *OPCResourceState) error { + keyName := state.Attributes["name"] + + if _, err := state.SSHKeys().GetSSHKey(keyName); err != nil { + return fmt.Errorf("Error retrieving state of key %s: %s", keyName, err) + } + + return nil +} + +func testAccCheckSSHKeyUpdated(state *OPCResourceState) error { + keyName := state.Attributes["name"] + info, err := state.SSHKeys().GetSSHKey(keyName) + if err != nil { + return err + } + if info.Key != updatedKey { + return fmt.Errorf("Expected key\n\t%s\nbut was\n\t%s", updatedKey, info.Key) + } + return nil +} + +func getInstanceName(rs *OPCResourceState) *compute.InstanceName { + return &compute.InstanceName{ + Name: rs.Attributes["name"], + ID: rs.Attributes["opcId"], + } +} + +func testAccCheckInstanceDestroyed(state *OPCResourceState) error { + instanceName := getInstanceName(state) + if info, err := state.Instances().GetInstance(instanceName); err == nil { + return fmt.Errorf("Instance %s still exists: %#v", instanceName, info) + } + + return nil +} + +const instanceName = "test_instance" +const keyName = "test_key" + +var instanceResourceName = fmt.Sprintf("opc_compute_instance.%s", instanceName) +var keyResourceName = fmt.Sprintf("opc_compute_ssh_key.%s", keyName) + +const originalKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCqw6JwbjIkZEr5UcMojtxhk6Zum39NOihHNXEvRWDt5WssX8TH/ghpv3D25K1pJkf+wfAi17HwEmYwPMEyEHENS443v6RZbXvzCkUWzkJzq7Zvbdqld038km31La2QUoMMp1KL5zk1nM65xCeQDVcR/h++03EScB2CuzTpAV6khMdfgOJgxm361kfrDVRwc1HQrAOpOnzkpPfwqBrYWqN1UnKvuO77Wk8z5LBe03EPNru3bLE3s3qHI9hjO0gXMiVUi0KyNxdWfDO8esqQlKavHAeePyrRA55YF8kBB5dEl4tVNOqpY/8TRnGN1mOe0LWxa8Ytz1wbyS49knsNVTel" +const updatedKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDHvb/2OSemgzUYLNW1/T3u33r7sZy1qbWtgVWiREH4gS5TVmDVPuvN1MFLdNqiWQA53gK8Gp24jtjNm9ftcPhicv81HVWJTB69C0sJGEfF0l4mgbemJLH3i37Mb6SdWJcGof9qHVDADPgiC8jIBVUhdiJSeq4fUJ3NQA2eUExBkRglQWairkNzPNA0mi3GL9KDGnoBnSCAXNGoKgDgIOqW0dYFP6oHyGWkF7V+/TME9aIQvmMpHjVzl7brZ/wED2t5vTJxxbgogHEmWnfs7p8EP5IsN6Vnjd0VNIt1tu3TduS8kH5npkPqZz8oIP93Ypxn0l7ZNEl9MahbhPj3gJ1YY7Cygrlt1VLC1ibBbOgIS2Lj6vGG/Yjkqs3Vw6qrmTRlsJ9c6bZO2xq0xzV11XQHvjPegBOClF6AztEe1jKU/RUFnzjIF8lUmM63fTaXuVkNERkTSE3E9XL3Uq6eqYdef7wHFFhCMSGotp3ANAb30kflysA9ID0b3o5QU2tB8OBxBicXQy11lh+u204YJuvIzeTXo+JAad5TWFlJcsUlbPFppLQdhUpoWaJouBGJV36DJb9R34i9T8Ze5tnJUQgPmMkERyPvb/+v5j3s2hs1A9WO6/MqmZd70gudsX/1bqWT898vCCOdM+CspNVY7nHVUtde7C6BrHzphr/C1YBXHw==" + +var testAccInstanceBasic = fmt.Sprintf(` +resource "opc_compute_instance" "%s" { + name = "test" + label = "test" + shape = "oc3" + imageList = "/oracle/public/oel_6.4_2GB_v1" + sshKeys = ["${opc_compute_ssh_key.test_key.name}"] + attributes = "{\"foo\": \"bar\"}" + storage = { + index = 1 + volume = "${opc_compute_storage_volume.test_volume.name}" + } +} + +resource "opc_compute_storage_volume" "test_volume" { + size = "3g" + description = "My volume" + name = "test_volume_b" + tags = ["foo", "bar", "baz"] +} + +resource "opc_compute_ssh_key" "%s" { + name = "test-key" + key = "%s" + enabled = true +} +`, instanceName, keyName, originalKey) + +var modifySSHKey = fmt.Sprintf(` +resource "opc_compute_instance" "%s" { + name = "test" + label = "test" + shape = "oc3" + imageList = "/oracle/public/oel_6.4_2GB_v1" + sshKeys = ["${opc_compute_ssh_key.test_key.name}"] + attributes = "{\"foo\": \"bar\"}" + storage = { + index = 1 + volume = "${opc_compute_storage_volume.test_volume.name}" + } +} + +resource "opc_compute_storage_volume" "test_volume" { + size = "3g" + description = "My volume" + name = "test_volume_b" + tags = ["foo", "bar", "baz"] +} + +resource "opc_compute_ssh_key" "%s" { + name = "test-key" + key = "%s" + enabled = true +} +`, instanceName, keyName, updatedKey) diff --git a/builtin/providers/oracleopc/resource_ip_association.go b/builtin/providers/oracleopc/resource_ip_association.go new file mode 100644 index 000000000..84df10ba8 --- /dev/null +++ b/builtin/providers/oracleopc/resource_ip_association.go @@ -0,0 +1,103 @@ +package opc + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/oracle/terraform-provider-compute/sdk/compute" + "log" +) + +func resourceIPAssociation() *schema.Resource { + return &schema.Resource{ + Create: resourceIPAssociationCreate, + Read: resourceIPAssociationRead, + Delete: resourceIPAssociationDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "vcable": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "parentpool": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceIPAssociationCreate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + + vcable, parentpool := getIPAssociationResourceData(d) + + log.Printf("[DEBUG] Creating ip association between vcable %s and parent pool %s", + vcable, parentpool) + + client := meta.(*OPCClient).IPAssociations() + info, err := client.CreateIPAssociation(vcable, parentpool) + if err != nil { + return fmt.Errorf("Error creating ip association between vcable %s and parent pool %s: %s", + vcable, parentpool, err) + } + + d.SetId(info.Name) + updateIPAssociationResourceData(d, info) + return nil +} + +func updateIPAssociationResourceData(d *schema.ResourceData, info *compute.IPAssociationInfo) { + d.Set("name", info.Name) + d.Set("parentpool", info.ParentPool) + d.Set("vcable", info.VCable) +} + +func resourceIPAssociationRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + client := meta.(*OPCClient).IPAssociations() + name := d.Get("name").(string) + + log.Printf("[DEBUG] Reading state of ip association %s", name) + result, err := client.GetIPAssociation(name) + if err != nil { + // IP Association does not exist + if compute.WasNotFoundError(err) { + d.SetId("") + return nil + } + return fmt.Errorf("Error reading ip association %s: %s", name, err) + } + + log.Printf("[DEBUG] Read state of ip association %s: %#v", name, result) + updateIPAssociationResourceData(d, result) + return nil +} + +func getIPAssociationResourceData(d *schema.ResourceData) (string, string) { + return d.Get("vcable").(string), d.Get("parentpool").(string) +} + +func resourceIPAssociationDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + client := meta.(*OPCClient).IPAssociations() + name := d.Get("name").(string) + + vcable, parentpool := getIPAssociationResourceData(d) + log.Printf("[DEBUG] Deleting ip association %s between vcable %s and parent pool %s", + name, vcable, parentpool) + + if err := client.DeleteIPAssociation(name); err != nil { + return fmt.Errorf("Error deleting ip association %s between vcable %s and parent pool %s: %s", + name, vcable, parentpool, err) + } + return nil +} diff --git a/builtin/providers/oracleopc/resource_ip_association_test.go b/builtin/providers/oracleopc/resource_ip_association_test.go new file mode 100644 index 000000000..44f48474f --- /dev/null +++ b/builtin/providers/oracleopc/resource_ip_association_test.go @@ -0,0 +1,74 @@ +package opc + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/resource" + "testing" +) + +func TestAccOPCResourceIPAssociation_Basic(t *testing.T) { + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: opcResourceCheck( + ipAssociationResourceName, + testAccCheckIPAssociationDestroyed), + Steps: []resource.TestStep{ + { + Config: testAccIPAssociationBasic, + Check: resource.ComposeTestCheckFunc( + opcResourceCheck( + ipAssociationResourceName, + testAccCheckIPAssociationExists), + ), + }, + }, + }) +} + +func testAccCheckIPAssociationExists(state *OPCResourceState) error { + associationName := getIPAssociationName(state) + + if _, err := state.IPAssociations().GetIPAssociation(associationName); err != nil { + return fmt.Errorf("Error retrieving state of ip assocation %s: %s", associationName, err) + } + + return nil +} + +func getIPAssociationName(rs *OPCResourceState) string { + return rs.Attributes["name"] +} + +func testAccCheckIPAssociationDestroyed(state *OPCResourceState) error { + associationName := getAssociationName(state) + if info, err := state.IPAssociations().GetIPAssociation(associationName); err == nil { + return fmt.Errorf("IP association %s still exists: %#v", associationName, info) + } + + return nil +} + +const ipAssociationName = "test_ip_association" + +var ipAssociationResourceName = fmt.Sprintf("opc_compute_ip_association.%s", ipAssociationName) + +var testAccIPAssociationBasic = fmt.Sprintf(` +resource "opc_compute_ip_reservation" "reservation1" { + parentpool = "/oracle/public/ippool" + permanent = true +} + +resource "opc_compute_ip_association" "%s" { + vcable = "${opc_compute_instance.test-instance1.vcable}" + parentpool = "ipreservation:${opc_compute_ip_reservation.reservation1.name}" +} + +resource "opc_compute_instance" "test-instance1" { + name = "test" + label = "test" + shape = "oc3" + imageList = "/oracle/public/oel_6.4_2GB_v1" +} +`, ipAssociationName) diff --git a/builtin/providers/oracleopc/resource_ip_reservation.go b/builtin/providers/oracleopc/resource_ip_reservation.go new file mode 100644 index 000000000..fa25679d2 --- /dev/null +++ b/builtin/providers/oracleopc/resource_ip_reservation.go @@ -0,0 +1,122 @@ +package opc + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/oracle/terraform-provider-compute/sdk/compute" + "log" +) + +func resourceIPReservation() *schema.Resource { + return &schema.Resource{ + Create: resourceIPReservationCreate, + Read: resourceIPReservationRead, + Delete: resourceIPReservationDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "permanent": &schema.Schema{ + Type: schema.TypeBool, + Required: true, + ForceNew: true, + }, + + "parentpool": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "tags": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "ip": &schema.Schema{ + Type: schema.TypeString, + Optional: false, + Computed: true, + }, + }, + } +} + +func resourceIPReservationCreate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + + parentpool, permanent, tags := getIPReservationResourceData(d) + + log.Printf("[DEBUG] Creating ip reservation from parentpool %s with tags=%s", + parentpool, tags) + + client := meta.(*OPCClient).IPReservations() + info, err := client.CreateIPReservation(parentpool, permanent, tags) + if err != nil { + return fmt.Errorf("Error creating ip reservation from parentpool %s with tags=%s: %s", + parentpool, tags, err) + } + + d.SetId(info.Name) + updateIPReservationResourceData(d, info) + return nil +} + +func updateIPReservationResourceData(d *schema.ResourceData, info *compute.IPReservationInfo) { + d.Set("name", info.Name) + d.Set("parentpool", info.ParentPool) + d.Set("permanent", info.Permanent) + d.Set("tags", info.Tags) + d.Set("ip", info.IP) +} + +func resourceIPReservationRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + client := meta.(*OPCClient).IPReservations() + name := d.Get("name").(string) + + log.Printf("[DEBUG] Reading state of ip reservation %s", name) + result, err := client.GetIPReservation(name) + if err != nil { + // IP Reservation does not exist + if compute.WasNotFoundError(err) { + d.SetId("") + return nil + } + return fmt.Errorf("Error reading ip reservation %s: %s", name, err) + } + + log.Printf("[DEBUG] Read state of ip reservation %s: %#v", name, result) + updateIPReservationResourceData(d, result) + return nil +} + +func getIPReservationResourceData(d *schema.ResourceData) (string, bool, []string) { + tagdata := d.Get("tags").([]interface{}) + tags := make([]string, len(tagdata)) + for i, tag := range tagdata { + tags[i] = tag.(string) + } + return d.Get("parentpool").(string), + d.Get("permanent").(bool), + tags +} + +func resourceIPReservationDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + client := meta.(*OPCClient).IPReservations() + name := d.Get("name").(string) + + log.Printf("[DEBUG] Deleting ip reservation %s", name) + + if err := client.DeleteIPReservation(name); err != nil { + return fmt.Errorf("Error deleting ip reservation %s", name) + } + return nil +} diff --git a/builtin/providers/oracleopc/resource_security_application.go b/builtin/providers/oracleopc/resource_security_application.go new file mode 100644 index 000000000..b7205754c --- /dev/null +++ b/builtin/providers/oracleopc/resource_security_application.go @@ -0,0 +1,124 @@ +package opc + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/oracle/terraform-provider-compute/sdk/compute" + "log" +) + +func resourceSecurityApplication() *schema.Resource { + return &schema.Resource{ + Create: resourceSecurityApplicationCreate, + Read: resourceSecurityApplicationRead, + Delete: resourceSecurityApplicationDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "protocol": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "dport": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "icmptype": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "icmpcode": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceSecurityApplicationCreate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + + name, protocol, dport, icmptype, icmpcode, description := getSecurityApplicationResourceData(d) + + log.Printf("[DEBUG] Creating security application %s", name) + + client := meta.(*OPCClient).SecurityApplications() + info, err := client.CreateSecurityApplication(name, protocol, dport, icmptype, icmpcode, description) + if err != nil { + return fmt.Errorf("Error creating security application %s: %s", name, err) + } + + d.SetId(info.Name) + updateSecurityApplicationResourceData(d, info) + return nil +} + +func updateSecurityApplicationResourceData(d *schema.ResourceData, info *compute.SecurityApplicationInfo) { + d.Set("name", info.Name) + d.Set("protocol", info.Protocol) + d.Set("dport", info.DPort) + d.Set("icmptype", info.ICMPType) + d.Set("icmpcode", info.ICMPCode) + d.Set("description", info.Description) +} + +func resourceSecurityApplicationRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + client := meta.(*OPCClient).SecurityApplications() + name := d.Get("name").(string) + + log.Printf("[DEBUG] Reading state of security application %s", name) + result, err := client.GetSecurityApplication(name) + if err != nil { + // Security Application does not exist + if compute.WasNotFoundError(err) { + d.SetId("") + return nil + } + return fmt.Errorf("Error reading security application %s: %s", name, err) + } + + log.Printf("[DEBUG] Read state of security application %s: %#v", name, result) + updateSecurityApplicationResourceData(d, result) + return nil +} + +func getSecurityApplicationResourceData(d *schema.ResourceData) (string, string, string, string, string, string) { + return d.Get("name").(string), + d.Get("protocol").(string), + d.Get("dport").(string), + d.Get("icmptype").(string), + d.Get("icmpcode").(string), + d.Get("description").(string) +} + +func resourceSecurityApplicationDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + client := meta.(*OPCClient).SecurityApplications() + name := d.Get("name").(string) + + log.Printf("[DEBUG] Deleting security application %s", name) + + if err := client.DeleteSecurityApplication(name); err != nil { + return fmt.Errorf("Error deleting security application %s: %s", name, err) + } + return nil +} diff --git a/builtin/providers/oracleopc/resource_security_association.go b/builtin/providers/oracleopc/resource_security_association.go new file mode 100644 index 000000000..15a912657 --- /dev/null +++ b/builtin/providers/oracleopc/resource_security_association.go @@ -0,0 +1,103 @@ +package opc + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/oracle/terraform-provider-compute/sdk/compute" + "log" +) + +func resourceSecurityAssociation() *schema.Resource { + return &schema.Resource{ + Create: resourceSecurityAssociationCreate, + Read: resourceSecurityAssociationRead, + Delete: resourceSecurityAssociationDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "vcable": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "seclist": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceSecurityAssociationCreate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + + vcable, seclist := getSecurityAssociationResourceData(d) + + log.Printf("[DEBUG] Creating security association between vcable %s and security list %s", + vcable, seclist) + + client := meta.(*OPCClient).SecurityAssociations() + info, err := client.CreateSecurityAssociation(vcable, seclist) + if err != nil { + return fmt.Errorf("Error creating security association between vcable %s and security list %s: %s", + vcable, seclist, err) + } + + d.SetId(info.Name) + updateSecurityAssociationResourceData(d, info) + return nil +} + +func updateSecurityAssociationResourceData(d *schema.ResourceData, info *compute.SecurityAssociationInfo) { + d.Set("name", info.Name) + d.Set("seclist", info.SecList) + d.Set("vcable", info.VCable) +} + +func resourceSecurityAssociationRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + client := meta.(*OPCClient).SecurityAssociations() + name := d.Get("name").(string) + + log.Printf("[DEBUG] Reading state of security association %s", name) + result, err := client.GetSecurityAssociation(name) + if err != nil { + // Security Association does not exist + if compute.WasNotFoundError(err) { + d.SetId("") + return nil + } + return fmt.Errorf("Error reading security association %s: %s", name, err) + } + + log.Printf("[DEBUG] Read state of security association %s: %#v", name, result) + updateSecurityAssociationResourceData(d, result) + return nil +} + +func getSecurityAssociationResourceData(d *schema.ResourceData) (string, string) { + return d.Get("vcable").(string), d.Get("seclist").(string) +} + +func resourceSecurityAssociationDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + client := meta.(*OPCClient).SecurityAssociations() + name := d.Get("name").(string) + + vcable, seclist := getSecurityAssociationResourceData(d) + log.Printf("[DEBUG] Deleting security association %s between vcable %s and security list %s", + name, vcable, seclist) + + if err := client.DeleteSecurityAssociation(name); err != nil { + return fmt.Errorf("Error deleting security association %s between vcable %s and security list %s: %s", + name, vcable, seclist, err) + } + return nil +} diff --git a/builtin/providers/oracleopc/resource_security_association_test.go b/builtin/providers/oracleopc/resource_security_association_test.go new file mode 100644 index 000000000..604ef64cb --- /dev/null +++ b/builtin/providers/oracleopc/resource_security_association_test.go @@ -0,0 +1,75 @@ +package opc + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/resource" + "testing" +) + +func TestAccOPCResourceSecurityAssociation_Basic(t *testing.T) { + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: opcResourceCheck( + associationResourceName, + testAccCheckAssociationDestroyed), + Steps: []resource.TestStep{ + { + Config: testAccSecurityAssociationBasic, + Check: resource.ComposeTestCheckFunc( + opcResourceCheck( + associationResourceName, + testAccCheckAssociationExists), + ), + }, + }, + }) +} + +func testAccCheckAssociationExists(state *OPCResourceState) error { + associationName := getAssociationName(state) + + if _, err := state.SecurityAssociations().GetSecurityAssociation(associationName); err != nil { + return fmt.Errorf("Error retrieving state of security assocation %s: %s", associationName, err) + } + + return nil +} + +func getAssociationName(rs *OPCResourceState) string { + return rs.Attributes["name"] +} + +func testAccCheckAssociationDestroyed(state *OPCResourceState) error { + associationName := getAssociationName(state) + if info, err := state.SecurityAssociations().GetSecurityAssociation(associationName); err == nil { + return fmt.Errorf("Association %s still exists: %#v", associationName, info) + } + + return nil +} + +const associationName = "test_rule" + +var associationResourceName = fmt.Sprintf("opc_compute_security_association.%s", associationName) + +var testAccSecurityAssociationBasic = fmt.Sprintf(` +resource "opc_compute_security_list" "sec-list1" { + name = "sec-list-1" + policy = "PERMIT" + outbound_cidr_policy = "DENY" +} + +resource "opc_compute_security_association" "%s" { + vcable = "${opc_compute_instance.test-instance1.vcable}" + seclist = "${opc_compute_security_list.sec-list1.name}" +} + +resource "opc_compute_instance" "test-instance1" { + name = "test" + label = "test" + shape = "oc3" + imageList = "/oracle/public/oel_6.4_2GB_v1" +} +`, ruleName) diff --git a/builtin/providers/oracleopc/resource_security_ip_list.go b/builtin/providers/oracleopc/resource_security_ip_list.go new file mode 100644 index 000000000..6a3e66b28 --- /dev/null +++ b/builtin/providers/oracleopc/resource_security_ip_list.go @@ -0,0 +1,117 @@ +package opc + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/oracle/terraform-provider-compute/sdk/compute" + "log" +) + +func resourceSecurityIPList() *schema.Resource { + return &schema.Resource{ + Create: resourceSecurityIPListCreate, + Read: resourceSecurityIPListRead, + Update: resourceSecurityIPListUpdate, + Delete: resourceSecurityIPListDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "ip_entries": &schema.Schema{ + Type: schema.TypeList, + Required: true, + ForceNew: false, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func resourceSecurityIPListCreate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + + name, ipEntries := getSecurityIPListResourceData(d) + + log.Printf("[DEBUG] Creating security IP list with name %s, entries %s", + name, ipEntries) + + client := meta.(*OPCClient).SecurityIPLists() + info, err := client.CreateSecurityIPList(name, ipEntries) + if err != nil { + return fmt.Errorf("Error creating security IP list %s: %s", name, err) + } + + d.SetId(info.Name) + updateSecurityIPListResourceData(d, info) + return nil +} + +func updateSecurityIPListResourceData(d *schema.ResourceData, info *compute.SecurityIPListInfo) { + d.Set("name", info.Name) + d.Set("entries", info.SecIPEntries) +} + +func resourceSecurityIPListRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + client := meta.(*OPCClient).SecurityIPLists() + name := d.Get("name").(string) + + log.Printf("[DEBUG] Reading state of security IP list %s", name) + result, err := client.GetSecurityIPList(name) + if err != nil { + // Security IP List does not exist + if compute.WasNotFoundError(err) { + d.SetId("") + return nil + } + return fmt.Errorf("Error reading security IP list %s: %s", name, err) + } + + log.Printf("[DEBUG] Read state of security IP list %s: %#v", name, result) + updateSecurityIPListResourceData(d, result) + return nil +} + +func getSecurityIPListResourceData(d *schema.ResourceData) (string, []string) { + name := d.Get("name").(string) + ipEntries := d.Get("ip_entries").([]interface{}) + ipEntryStrings := []string{} + for _, entry := range ipEntries { + ipEntryStrings = append(ipEntryStrings, entry.(string)) + } + return name, ipEntryStrings +} + +func resourceSecurityIPListUpdate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + + client := meta.(*OPCClient).SecurityIPLists() + name, entries := getSecurityIPListResourceData(d) + + log.Printf("[DEBUG] Updating security IP list %s with ip entries %s", + name, entries) + + info, err := client.UpdateSecurityIPList(name, entries) + if err != nil { + return fmt.Errorf("Error updating security IP list %s: %s", name, err) + } + + updateSecurityIPListResourceData(d, info) + return nil +} + +func resourceSecurityIPListDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + client := meta.(*OPCClient).SecurityIPLists() + name := d.Get("name").(string) + + log.Printf("[DEBUG] Deleting security IP list %s", name) + if err := client.DeleteSecurityIPList(name); err != nil { + return fmt.Errorf("Error deleting security IP list %s: %s", name, err) + } + return nil +} diff --git a/builtin/providers/oracleopc/resource_security_list.go b/builtin/providers/oracleopc/resource_security_list.go new file mode 100644 index 000000000..eea11bbb1 --- /dev/null +++ b/builtin/providers/oracleopc/resource_security_list.go @@ -0,0 +1,119 @@ +package opc + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/oracle/terraform-provider-compute/sdk/compute" + "log" +) + +func resourceSecurityList() *schema.Resource { + return &schema.Resource{ + Create: resourceSecurityListCreate, + Read: resourceSecurityListRead, + Update: resourceSecurityListUpdate, + Delete: resourceSecurityListDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "policy": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + + "outbound_cidr_policy": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + }, + } +} + +func resourceSecurityListCreate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + + name, policy, outboundCIDRPolicy := getSecurityListResourceData(d) + + log.Printf("[DEBUG] Creating security list with name %s, policy %s, outbound CIDR policy %s", + name, policy, outboundCIDRPolicy) + + client := meta.(*OPCClient).SecurityLists() + info, err := client.CreateSecurityList(name, policy, outboundCIDRPolicy) + if err != nil { + return fmt.Errorf("Error creating security list %s: %s", name, err) + } + + d.SetId(info.Name) + updateSecurityListResourceData(d, info) + return nil +} + +func updateSecurityListResourceData(d *schema.ResourceData, info *compute.SecurityListInfo) { + d.Set("name", info.Name) + d.Set("policy", info.Policy) + d.Set("outbound_cidr_policy", info.OutboundCIDRPolicy) +} + +func resourceSecurityListRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + client := meta.(*OPCClient).SecurityLists() + name := d.Get("name").(string) + + log.Printf("[DEBUG] Reading state of security list %s", name) + result, err := client.GetSecurityList(name) + if err != nil { + // Security List does not exist + if compute.WasNotFoundError(err) { + d.SetId("") + return nil + } + return fmt.Errorf("Error reading security list %s: %s", name, err) + } + + log.Printf("[DEBUG] Read state of ssh key %s: %#v", name, result) + updateSecurityListResourceData(d, result) + return nil +} + +func getSecurityListResourceData(d *schema.ResourceData) (string, string, string) { + return d.Get("name").(string), + d.Get("policy").(string), + d.Get("outbound_cidr_policy").(string) +} + +func resourceSecurityListUpdate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + + client := meta.(*OPCClient).SecurityLists() + name, policy, outboundCIDRPolicy := getSecurityListResourceData(d) + + log.Printf("[DEBUG] Updating security list %s with policy %s, outbound_cidr_policy %s", + name, policy, outboundCIDRPolicy) + + info, err := client.UpdateSecurityList(name, policy, outboundCIDRPolicy) + if err != nil { + return fmt.Errorf("Error updating security list %s: %s", name, err) + } + + updateSecurityListResourceData(d, info) + return nil +} + +func resourceSecurityListDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + client := meta.(*OPCClient).SecurityLists() + name := d.Get("name").(string) + + log.Printf("[DEBUG] Deleting ssh key volume %s", name) + if err := client.DeleteSecurityList(name); err != nil { + return fmt.Errorf("Error deleting security list %s: %s", name, err) + } + return nil +} diff --git a/builtin/providers/oracleopc/resource_security_rule.go b/builtin/providers/oracleopc/resource_security_rule.go new file mode 100644 index 000000000..0d9eb562c --- /dev/null +++ b/builtin/providers/oracleopc/resource_security_rule.go @@ -0,0 +1,143 @@ +package opc + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/oracle/terraform-provider-compute/sdk/compute" + "log" +) + +func resourceSecurityRule() *schema.Resource { + return &schema.Resource{ + Create: resourceSecurityRuleCreate, + Read: resourceSecurityRuleRead, + Update: resourceSecurityRuleUpdate, + Delete: resourceSecurityRuleDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "source_list": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "destination_list": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "application": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "action": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + + "disabled": &schema.Schema{ + Type: schema.TypeBool, + Required: true, + ForceNew: false, + }, + }, + } +} + +func resourceSecurityRuleCreate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + + name, sourceList, destinationList, application, action, disabled := getSecurityRuleResourceData(d) + + log.Printf("[DEBUG] Creating security list with name %s, sourceList %s, destinationList %s, application %s, action %s, disabled %s", + name, sourceList, destinationList, application, action, disabled) + + client := meta.(*OPCClient).SecurityRules() + info, err := client.CreateSecurityRule(name, sourceList, destinationList, application, action, disabled) + if err != nil { + return fmt.Errorf("Error creating security rule %s: %s", name, err) + } + + d.SetId(info.Name) + updateSecurityRuleResourceData(d, info) + return nil +} + +func updateSecurityRuleResourceData(d *schema.ResourceData, info *compute.SecurityRuleInfo) { + d.Set("name", info.Name) + d.Set("source_list", info.SourceList) + d.Set("destination_list", info.DestinationList) + d.Set("application", info.Application) + d.Set("action", info.Action) + d.Set("disabled", info.Disabled) +} + +func resourceSecurityRuleRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + client := meta.(*OPCClient).SecurityRules() + name := d.Get("name").(string) + + log.Printf("[DEBUG] Reading state of security rule %s", name) + result, err := client.GetSecurityRule(name) + if err != nil { + // Security Rule does not exist + if compute.WasNotFoundError(err) { + d.SetId("") + return nil + } + return fmt.Errorf("Error reading security list %s: %s", name, err) + } + + log.Printf("[DEBUG] Read state of ssh key %s: %#v", name, result) + updateSecurityRuleResourceData(d, result) + return nil +} + +func getSecurityRuleResourceData(d *schema.ResourceData) (string, string, string, string, string, bool) { + return d.Get("name").(string), + d.Get("source_list").(string), + d.Get("destination_list").(string), + d.Get("application").(string), + d.Get("action").(string), + d.Get("disabled").(bool) +} + +func resourceSecurityRuleUpdate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + + client := meta.(*OPCClient).SecurityRules() + name, sourceList, destinationList, application, action, disabled := getSecurityRuleResourceData(d) + + log.Printf("[DEBUG] Updating security list %s with sourceList %s, destinationList %s, application %s, action %s, disabled %s", + name, sourceList, destinationList, application, action, disabled) + + info, err := client.UpdateSecurityRule(name, sourceList, destinationList, application, action, disabled) + if err != nil { + return fmt.Errorf("Error updating security rule %s: %s", name, err) + } + + updateSecurityRuleResourceData(d, info) + return nil +} + +func resourceSecurityRuleDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource state: %#v", d.State()) + client := meta.(*OPCClient).SecurityRules() + name := d.Get("name").(string) + + log.Printf("[DEBUG] Deleting ssh key volume %s", name) + if err := client.DeleteSecurityRule(name); err != nil { + return fmt.Errorf("Error deleting security rule %s: %s", name, err) + } + return nil +} diff --git a/builtin/providers/oracleopc/resource_security_rule_test.go b/builtin/providers/oracleopc/resource_security_rule_test.go new file mode 100644 index 000000000..f09c2b879 --- /dev/null +++ b/builtin/providers/oracleopc/resource_security_rule_test.go @@ -0,0 +1,85 @@ +package opc + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/resource" + "testing" +) + +func TestAccOPCResourceSecurityRule_Basic(t *testing.T) { + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: opcResourceCheck( + ruleResourceName, + testAccCheckRuleDestroyed), + Steps: []resource.TestStep{ + { + Config: testAccSecurityRuleBasic, + Check: resource.ComposeTestCheckFunc( + opcResourceCheck( + ruleResourceName, + testAccCheckRuleExists), + ), + }, + }, + }) +} + +func testAccCheckRuleExists(state *OPCResourceState) error { + ruleName := getRuleName(state) + + if _, err := state.SecurityRules().GetSecurityRule(ruleName); err != nil { + return fmt.Errorf("Error retrieving state of security rule %s: %s", ruleName, err) + } + + return nil +} + +func getRuleName(rs *OPCResourceState) string { + return rs.Attributes["name"] +} + +func testAccCheckRuleDestroyed(state *OPCResourceState) error { + ruleName := getRuleName(state) + if info, err := state.SecurityRules().GetSecurityRule(ruleName); err == nil { + return fmt.Errorf("Rule %s still exists: %#v", ruleName, info) + } + + return nil +} + +const ruleName = "test_rule" +const secListName = "sec-list1" +const secIpListName = "sec-ip-list1" + +var ruleResourceName = fmt.Sprintf("opc_compute_security_rule.%s", ruleName) + +var testAccSecurityRuleBasic = fmt.Sprintf(` +resource "opc_compute_security_rule" "%s" { + name = "test" + source_list = "seclist:${opc_compute_security_list.sec-list1.name}" + destination_list = "seciplist:${opc_compute_security_ip_list.sec-ip-list1.name}" + action = "PERMIT" + application = "${opc_compute_security_application.spring-boot.name}" + disabled = false +} + +resource "opc_compute_security_list" "%s" { + name = "sec-list-1" + policy = "PERMIT" + outbound_cidr_policy = "DENY" +} + +resource "opc_compute_security_application" "spring-boot" { + name = "spring-boot" + protocol = "tcp" + dport = "8080" +} + +resource "opc_compute_security_ip_list" "%s" { + name = "sec-ip-list1" + ip_entries = ["217.138.34.4"] +} +`, ruleName, secListName, secIpListName) diff --git a/builtin/providers/oracleopc/resource_ssh_key.go b/builtin/providers/oracleopc/resource_ssh_key.go new file mode 100644 index 000000000..29f68b4aa --- /dev/null +++ b/builtin/providers/oracleopc/resource_ssh_key.go @@ -0,0 +1,117 @@ +package opc + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/oracle/terraform-provider-compute/sdk/compute" + "log" +) + +func resourceSSHKey() *schema.Resource { + return &schema.Resource{ + Create: resourceSSHKeyCreate, + Read: resourceSSHKeyRead, + Update: resourceSSHKeyUpdate, + Delete: resourceSSHKeyDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "key": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + + "enabled": &schema.Schema{ + Type: schema.TypeBool, + Required: true, + ForceNew: false, + }, + }, + } +} + +func resourceSSHKeyCreate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource data: %#v", d) + + client := meta.(*OPCClient).SSHKeys() + name := d.Get("name").(string) + key := d.Get("key").(string) + enabled := d.Get("enabled").(bool) + + log.Printf("[DEBUG] Creating ssh key with name %s, key %s, enabled %s", + name, key, enabled) + + info, err := client.CreateSSHKey(name, key, enabled) + if err != nil { + return fmt.Errorf("Error creating ssh key %s: %s", name, err) + } + + d.SetId(info.Name) + updateSSHKeyResourceData(d, info) + return nil +} + +func updateSSHKeyResourceData(d *schema.ResourceData, info *compute.SSHKeyInfo) { + d.Set("name", info.Name) + d.Set("key", info.Key) + d.Set("enabled", info.Enabled) +} + +func resourceSSHKeyRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource data: %#v", d) + client := meta.(*OPCClient).SSHKeys() + name := d.Get("name").(string) + + log.Printf("[DEBUG] Reading state of ssh key %s", name) + result, err := client.GetSSHKey(name) + if err != nil { + // SSH Key does not exist + if compute.WasNotFoundError(err) { + d.SetId("") + return nil + } + return fmt.Errorf("Error reading ssh key %s: %s", name, err) + } + + log.Printf("[DEBUG] Read state of ssh key %s: %#v", name, result) + updateSSHKeyResourceData(d, result) + return nil +} + +func resourceSSHKeyUpdate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource data: %#v", d) + + client := meta.(*OPCClient).SSHKeys() + name := d.Get("name").(string) + key := d.Get("key").(string) + enabled := d.Get("enabled").(bool) + + log.Printf("[DEBUG] Updating ssh key with name %s, key %s, enabled %s", + name, key, enabled) + + info, err := client.UpdateSSHKey(name, key, enabled) + if err != nil { + return fmt.Errorf("Error updating ssh key %s: %s", name, err) + } + + updateSSHKeyResourceData(d, info) + return nil +} + +func resourceSSHKeyDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource data: %#v", d) + client := meta.(*OPCClient).SSHKeys() + name := d.Get("name").(string) + + log.Printf("[DEBUG] Deleting ssh key volume %s", name) + if err := client.DeleteSSHKey(name); err != nil { + return fmt.Errorf("Error deleting ssh key %s: %s", name, err) + } + return nil +} diff --git a/builtin/providers/oracleopc/resource_storage_volume.go b/builtin/providers/oracleopc/resource_storage_volume.go new file mode 100644 index 000000000..2d80d09f2 --- /dev/null +++ b/builtin/providers/oracleopc/resource_storage_volume.go @@ -0,0 +1,301 @@ +package opc + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/oracle/terraform-provider-compute/sdk/compute" + "log" +) + +func resourceStorageVolume() *schema.Resource { + return &schema.Resource{ + Create: resourceStorageVolumeCreate, + Read: resourceStorageVolumeRead, + Update: resourceStorageVolumeUpdate, + Delete: resourceStorageVolumeDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "size": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "sizeInBytes": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "storage": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "/oracle/public/storage/default", + }, + + "tags": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: false, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "bootableImage": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "bootableImageVersion": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + Default: -1, + }, + + "snapshot": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "account": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + }, + }, + + "snapshotId": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceStorageVolumeCreate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Resource data: %#v", d) + + sv := meta.(*OPCClient).StorageVolumes() + name := d.Get("name").(string) + properties := []string{d.Get("storage").(string)} + + spec := sv.NewStorageVolumeSpec( + d.Get("size").(string), + properties, + name) + + if d.Get("description").(string) != "" { + spec.SetDescription(d.Get("description").(string)) + } + + spec.SetTags(getTags(d)) + + if d.Get("bootableImage") != "" { + spec.SetBootableImage(d.Get("bootableImage").(string), d.Get("bootableImageVersion").(int)) + } + + if len(d.Get("snapshot").(*schema.Set).List()) > 0 { + snapshotDetails := d.Get("snapshot").(*schema.Set).List()[0].(map[string]interface{}) + spec.SetSnapshot( + snapshotDetails["name"].(string), + snapshotDetails["account"].(string), + ) + } + + if d.Get("snapshotId") != "" { + spec.SetSnapshotID(d.Get("snapshotId").(string)) + } + + log.Printf("[DEBUG] Creating storage volume %s with spec %#v", name, spec) + err := sv.CreateStorageVolume(spec) + if err != nil { + return fmt.Errorf("Error creating storage volume %s: %s", name, err) + } + + log.Printf("[DEBUG] Waiting for storage volume %s to come online", name) + info, err := sv.WaitForStorageVolumeOnline(name, meta.(*OPCClient).MaxRetryTimeout) + if err != nil { + return fmt.Errorf("Error waiting for storage volume %s to come online: %s", name, err) + } + + log.Printf("[DEBUG] Created storage volume %s: %#v", name, info) + + cachedAttachments, attachmentsFound := meta.(*OPCClient).storageAttachmentsByVolumeCache[name] + if attachmentsFound { + log.Printf("[DEBUG] Rebuilding storage attachments for volume %s", name) + for _, cachedAttachment := range cachedAttachments { + log.Printf("[DEBUG] Rebuilding storage attachments between volume %s and instance %s", + name, + cachedAttachment.instanceName) + + attachmentInfo, err := meta.(*OPCClient).StorageAttachments().CreateStorageAttachment( + cachedAttachment.index, + cachedAttachment.instanceName, + name, + ) + + if err != nil { + return fmt.Errorf( + "Error recreating storage attachment between volume %s and instance %s: %s", + name, + *cachedAttachment.instanceName, + err) + } + err = meta.(*OPCClient).StorageAttachments().WaitForStorageAttachmentCreated( + attachmentInfo.Name, + meta.(*OPCClient).MaxRetryTimeout) + if err != nil { + return fmt.Errorf( + "Error recreating storage attachment between volume %s and instance %s: %s", + name, + *cachedAttachment.instanceName, + err) + } + } + meta.(*OPCClient).storageAttachmentsByVolumeCache[name] = nil + } + + d.SetId(name) + updateResourceData(d, info) + return nil +} + +func getTags(d *schema.ResourceData) []string { + tags := []string{} + for _, i := range d.Get("tags").([]interface{}) { + tags = append(tags, i.(string)) + } + return tags +} + +func updateResourceData(d *schema.ResourceData, info *compute.StorageVolumeInfo) error { + d.Set("name", info.Name) + d.Set("description", info.Description) + d.Set("storage", info.Properties[0]) + d.Set("sizeInBytes", info.Size) + d.Set("tags", info.Tags) + d.Set("bootableImage", info.ImageList) + d.Set("bootableImageVersion", info.ImageListEntry) + if info.Snapshot != "" { + d.Set("snapshot", map[string]interface{}{ + "name": info.Snapshot, + "account": info.SnapshotAccount, + }) + } + d.Set("snapshotId", info.SnapshotID) + + return nil +} + +func resourceStorageVolumeRead(d *schema.ResourceData, meta interface{}) error { + sv := meta.(*OPCClient).StorageVolumes() + name := d.Get("name").(string) + + log.Printf("[DEBUG] Reading state of storage volume %s", name) + result, err := sv.GetStorageVolume(name) + if err != nil { + // Volume doesn't exist + if compute.WasNotFoundError(err) { + d.SetId("") + return nil + } + return fmt.Errorf("Error reading storage volume %s: %s", name, err) + } + + if len(result.Result) == 0 { + // Volume doesn't exist + d.SetId("") + return nil + } + + log.Printf("[DEBUG] Read state of storage volume %s: %#v", name, &result.Result[0]) + updateResourceData(d, &result.Result[0]) + + return nil +} + +func resourceStorageVolumeUpdate(d *schema.ResourceData, meta interface{}) error { + sv := meta.(*OPCClient).StorageVolumes() + name := d.Get("name").(string) + description := d.Get("description").(string) + size := d.Get("size").(string) + tags := getTags(d) + + log.Printf("[DEBUG] Updating storage volume %s with size %s, description %s, tags %#v", name, size, description, tags) + err := sv.UpdateStorageVolume(name, size, description, tags) + + if err != nil { + return fmt.Errorf("Error updating storage volume %s: %s", name, err) + } + + log.Printf("[DEBUG] Waiting for updated storage volume %s to come online", name) + info, err := sv.WaitForStorageVolumeOnline(name, meta.(*OPCClient).MaxRetryTimeout) + if err != nil { + return fmt.Errorf("Error waiting for updated storage volume %s to come online: %s", name, err) + } + + log.Printf("[DEBUG] Updated storage volume %s: %#v", name, info) + updateResourceData(d, info) + return nil +} + +func resourceStorageVolumeDelete(d *schema.ResourceData, meta interface{}) error { + sv := meta.(*OPCClient).StorageVolumes() + name := d.Get("name").(string) + + sva := meta.(*OPCClient).StorageAttachments() + attachments, err := sva.GetStorageAttachmentsForVolume(name) + if err != nil { + return fmt.Errorf("Error retrieving storage attachments for volume %s: %s", name, err) + } + + attachmentsToCache := make([]storageAttachment, len(*attachments)) + for index, attachment := range *attachments { + log.Printf("[DEBUG] Deleting storage attachment %s for volume %s", attachment.Name, name) + sva.DeleteStorageAttachment(attachment.Name) + sva.WaitForStorageAttachmentDeleted(attachment.Name, meta.(*OPCClient).MaxRetryTimeout) + attachmentsToCache[index] = storageAttachment{ + index: attachment.Index, + instanceName: compute.InstanceNameFromString(attachment.InstanceName), + } + } + meta.(*OPCClient).storageAttachmentsByVolumeCache[name] = attachmentsToCache + + log.Printf("[DEBUG] Deleting storage volume %s", name) + err = sv.DeleteStorageVolume(name) + if err != nil { + return fmt.Errorf("Error deleting storage volume %s: %s", name, err) + } + + log.Printf("[DEBUG] Waiting for storage volume %s to finish deleting", name) + err = sv.WaitForStorageVolumeDeleted(name, meta.(*OPCClient).MaxRetryTimeout) + if err != nil { + return fmt.Errorf("Error waiting for storage volume %s to finish deleting: %s", name, err) + } + + log.Printf("[DEBUG] Deleted storage volume %s", name) + return nil +} diff --git a/builtin/providers/oracleopc/resource_storage_volume_test.go b/builtin/providers/oracleopc/resource_storage_volume_test.go new file mode 100644 index 000000000..d168b5309 --- /dev/null +++ b/builtin/providers/oracleopc/resource_storage_volume_test.go @@ -0,0 +1,70 @@ +package opc + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/resource" + "testing" +) + +func TestAccOPCStorageVolume_Basic(t *testing.T) { + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: opcResourceCheck( + "opc_compute_storage_volume.test_volume", + testAccCheckStorageVolumeDestroyed), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccStorageVolumeBasic, + Check: resource.ComposeTestCheckFunc( + opcResourceCheck( + "opc_compute_storage_volume.test_volume", + testAccCheckStorageVolumeExists), + ), + }, + }, + }) +} + +func testAccCheckStorageVolumeExists(state *OPCResourceState) error { + sv := state.StorageVolumes() + volumeName := state.Attributes["name"] + + info, err := sv.GetStorageVolume(volumeName) + if err != nil { + return fmt.Errorf("Error retrieving state of volume %s: %s", volumeName, err) + } + + if len(info.Result) == 0 { + return fmt.Errorf("No info found for volume %s", volumeName) + } + + return nil +} + +func testAccCheckStorageVolumeDestroyed(state *OPCResourceState) error { + sv := state.StorageVolumes() + + volumeName := state.Attributes["name"] + + info, err := sv.GetStorageVolume(volumeName) + if err != nil { + return fmt.Errorf("Error retrieving state of volume %s: %s", volumeName, err) + } + + if len(info.Result) != 0 { + return fmt.Errorf("Volume %s still exists", volumeName) + } + + return nil +} + +const testAccStorageVolumeBasic = ` +resource "opc_compute_storage_volume" "test_volume" { + size = "3g" + description = "My volume" + name = "test_volume_b" + tags = ["foo", "bar", "baz"] +} +`