Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manila: add share acl resource #526

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package openstack

import (
"fmt"
"testing"

"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccSFSV2ShareAccess_importBasic(t *testing.T) {
shareName := "openstack_sharedfilesystem_share_v2.share_1"
shareAccessName := "openstack_sharedfilesystem_share_access_v2.share_access_1"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheckSFS(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckSFSV2ShareAccessDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccSFSV2ShareAccessConfig_basic,
},

resource.TestStep{
ResourceName: shareAccessName,
ImportState: true,
ImportStateVerify: true,
ImportStateIdFunc: testAccSFSV2ShareAccessImportID(shareName, shareAccessName),
},
},
})
}

func testAccSFSV2ShareAccessImportID(shareResource, shareAccessResource string) resource.ImportStateIdFunc {
return func(s *terraform.State) (string, error) {
share, ok := s.RootModule().Resources[shareResource]
if !ok {
return "", fmt.Errorf("Share not found: %s", shareResource)
}

shareAccess, ok := s.RootModule().Resources[shareAccessResource]
if !ok {
return "", fmt.Errorf("Share access not found: %s", shareAccessResource)
}

return fmt.Sprintf("%s/%s", share.Primary.ID, shareAccess.Primary.ID), nil
}
}
28 changes: 28 additions & 0 deletions openstack/import_openstack_sharedfilesystem_share_v2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package openstack

import (
"testing"

"github.com/hashicorp/terraform/helper/resource"
)

func TestAccSFSV2Share_importBasic(t *testing.T) {
resourceName := "openstack_sharedfilesystem_share_v2.share_1"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheckSFS(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckSFSV2ShareDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccSFSV2ShareConfig_basic,
},

resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}
2 changes: 2 additions & 0 deletions openstack/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ func Provider() terraform.ResourceProvider {
"openstack_vpnaas_site_connection_v2": resourceSiteConnectionV2(),
"openstack_sharedfilesystem_securityservice_v2": resourceSharedFilesystemSecurityServiceV2(),
"openstack_sharedfilesystem_sharenetwork_v2": resourceSharedFilesystemShareNetworkV2(),
"openstack_sharedfilesystem_share_v2": resourceSharedFilesystemShareV2(),
"openstack_sharedfilesystem_share_access_v2": resourceSharedFilesystemShareAccessV2(),
},

ConfigureFunc: configureProvider,
Expand Down
282 changes: 282 additions & 0 deletions openstack/resource_openstack_sharedfilesystem_share_access_v2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
package openstack

import (
"fmt"
"log"
"strings"
"time"

"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"

"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/errors"
"github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares"
)

func resourceSharedFilesystemShareAccessV2() *schema.Resource {
return &schema.Resource{
Create: resourceSharedFilesystemShareAccessV2Create,
Read: resourceSharedFilesystemShareAccessV2Read,
Delete: resourceSharedFilesystemShareAccessV2Delete,
Importer: &schema.ResourceImporter{
resourceSharedFilesystemShareAccessV2Import,
},

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(10 * time.Minute),
Update: schema.DefaultTimeout(10 * time.Minute),
Delete: schema.DefaultTimeout(10 * time.Minute),
},

Schema: map[string]*schema.Schema{
"share_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"access_type": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{
"ip", "user", "cert",
}, true),
},

"access_to": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"access_level": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{
"rw", "ro",
}, true),
},
},
}
}

func resourceSharedFilesystemShareAccessV2Create(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config))
if err != nil {
return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err)
}

sfsClient.Microversion = minManilaMicroversion

shareID := d.Get("share_id").(string)

grantOpts := shares.GrantAccessOpts{
AccessType: d.Get("access_type").(string),
AccessTo: d.Get("access_to").(string),
AccessLevel: d.Get("access_level").(string),
}

log.Printf("[DEBUG] Create Options: %#v", grantOpts)

timeout := d.Timeout(schema.TimeoutCreate)

log.Printf("[DEBUG] Attempting to grant access")
var access *shares.AccessRight
err = resource.Retry(timeout, func() *resource.RetryError {
access, err = shares.GrantAccess(sfsClient, shareID, grantOpts).Extract()
if err != nil {
return checkForRetryableError(err)
}
return nil
})

if err != nil {
detailedErr := errors.ErrorDetails{}
e := errors.ExtractErrorInto(err, &detailedErr)
if e != nil {
return fmt.Errorf("Error granting access: %s: %s", err, e)
}
for k, msg := range detailedErr {
return fmt.Errorf("Error granting access: %s (%d): %s", k, msg.Code, msg.Message)
}
}

d.SetId(access.ID)

pending := []string{"new", "queued_to_apply", "applying"}
// Wait for access to become active before continuing
err = waitForSFV2Access(sfsClient, shareID, access.ID, "active", pending, timeout)
if err != nil {
return fmt.Errorf("Error waiting for OpenStack share ACL on %s to be applied: %s", shareID, err)
}

return resourceSharedFilesystemShareAccessV2Read(d, meta)
}

func resourceSharedFilesystemShareAccessV2Read(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config))
if err != nil {
return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err)
}

