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 8 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
137 changes: 137 additions & 0 deletions internal/services/compute/disk_export_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package compute

import (
"crypto/sha256"
"encoding/hex"
"fmt"
"log"
"time"

"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2021-07-01/compute"
"github.com/hashicorp/terraform-provider-azurerm/helpers/azure"
"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/internal/tf/suppress"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
"github.com/hashicorp/terraform-provider-azurerm/internal/timeouts"
)

func resourceDiskExport() *pluginsdk.Resource {

return &pluginsdk.Resource{
Create: resourceDiskExportCreateUpdate,
harshavmb marked this conversation as resolved.
Show resolved Hide resolved
Read: resourceDiskExportRead,
Update: resourceDiskExportCreateUpdate,
harshavmb marked this conversation as resolved.
Show resolved Hide resolved
Delete: resourceDiskExportDelete,

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

Schema: map[string]*pluginsdk.Schema{
Copy link
Contributor

Choose a reason for hiding this comment

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

taking a look through the SDK, it appears this supports Tags too, which do support Update - so maybe we should support those?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm., I don't think tags would be of much user here. I couldn't find them in the API documentation here.
Please let me know if you need those tags.

"managed_disk_id": {
Type: pluginsdk.TypeString,
Required: true,
harshavmb marked this conversation as resolved.
Show resolved Hide resolved
DiffSuppressFunc: suppress.CaseDifference,
harshavmb marked this conversation as resolved.
Show resolved Hide resolved
ValidateFunc: azure.ValidateResourceID,
harshavmb marked this conversation as resolved.
Show resolved Hide resolved
},

"duration_in_seconds": {
Type: pluginsdk.TypeInt,
Required: true,
harshavmb marked this conversation as resolved.
Show resolved Hide resolved
},

"access": {
Type: pluginsdk.TypeString,
Optional: true,
Default: "Read",
},
harshavmb marked this conversation as resolved.
Show resolved Hide resolved

"sas_url": {
Type: pluginsdk.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
Sensitive: true,
ValidateFunc: validation.IsURLWithScheme([]string{"http", "https"}),
harshavmb marked this conversation as resolved.
Show resolved Hide resolved
},
},
}

}

func resourceDiskExportCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Compute.DisksClient
ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d)
harshavmb marked this conversation as resolved.
Show resolved Hide resolved
defer cancel()

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

parsedManagedDiskId, err := parse.ManagedDiskID(managedDiskId)
harshavmb marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return fmt.Errorf("parsing Managed Disk ID %q: %+v", parsedManagedDiskId.ID(), err)
harshavmb marked this conversation as resolved.
Show resolved Hide resolved
}

diskName := parsedManagedDiskId.DiskName
resourceGroupName := parsedManagedDiskId.ResourceGroup

grantAccessData := compute.GrantAccessData{
Access: compute.AccessLevel(access),
DurationInSeconds: &durationInSeconds,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

we're missing a Requires Import check here, we need to check if the Disk already has an Export and if so, raise a "this should be imported" error, (as below) it appears that this can be accessed by retrieving the disk and checking if it has a DiskAccess URI

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @tombuildsstuff ,

I think disk access is more related to private links. I did check the API again here. diskAccessId existed only for disks with private endpoints.

Having said that, I found diskState parameter which will be ActiveSAS when the disk export is active. I'll check this parameter.


// Request to Grant Access for Disk
diskGrantFuture, err := client.GrantAccess(ctx, resourceGroupName, diskName, grantAccessData)
if err != nil {
return fmt.Errorf("Error while granting access for disk export %q: %+v", parsedManagedDiskId.ID(), err)
}

// Wait until the Grant Request is complete
err = diskGrantFuture.WaitForCompletionRef(ctx, client.Client)
if err != nil {
return fmt.Errorf("Grant access operation failed %q (Resource Group %q): %+v", diskName, resourceGroupName, err)
}

// Fetch the SAS token from the response
diskGrantResponse, err := diskGrantFuture.Result(*client)
if err != nil {
return fmt.Errorf("Error while fetching the response %q: %+v", parsedManagedDiskId.ID(), err)
}

sasToken := *diskGrantResponse.AccessSAS
d.Set("sas_url", sasToken)
tokenHash := sha256.Sum256([]byte(sasToken))
d.SetId(hex.EncodeToString(tokenHash[:]))
harshavmb marked this conversation as resolved.
Show resolved Hide resolved

return resourceDiskExportRead(d, meta)

}

func resourceDiskExportRead(d *pluginsdk.ResourceData, meta interface{}) error {
return nil
}
harshavmb marked this conversation as resolved.
Show resolved Hide resolved

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.Get("managed_disk_id").(string))
harshavmb marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}

future, err := client.RevokeAccess(ctx, id.ResourceGroup, id.DiskName)
if err != nil {
return err
}

return future.WaitForCompletionRef(ctx, client.Client)
harshavmb marked this conversation as resolved.
Show resolved Hide resolved
}
49 changes: 49 additions & 0 deletions internal/services/compute/disk_export_resource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package compute_test

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance"
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check"
)

type DiskExportResource struct{}

func TestAccDiskExport_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_disk_export", "test")

data.DataSourceTest(t, []acceptance.TestStep{
{
Config: DiskExportResource{}.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).Key("id").Exists(),
harshavmb marked this conversation as resolved.
Show resolved Hide resolved
),
},
})
}

func (t DiskExportResource) 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_disk_export" "test" {
managed_disk_id = azurerm_managed_disk.test.id
duration_in_seconds = 300
access = "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_disk_export": resourceDiskExport(),
harshavmb marked this conversation as resolved.
Show resolved Hide resolved
}

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

# azurerm_disk_export

Manages a Disk Export.

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_disk_export" "test" {
managed_disk_id = azurerm_managed_disk.test.id
duration_in_seconds = 300
access = "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 greater than 30 seconds.

* `access` - (Optional) The level of access required on the disk. Supported are Read, Write. Defaults to Read.

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.
* `update` - (Defaults to 30 minutes) Used when updating 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..