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

New resource azurerm_managed_disk_sas_token to manage disk exports #15558

Merged
merged 12 commits into from
May 9, 2022
Merged
Show file tree
Hide file tree
Changes from 11 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
169 changes: 169 additions & 0 deletions internal/services/compute/disk_sas_token_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package compute

import (
"fmt"
"log"
"time"

"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2021-11-01/compute"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/compute/parse"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/compute/validate"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
"github.com/hashicorp/terraform-provider-azurerm/internal/timeouts"
)

func resourceDiskExport() *pluginsdk.Resource {
harshavmb marked this conversation as resolved.
Show resolved Hide resolved

return &pluginsdk.Resource{
Create: resourceDiskExportCreate,
Read: resourceDiskExportRead,
Delete: resourceDiskExportDelete,

Timeouts: &pluginsdk.ResourceTimeout{
Create: pluginsdk.DefaultTimeout(30 * time.Minute),
Read: pluginsdk.DefaultTimeout(5 * time.Minute),
Delete: pluginsdk.DefaultTimeout(30 * time.Minute),
},

Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error {
_, err := parse.ManagedDiskID(id)
return err
}),

Schema: map[string]*pluginsdk.Schema{
"managed_disk_id": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validate.ManagedDiskID,
},

// unable to provide upper value of 4294967295 as it's not comptabile with 32-bit (overflow errors)
"duration_in_seconds": {
Type: pluginsdk.TypeInt,
Required: true,
ForceNew: true,
ValidateFunc: validation.IntAtLeast(30),
},

"access_level": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{
string(compute.AccessLevelRead),
string(compute.AccessLevelWrite),
}, false),
},

"sas_url": {
Type: pluginsdk.TypeString,
Computed: true,
Sensitive: true,
},
},
}

}

func resourceDiskExportCreate(d *pluginsdk.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Compute.DisksClient
ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d)
defer cancel()

log.Printf("[INFO] preparing arguments for AzureRM Disk Export.")
durationInSeconds := int32(d.Get("duration_in_seconds").(int))
access := compute.AccessLevel(d.Get("access_level").(string))

diskId, err := parse.ManagedDiskID(d.Get("managed_disk_id").(string))
if err != nil {
return err
}

grantAccessData := compute.GrantAccessData{
Access: access,
DurationInSeconds: &durationInSeconds,
}

resp, err := client.Get(ctx, diskId.ResourceGroup, diskId.DiskName)
if err != nil {
return fmt.Errorf("error retrieving Disk %s: %+v", *diskId, err)
}

// checking whether disk export SAS URL is active already before creating. If yes, we raise an error
if resp.DiskState == "ActiveSAS" {
return fmt.Errorf("active SAS Token for Disk Export already exists, cannot create another one %s: %+v", *diskId, err)
}

future, err := client.GrantAccess(ctx, diskId.ResourceGroup, diskId.DiskName, grantAccessData)
if err != nil {
return fmt.Errorf("granting access to %s: %+v", *diskId, err)
}
if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
return fmt.Errorf("waiting for access to be granted to %s: %+v", *diskId, err)
}
read, err := future.Result(*client)
if err != nil {
return fmt.Errorf("retrieving SAS Token for Disk Access %s: %+v", *diskId, err)
}
if read.AccessSAS == nil {
return fmt.Errorf("retrieving SAS Token for Disk Access %s: SAS was nil", *diskId)
}

d.SetId(diskId.ID())
sasToken := *read.AccessSAS
d.Set("sas_url", sasToken)

return resourceDiskExportRead(d, meta)

}

func resourceDiskExportRead(d *pluginsdk.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Compute.DisksClient
ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
defer cancel()

diskId, err := parse.ManagedDiskID(d.Id())
if err != nil {
return err
}

resp, err := client.Get(ctx, diskId.ResourceGroup, diskId.DiskName)
if err != nil {
// checking whether disk export SAS URL is active post creation. If no, we raise an error
if resp.DiskState != "ActiveSAS" {
log.Printf("[INFO] Disk SAS token %q does not exist - removing from state", d.Id())
d.SetId("")
return nil
}
return fmt.Errorf("making Read request on Azure Disk Export for SAS Token %s (resource group %s): %s", diskId.DiskName, diskId.ResourceGroup, err)
}

d.SetId(diskId.ID())

return nil
}

func resourceDiskExportDelete(d *pluginsdk.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Compute.DisksClient
ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d)
defer cancel()

id, err := parse.ManagedDiskID(d.Id())
if err != nil {
return err
}

future, err := client.RevokeAccess(ctx, id.ResourceGroup, id.DiskName)
if err != nil {
return fmt.Errorf("revoking access to %s: %+v", *id, err)
}

if err := future.WaitForCompletionRef(ctx, client.Client); err != nil {
return fmt.Errorf("waiting for revocation of access to %s: %+v", *id, err)
}

