From ef144a43304ed0b85ce4dfd08bbeb7d62d9c5d1f Mon Sep 17 00:00:00 2001 From: Ryan Belgrave Date: Thu, 18 Jan 2018 21:57:02 -0600 Subject: [PATCH] add volumes --- glide.lock | 17 +- glide.yaml | 2 +- sandwich/provider.go | 1 + sandwich/resource_sandwich_volume.go | 304 +++++++++++++++++++++++++++ 4 files changed, 316 insertions(+), 8 deletions(-) create mode 100644 sandwich/resource_sandwich_volume.go diff --git a/glide.lock b/glide.lock index 885c83b..8519351 100644 --- a/glide.lock +++ b/glide.lock @@ -1,12 +1,12 @@ -hash: 14cdf6cd86c0c0083604f0ff515874f83776c4a018b21cc4e5234c424444e5dd -updated: 2018-01-15T22:45:42.9260039-06:00 +hash: 733053d2b40acb61f3329ff855cdc7c83bb151c8ab054346204ae13af42adf86 +updated: 2018-01-18T19:46:09.6304202-06:00 imports: - name: github.com/apparentlymart/go-cidr version: 2bd8b58cf4275aeb086ade613de226773e29e853 subpackages: - cidr - name: github.com/aws/aws-sdk-go - version: 2fe57096de348e6cff4031af99254613f8ef73ea + version: 42f4ce615286af6026c8aac43a7af02457358ef6 subpackages: - aws - aws/awserr @@ -66,7 +66,7 @@ imports: - name: github.com/hashicorp/go-multierror version: b7773ae218740a7be65057fc60b366a49b538a44 - name: github.com/hashicorp/go-plugin - version: e37881a3f1a07fce82b3d99ce0342a72e53386bc + version: 1fc09c47b843b73705f51ffb0520e3ac1bfecf99 - name: github.com/hashicorp/go-uuid version: 64130c7a86d732268a38cb04cfbaf0cc987fda98 - name: github.com/hashicorp/go-version @@ -127,8 +127,10 @@ imports: version: b4575eea38cca1123ec2dc90c26529b5c5acfcff - name: github.com/mitchellh/reflectwalk version: 63d60e9d0dbc60cf9164e6510889b0db6683d98c +- name: github.com/oklog/run + version: 4dadeb3030eda0273a12382bb2348ffc7c9d1a39 - name: github.com/sandwichcloud/deli-cli - version: 851787e73850ccae3d209fcbf9d8bbc303516f36 + version: 53ffd4c7271cccee277acb402d26b1d0ea2ff4e6 subpackages: - api - api/client @@ -144,6 +146,7 @@ imports: - api/client/region - api/client/role - api/client/serviceAccount + - api/client/volume - api/client/zone - metadata - utils @@ -184,7 +187,7 @@ imports: - lex/httplex - trace - name: golang.org/x/oauth2 - version: 30785a2c434e431ef7c507b54617d6a951d5f2b4 + version: b28fcf2b08a19742b43084fb40ab78ac6c3d8067 subpackages: - internal - name: golang.org/x/sys @@ -214,7 +217,7 @@ imports: subpackages: - googleapis/rpc/status - name: google.golang.org/grpc - version: 1cd234627e6f392ade0527d593eb3fe53e832d4a + version: 46bef23bc38dccc7830e089fc5840c95c4bf24ab subpackages: - balancer - balancer/base diff --git a/glide.yaml b/glide.yaml index d57e397..fa27aee 100644 --- a/glide.yaml +++ b/glide.yaml @@ -3,4 +3,4 @@ import: - package: github.com/hashicorp/terraform version: 0.10.0 - package: github.com/sandwichcloud/deli-cli - version: 0.0.16 + version: 0.0.17 diff --git a/sandwich/provider.go b/sandwich/provider.go index 14519e3..6db5523 100644 --- a/sandwich/provider.go +++ b/sandwich/provider.go @@ -45,6 +45,7 @@ func Provider() *schema.Provider { "sandwich_keypair": resourceKeypair(), "sandwich_flavor": resourceFlavor(), "sandwich_instance": resourceInstance(), + "sandwich_volume": resourceVolume(), }, ConfigureFunc: configureProvider, } diff --git a/sandwich/resource_sandwich_volume.go b/sandwich/resource_sandwich_volume.go new file mode 100644 index 0000000..9c80df8 --- /dev/null +++ b/sandwich/resource_sandwich_volume.go @@ -0,0 +1,304 @@ +package sandwich + +import ( + "fmt" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/sandwichcloud/deli-cli/api" + "github.com/sandwichcloud/deli-cli/api/client" + "github.com/satori/go.uuid" +) + +func resourceVolume() *schema.Resource { + return &schema.Resource{ + Create: resourceVolumeCreate, + Read: resourceVolumeRead, + Update: resourceVolumeUpdate, + Delete: resourceVolumeDelete, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Read: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "zone_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "size": { + Type: schema.TypeInt, + Required: true, + ForceNew: false, + }, + "cloned_from": { + Type: schema.TypeString, + Required: false, + Optional: true, + ForceNew: false, + }, + "attached_to": { + Type: schema.TypeString, + Required: false, + Optional: true, + ForceNew: false, + Default: uuid.UUID{}.String(), + }, + }, + } +} + +func resourceVolumeCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + volumeClient := config.SandwichClient.Volume() + name := d.Get("name").(string) + zoneID := d.Get("zone_id").(string) + size := d.Get("size").(int) + clonedFrom := d.Get("cloned_from").(string) + + if len(clonedFrom) == 0 { + volume, err := volumeClient.Create(name, zoneID, size) + if err != nil { + return err + } + d.Partial(true) + d.SetId(volume.ID.String()) + stateConf := &resource.StateChangeConf{ + Pending: []string{"ToCreate", "Creating"}, + Target: []string{"Created"}, + Refresh: VolumeStateRefreshFunc(volumeClient, volume.ID.String()), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for volume (%s) to become ready: %s", volume.ID.String(), err) + } + d.Partial(false) + } else { + volume, err := volumeClient.Get(clonedFrom) + if err != nil { + return err + } + volume, err = volumeClient.ActionClone(volume.ID.String(), name) + if err != nil { + return err + } + d.Partial(true) + d.SetId(volume.ID.String()) + stateConf := &resource.StateChangeConf{ + Pending: []string{"ToCreate", "Creating"}, + Target: []string{"Created"}, + Refresh: VolumeStateRefreshFunc(volumeClient, volume.ID.String()), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for volume (%s) to become ready: %s", volume.ID.String(), err) + } + d.Partial(false) + } + + return resourceVolumeUpdate(d, meta) +} + +func resourceVolumeRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + volumeClient := config.SandwichClient.Volume() + + volume, err := volumeClient.Get(d.Id()) + if err != nil { + if apiError, ok := err.(api.APIErrorInterface); ok { + if apiError.IsNotFound() { + d.SetId("") + return nil + } + } + return err + } + + d.Set("name", volume.Name) + d.Set("zone_id", volume.ZoneID) + d.Set("size", volume.Size) + d.Set("attached_to", volume.AttachedTo) + + return nil +} + +func resourceVolumeUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + volumeClient := config.SandwichClient.Volume() + + size := d.Get("size").(int) + attachedToStr := d.Get("attached_to").(string) + attachedTo, err := uuid.FromString(attachedToStr) + if err != nil { + return err + } + + volume, err := volumeClient.Get(d.Id()) + if err != nil { + if apiError, ok := err.(api.APIErrorInterface); ok { + if apiError.IsNotFound() { + d.SetId("") + return nil + } + } + return err + } + + if volume.AttachedTo != attachedTo { + err := volumeClient.ActionDetach(volume.ID.String()) + if err != nil { + if apiError, ok := err.(api.APIError); ok { + if apiError.StatusCode != 409 { + return err + } + } + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"DETACHING"}, + Target: []string{""}, + Refresh: VolumeTaskRefreshFunc(volumeClient, volume.ID.String()), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for volume (%s) to detach: %s", volume.ID.String(), err) + } + } + + if volume.Size != size { + err := volumeClient.ActionGrow(d.Id(), size) + if err != nil { + return err + } + stateConf := &resource.StateChangeConf{ + Pending: []string{"GROWING"}, + Target: []string{""}, + Refresh: VolumeTaskRefreshFunc(volumeClient, volume.ID.String()), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for volume (%s) to grow: %s", volume.ID.String(), err) + } + } + + if attachedTo != (uuid.UUID{}) { + err := volumeClient.ActionAttach(volume.ID.String(), attachedTo.String()) + if err != nil { + return err + } + stateConf := &resource.StateChangeConf{ + Pending: []string{"ATTACHING"}, + Target: []string{""}, + Refresh: VolumeTaskRefreshFunc(volumeClient, volume.ID.String()), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for volume (%s) to attach: %s", volume.ID.String(), err) + } + } + + return resourceVolumeRead(d, meta) +} + +func resourceVolumeDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + volumeClient := config.SandwichClient.Volume() + + err := volumeClient.ActionDetach(d.Id()) + if err != nil { + if apiError, ok := err.(api.APIError); ok { + if apiError.StatusCode != 409 { + return err + } + } + } + stateConf := &resource.StateChangeConf{ + Pending: []string{"DETACHING"}, + Target: []string{""}, + Refresh: VolumeTaskRefreshFunc(volumeClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for volume (%s) to detach: %s", d.Id(), err) + } + + err = volumeClient.Delete(d.Id()) + if err != nil { + return err + } + + stateConf = &resource.StateChangeConf{ + Pending: []string{"ToDelete", "Deleting"}, + Target: []string{"Deleted"}, + Refresh: VolumeStateRefreshFunc(volumeClient, d.Id()), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for volume (%s) to delete: %s", d.Id(), err) + } + + d.SetId("") + + return nil +} + +func VolumeStateRefreshFunc(volumeClient client.VolumeClientInterface, volumeID string) func() (result interface{}, state string, err error) { + return func() (result interface{}, state string, err error) { + volume, err := volumeClient.Get(volumeID) + if err != nil { + if apiError, ok := err.(api.APIErrorInterface); ok { + if apiError.IsNotFound() { + return volume, "Deleted", nil + } + } + return nil, "", err + } + return volume, volume.State, nil + } +} + +func VolumeTaskRefreshFunc(volumeClient client.VolumeClientInterface, volumeID string) func() (result interface{}, state string, err error) { + return func() (result interface{}, state string, err error) { + volume, err := volumeClient.Get(volumeID) + if err != nil { + if apiError, ok := err.(api.APIErrorInterface); ok { + if apiError.IsNotFound() { + return volume, "Deleted", nil + } + } + return nil, "", err + } + return volume, volume.Task, nil + } +}