sfsClient.Microversion = minManilaMicroversion

shareID := d.Get("share_id").(string)

access, err := shares.ListAccessRights(sfsClient, shareID).Extract()
if err != nil {
return CheckDeleted(d, err, "share")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only checking for a 404 return code. Will this ever return a 404, even if no acls exist?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it wont. But if there is no share, it will return 404 and ACLs should be removed since they are subobjects of the share object.

}

for _, v := range access {
if v.ID == d.Id() {
log.Printf("[DEBUG] Retrieved %s share ACL: %#v", d.Id(), v)

d.Set("access_type", v.AccessType)
d.Set("access_to", v.AccessTo)
d.Set("access_level", v.AccessLevel)

return nil
}
}

log.Printf("[DEBUG] Unable to find %s share access", d.Id())
d.SetId("")

return nil
}

func resourceSharedFilesystemShareAccessV2Delete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config))
if err != nil {
return fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err)
}

sfsClient.Microversion = minManilaMicroversion

shareID := d.Get("share_id").(string)

revokeOpts := shares.RevokeAccessOpts{AccessID: d.Id()}

timeout := d.Timeout(schema.TimeoutDelete)

log.Printf("[DEBUG] Attempting to revoke access %s", d.Id())
err = resource.Retry(timeout, func() *resource.RetryError {
err = shares.RevokeAccess(sfsClient, shareID, revokeOpts).ExtractErr()
if err != nil {
return checkForRetryableError(err)
}
return nil
})

if err != nil {
detailedErr := errors.ErrorDetails{}
e := errors.ExtractErrorInto(err, &detailedErr)
if e != nil {
return fmt.Errorf("Error waiting for OpenStack share ACL on %s to be removed: %s: %s", shareID, err, e)
}
for k, msg := range detailedErr {
return fmt.Errorf("Error waiting for OpenStack share ACL on %s to be removed: %s (%d): %s", shareID, k, msg.Code, msg.Message)
}
}

// Wait for access to become deleted before continuing
pending := []string{"new", "queued_to_deny", "denying"}
err = waitForSFV2Access(sfsClient, shareID, d.Id(), "denied", pending, timeout)
if err != nil {
return fmt.Errorf("Error waiting for OpenStack share ACL on %s to be removed: %s", shareID, err)
}

return nil
}

func resourceSharedFilesystemShareAccessV2Import(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
parts := strings.SplitN(d.Id(), "/", 2)
if len(parts) != 2 {
err := fmt.Errorf("Invalid format specified for Openstack share ACL. Format must be <share id>/<ACL id>")
return nil, err
}

config := meta.(*Config)
sfsClient, err := config.sharedfilesystemV2Client(GetRegion(d, config))
if err != nil {
return nil, fmt.Errorf("Error creating OpenStack sharedfilesystem client: %s", err)
}

sfsClient.Microversion = minManilaMicroversion

shareID := parts[0]
accessID := parts[1]

access, err := shares.ListAccessRights(sfsClient, shareID).Extract()
if err != nil {
return nil, fmt.Errorf("Unable to get %s Openstack share and its ACL's: %s", shareID, err)
}

for _, v := range access {
if v.ID == accessID {
log.Printf("[DEBUG] Retrieved %s share ACL: %#v", accessID, v)

d.SetId(accessID)
d.Set("share_id", shareID)
d.Set("access_type", v.AccessType)
d.Set("access_to", v.AccessTo)
d.Set("access_level", v.AccessLevel)

return []*schema.ResourceData{d}, nil
}
}

return nil, fmt.Errorf("[DEBUG] Unable to find %s share access", accessID)
}

// Full list of the share access statuses: https://developer.openstack.org/api-ref/shared-file-system/?expanded=list-services-detail,list-access-rules-detail#list-access-rules
func waitForSFV2Access(sfsClient *gophercloud.ServiceClient, shareID string, id string, target string, pending []string, timeout time.Duration) error {
log.Printf("[DEBUG] Waiting for access %s to become %s.", id, target)

stateConf := &resource.StateChangeConf{
Target: []string{target},
Pending: pending,
Refresh: resourceSFV2AccessRefreshFunc(sfsClient, shareID, id),
Timeout: timeout,
Delay: 1 * time.Second,
MinTimeout: 1 * time.Second,
}

_, err := stateConf.WaitForState()
if err != nil {
if _, ok := err.(gophercloud.ErrDefault404); ok {
switch target {
case "denied":
return nil
default:
return fmt.Errorf("Error: access %s not found: %s", id, err)
}
}
return fmt.Errorf("Error waiting for access %s to become %s: %s", id, target, err)
}

return nil
}

func resourceSFV2AccessRefreshFunc(sfsClient *gophercloud.ServiceClient, shareID string, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
access, err := shares.ListAccessRights(sfsClient, shareID).Extract()
if err != nil {
return nil, "", err
}
for _, v := range access {
if v.ID == id {
return v, v.State, nil
}
}
return nil, "", gophercloud.ErrDefault404{}
}
}
Loading