return nil
}
73 changes: 73 additions & 0 deletions internal/services/compute/disk_sas_token_resource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package compute_test

import (
"context"
"fmt"
"testing"

"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance"
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/compute/parse"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/utils"
)

type ManagedDiskSASTokenResource struct{}

func TestAccManagedDiskSASToken_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_managed_disk_sas_token", "test")
r := ManagedDiskSASTokenResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
})
}

func (t ManagedDiskSASTokenResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) {
id, err := parse.ManagedDiskID(state.ID)
if err != nil {
return nil, err
}

resp, err := clients.Compute.DisksClient.Get(ctx, id.ResourceGroup, id.DiskName)
if err != nil {
return nil, fmt.Errorf("retrieving Compute Disk Export status %q", id.String())
}

if resp.DiskState != "ActiveSAS" {
return nil, fmt.Errorf("Disk SAS token %s (resource group %s): %s", id.DiskName, id.ResourceGroup, err)
}

return utils.Bool(resp.ID != nil), nil
}

func (r ManagedDiskSASTokenResource) basic(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "test" {
name = "acctestRG-revokedisk-%d"
location = "%s"
}
resource "azurerm_managed_disk" "test" {
name = "acctestsads%s"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
storage_account_type = "Standard_LRS"
create_option = "Empty"
disk_size_gb = "1"
}
resource "azurerm_managed_disk_sas_token" "test" {
harshavmb marked this conversation as resolved.
Show resolved Hide resolved
managed_disk_id = azurerm_managed_disk.test.id
duration_in_seconds = 300
access_level = "Read"
}
`, data.RandomInteger, data.Locations.Primary, data.RandomString)
}
1 change: 1 addition & 0 deletions internal/services/compute/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource {
"azurerm_windows_virtual_machine": resourceWindowsVirtualMachine(),
"azurerm_windows_virtual_machine_scale_set": resourceWindowsVirtualMachineScaleSet(),
"azurerm_ssh_public_key": resourceSshPublicKey(),
"azurerm_managed_disk_sas_token": resourceDiskExport(),
}

return resources
Expand Down
68 changes: 68 additions & 0 deletions website/docs/r/disk_export.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
subcategory: "Compute"
layout: "azurerm"
page_title: "Azure Resource Manager: azurerm_managed_disk_sas_token"
description: |-
Manages a Disk Export.
---

# azurerm_managed_disk_sas_token

Manages a Disk Export.
harshavmb marked this conversation as resolved.
Show resolved Hide resolved

Use this resource to obtain a Shared Access Signature (SAS Token) for an existing Managed Disk.

Shared access signatures allow fine-grained, ephemeral access control to various aspects of Managed Disk similar to blob/storage account container.

With the help of this resource, data from the disk can be copied from managed disk to a storage blob or to some other system without the need of azcopy.

## Example Usage

```hcl
resource "azurerm_resource_group" "test" {
name = "testrg"
location = "West Europe"
}
resource "azurerm_managed_disk" "test" {
name = "tst-disk-export"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
storage_account_type = "Standard_LRS"
create_option = "Empty"
disk_size_gb = "1"
}
resource "azurerm_managed_disk_sas_token" "test" {
harshavmb marked this conversation as resolved.
Show resolved Hide resolved
managed_disk_id = azurerm_managed_disk.test.id
duration_in_seconds = 300
access_level = "Read"
}
```

## Arguments Reference

The following arguments are supported:

* `managed_disk_id` - (Required) The ID of an existing Managed Disk which should be exported. Changing this forces a new resource to be created.

* `duration_in_seconds` - (Required) The duration for which the export should be allowed. Should be between 30 & 4294967295 seconds.

* `access_level` - (Required) The level of access required on the disk. Supported are Read, Write.

Refer to the [SAS creation reference from Azure](https://docs.microsoft.com/en-us/rest/api/compute/disks/grant-access)
for additional details on the fields above.

## Attributes Reference

In addition to the Arguments listed above - the following Attributes are exported:

* `id` - The ID of the Disk Export resource.

* `sas_url` - The computed Shared Access Signature (SAS) of the Managed Disk.

## Timeouts

The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions:

* `create` - (Defaults to 30 minutes) Used when creating the Disk.
* `read` - (Defaults to 5 minutes) Used when retrieving the Disk.
* `delete` - (Defaults to 30 minutes) Used when deleting the Disk.
Copy link
Collaborator

Choose a reason for hiding this comment

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

we should include how to import the resource here?

Copy link
Contributor Author

@harshavmb harshavmb May 6, 2022

Choose a reason for hiding this comment

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

The SAS tokens are ephemeral, I don't know how useful it would be to import the disk here.

I'm just importing the managed disk resource id. Hope that's okay..