diff --git a/azurerm/internal/services/compute/client/client.go b/azurerm/internal/services/compute/client/client.go index cfef5feed2e2..3a0361188f3a 100644 --- a/azurerm/internal/services/compute/client/client.go +++ b/azurerm/internal/services/compute/client/client.go @@ -29,6 +29,7 @@ type Client struct { VMScaleSetVMsClient *compute.VirtualMachineScaleSetVMsClient VMClient *compute.VirtualMachinesClient VMImageClient *compute.VirtualMachineImagesClient + SSHPublicKeysClient *compute.SSHPublicKeysClient } func NewClient(o *common.ClientOptions) *Client { @@ -98,6 +99,9 @@ func NewClient(o *common.ClientOptions) *Client { vmClient := compute.NewVirtualMachinesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&vmClient.Client, o.ResourceManagerAuthorizer) + sshPublicKeysClient := compute.NewSSHPublicKeysClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&sshPublicKeysClient.Client, o.ResourceManagerAuthorizer) + return &Client{ AvailabilitySetsClient: &availabilitySetsClient, DedicatedHostsClient: &dedicatedHostsClient, @@ -121,5 +125,6 @@ func NewClient(o *common.ClientOptions) *Client { VMScaleSetVMsClient: &vmScaleSetVMsClient, VMClient: &vmClient, VMImageClient: &vmImageClient, + SSHPublicKeysClient: &sshPublicKeysClient, } } diff --git a/azurerm/internal/services/compute/parse/ssh_public_key.go b/azurerm/internal/services/compute/parse/ssh_public_key.go new file mode 100644 index 000000000000..524471ea3bc8 --- /dev/null +++ b/azurerm/internal/services/compute/parse/ssh_public_key.go @@ -0,0 +1,69 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + "strings" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type SSHPublicKeyId struct { + SubscriptionId string + ResourceGroup string + Name string +} + +func NewSSHPublicKeyID(subscriptionId, resourceGroup, name string) SSHPublicKeyId { + return SSHPublicKeyId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + Name: name, + } +} + +func (id SSHPublicKeyId) String() string { + segments := []string{ + fmt.Sprintf("Name %q", id.Name), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "S S H Public Key", segmentsStr) +} + +func (id SSHPublicKeyId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/sshPublicKeys/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.Name) +} + +// SSHPublicKeyID parses a SSHPublicKey ID into an SSHPublicKeyId struct +func SSHPublicKeyID(input string) (*SSHPublicKeyId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := SSHPublicKeyId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") + } + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") + } + + if resourceId.Name, err = id.PopSegment("sshPublicKeys"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/azurerm/internal/services/compute/parse/ssh_public_key_test.go b/azurerm/internal/services/compute/parse/ssh_public_key_test.go new file mode 100644 index 000000000000..696510deca26 --- /dev/null +++ b/azurerm/internal/services/compute/parse/ssh_public_key_test.go @@ -0,0 +1,112 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" +) + +var _ resourceid.Formatter = SSHPublicKeyId{} + +func TestSSHPublicKeyIDFormatter(t *testing.T) { + actual := NewSSHPublicKeyID("12345678-1234-9876-4563-123456789012", "resGroup1", "sshpublickey1").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/sshPublicKeys/sshpublickey1" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestSSHPublicKeyID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *SSHPublicKeyId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, + }, + + { + // missing Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/", + Error: true, + }, + + { + // missing value for Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/sshPublicKeys/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/sshPublicKeys/sshpublickey1", + Expected: &SSHPublicKeyId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + Name: "sshpublickey1", + }, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.COMPUTE/SSHPUBLICKEYS/SSHPUBLICKEY1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := SSHPublicKeyID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + if actual.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + } +} diff --git a/azurerm/internal/services/compute/registration.go b/azurerm/internal/services/compute/registration.go index 0a2dd816fe9b..224dd228da40 100644 --- a/azurerm/internal/services/compute/registration.go +++ b/azurerm/internal/services/compute/registration.go @@ -38,6 +38,7 @@ func (r Registration) SupportedDataSources() map[string]*schema.Resource { "azurerm_snapshot": dataSourceArmSnapshot(), "azurerm_virtual_machine": dataSourceArmVirtualMachine(), "azurerm_virtual_machine_scale_set": dataSourceArmVirtualMachineScaleSet(), + "azurerm_ssh_public_key": dataSourceArmSshPublicKey(), } } @@ -67,6 +68,7 @@ func (r Registration) SupportedResources() map[string]*schema.Resource { "azurerm_virtual_machine_scale_set_extension": resourceArmVirtualMachineScaleSetExtension(), "azurerm_windows_virtual_machine": resourceWindowsVirtualMachine(), "azurerm_windows_virtual_machine_scale_set": resourceArmWindowsVirtualMachineScaleSet(), + "azurerm_ssh_public_key": resourceSshPublicKey(), } return resources diff --git a/azurerm/internal/services/compute/resourceids.go b/azurerm/internal/services/compute/resourceids.go index c18f1d589296..662fc3abd1b0 100644 --- a/azurerm/internal/services/compute/resourceids.go +++ b/azurerm/internal/services/compute/resourceids.go @@ -14,4 +14,5 @@ package compute //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=VirtualMachineExtension -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/virtualMachines/machine1/extensions/extension1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=VirtualMachineScaleSet -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/virtualMachineScaleSets/scaleSet1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=VirtualMachineScaleSetExtension -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/virtualMachineScaleSets/scaleSet1/extensions/extension1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=SSHPublicKey -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/sshPublicKeys/sshpublickey1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=DiskAccess -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/diskAccesses/diskAccess1 diff --git a/azurerm/internal/services/compute/ssh_public_key_data_source.go b/azurerm/internal/services/compute/ssh_public_key_data_source.go new file mode 100644 index 000000000000..b91d0b0a515b --- /dev/null +++ b/azurerm/internal/services/compute/ssh_public_key_data_source.go @@ -0,0 +1,74 @@ +package compute + +import ( + "fmt" + "regexp" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func dataSourceArmSshPublicKey() *schema.Resource { + return &schema.Resource{ + Read: dataSourceArmSshPublicKeyRead, + + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(5 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringMatch( + regexp.MustCompile("^[-a-zA-Z0-9(_)]{1,128}$"), + "Public SSH Key name must be 1 - 128 characters long, can contain letters, numbers, underscores, and hyphens (but the first and last character must be a letter or number).", + ), + }, + + "resource_group_name": azure.SchemaResourceGroupNameForDataSource(), + + "public_key": { + Type: schema.TypeString, + Computed: true, + }, + + "tags": tags.Schema(), + }, + } +} + +func dataSourceArmSshPublicKeyRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Compute.SSHPublicKeysClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + resGroup := d.Get("resource_group_name").(string) + name := d.Get("name").(string) + + resp, err := client.Get(ctx, resGroup, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error: SSH Public Key %q (Resource Group %q) was not found", name, resGroup) + } + return fmt.Errorf("[ERROR] Error making Read request on Azure SSH Public Key %q (Resource Group %q): %s", name, resGroup, err) + } + + d.SetId(*resp.ID) + + d.Set("name", name) + d.Set("resource_group_name", resGroup) + + if props := resp.SSHPublicKeyResourceProperties; props != nil { + d.Set("public_key", props.PublicKey) + } + + return tags.FlattenAndSet(d, resp.Tags) +} diff --git a/azurerm/internal/services/compute/ssh_public_key_resource.go b/azurerm/internal/services/compute/ssh_public_key_resource.go new file mode 100644 index 000000000000..9f9b9b583866 --- /dev/null +++ b/azurerm/internal/services/compute/ssh_public_key_resource.go @@ -0,0 +1,232 @@ +package compute + +import ( + "fmt" + "log" + "regexp" + "time" + + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute" + "github.com/hashicorp/go-azure-helpers/response" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/compute/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceSshPublicKey() *schema.Resource { + return &schema.Resource{ + Create: resourceSshPublicKeyCreate, + Read: resourceSshPublicKeyRead, + Update: resourceSshPublicKeyUpdate, + Delete: resourceSshPublicKeyDelete, + + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.SSHPublicKeyID(id) + return err + }), + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(45 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(45 * time.Minute), + Delete: schema.DefaultTimeout(45 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringMatch( + regexp.MustCompile("^[-a-zA-Z0-9(_)]{1,128}$"), + "Public SSH Key name must be 1 - 128 characters long, can contain letters, numbers, underscores, and hyphens (but the first and last character must be a letter or number).", + ), + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "location": azure.SchemaLocation(), + + "public_key": { + Type: schema.TypeString, + Required: true, + ForceNew: false, + ValidateFunc: ValidateSSHKey, + }, + + "tags": tags.Schema(), + }, + } +} + +func resourceSshPublicKeyCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Compute.SSHPublicKeysClient + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + public_key := d.Get("public_key").(string) + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("checking for existing SSH Public Key %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if !utils.ResponseWasNotFound(resp.Response) { + return tf.ImportAsExistsError("azurerm_ssh_public_key", *resp.ID) + } + + location := azure.NormalizeLocation(d.Get("location").(string)) + + t := d.Get("tags").(map[string]interface{}) + + params := compute.SSHPublicKeyResource{ + Name: utils.String(name), + Location: utils.String(location), + Tags: tags.Expand(t), + SSHPublicKeyResourceProperties: &compute.SSHPublicKeyResourceProperties{ + PublicKey: utils.String(public_key), + }, + } + + if _, err := client.Create(ctx, resourceGroup, name, params); err != nil { + return fmt.Errorf("creating SSH Public Key %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + read, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("retrieving SSH Public Key %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + if read.ID == nil { + return fmt.Errorf("retrieving SSH Public Key %q (Resource Group %q): `id` was nil", name, resourceGroup) + } + + d.SetId(*read.ID) + return resourceSshPublicKeyRead(d, meta) +} + +func resourceSshPublicKeyRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Compute.SSHPublicKeysClient + + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.SSHPublicKeyID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[DEBUG] SSH Public Key %q was not found in Resource Group %q - removing from state!", id.Name, id.ResourceGroup) + d.SetId("") + return nil + } + + return fmt.Errorf("retrieving SSH Public Key %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + + d.Set("name", id.Name) + d.Set("resource_group_name", id.ResourceGroup) + if location := resp.Location; location != nil { + d.Set("location", azure.NormalizeLocation(*location)) + } + + if props := resp.SSHPublicKeyResourceProperties; props != nil { + d.Set("public_key", props.PublicKey) + } + + return tags.FlattenAndSet(d, resp.Tags) +} + +func resourceSshPublicKeyUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Compute.SSHPublicKeysClient + ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.SSHPublicKeyID(d.Id()) + if err != nil { + return err + } + + log.Printf("[DEBUG] Retrieving SSH Public Key %q (Resource Group %q)..", id.Name, id.ResourceGroup) + existing, err := client.Get(ctx, id.ResourceGroup, id.Name) + if err != nil { + if utils.ResponseWasNotFound(existing.Response) { + return nil + } + + return fmt.Errorf("retrieving SSH Public Key %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + + props := compute.SSHPublicKeyResourceProperties{} + + if d.HasChange("public_key") { + props.PublicKey = utils.String(d.Get("public_key").(string)) + } + + update := compute.SSHPublicKeyUpdateResource{ + SSHPublicKeyResourceProperties: &props, + } + + if d.HasChange("tags") { + tagsRaw := d.Get("tags").(map[string]interface{}) + update.Tags = tags.Expand(tagsRaw) + } + + log.Printf("[DEBUG] Updating SSH Public Key %q (Resource Group %q)..", id.Name, id.ResourceGroup) + + if _, err := client.Update(ctx, id.ResourceGroup, id.Name, update); err != nil { + return fmt.Errorf("updating SSH Public Key %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + + log.Printf("[DEBUG] Updated SSH Public Key %q (Resource Group %q).", id.Name, id.ResourceGroup) + + return resourceSshPublicKeyRead(d, meta) +} + +func resourceSshPublicKeyDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Compute.SSHPublicKeysClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.SSHPublicKeyID(d.Id()) + if err != nil { + return err + } + + log.Printf("[DEBUG] Retrieving SSH Public Key %q (Resource Group %q)..", id.Name, id.ResourceGroup) + existing, err := client.Get(ctx, id.ResourceGroup, id.Name) + if err != nil { + if utils.ResponseWasNotFound(existing.Response) { + return nil + } + + return fmt.Errorf("retrieving SSH Public Key %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + + log.Printf("[DEBUG] Deleting SSH Public Key %q (Resource Group %q)..", id.Name, id.ResourceGroup) + resp, err := client.Delete(ctx, id.ResourceGroup, id.Name) + if err != nil { + if response.WasNotFound(resp.Response) { + return nil + } + return fmt.Errorf("deleting SSH Public Key %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + + log.Printf("[DEBUG] Deleted SSH Public Key %q (Resource Group %q).", id.Name, id.ResourceGroup) + + return nil +} diff --git a/azurerm/internal/services/compute/tests/ssh_public_key_data_source_test.go b/azurerm/internal/services/compute/tests/ssh_public_key_data_source_test.go new file mode 100644 index 000000000000..467b35582395 --- /dev/null +++ b/azurerm/internal/services/compute/tests/ssh_public_key_data_source_test.go @@ -0,0 +1,41 @@ +package tests + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" +) + +func TestAccDataSourceAzureSshPublicKey_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_ssh_public_key", "test") + + key1 := "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+wWK73dCr+jgQOAxNsHAnNNNMEMWOHYEccp6wJm2gotpr9katuF/ZAdou5AaW1C61slRkHRkpRRX9FA9CYBiitZgvCCz+3nWNN7l/Up54Zps/pHWGZLHNJZRYyAB6j5yVLMVHIHriY49d/GZTZVNB8GoJv9Gakwc/fuEZYYl4YDFiGMBP///TzlI4jhiJzjKnEvqPFki5p2ZRJqcbCiF4pJrxUQR/RXqVFQdbRLZgYfJ8xGB878RENq3yQ39d8dVOkq4edbkzwcUmwwwkYVPIoDGsYLaRHnG+To7FvMeyO7xDVQkMKzopTQV8AuKpyvpqu0a9pWOMaiCyDytO7GGN you@me.com" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureSshPublicKeyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAzureSshPublicKey_template(data, key1), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(data.ResourceName, "public_key", key1), + ), + }, + }, + }) +} + +func testAccDataSourceAzureSshPublicKey_template(data acceptance.TestData, sshKey string) string { + template := testAccAzureSshPublicKey_template(data, sshKey) + return fmt.Sprintf(` +%s + +data "azurerm_ssh_public_key" "test" { + name = azurerm_ssh_public_key.test.name + resource_group_name = azurerm_resource_group.test.name +} +`, template) +} diff --git a/azurerm/internal/services/compute/tests/ssh_public_key_resource_test.go b/azurerm/internal/services/compute/tests/ssh_public_key_resource_test.go new file mode 100644 index 000000000000..806799c38a51 --- /dev/null +++ b/azurerm/internal/services/compute/tests/ssh_public_key_resource_test.go @@ -0,0 +1,122 @@ +package tests + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/compute/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureSshPublicKey_CreateUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ssh_public_key", "test") + + key1 := "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+wWK73dCr+jgQOAxNsHAnNNNMEMWOHYEccp6wJm2gotpr9katuF/ZAdou5AaW1C61slRkHRkpRRX9FA9CYBiitZgvCCz+3nWNN7l/Up54Zps/pHWGZLHNJZRYyAB6j5yVLMVHIHriY49d/GZTZVNB8GoJv9Gakwc/fuEZYYl4YDFiGMBP///TzlI4jhiJzjKnEvqPFki5p2ZRJqcbCiF4pJrxUQR/RXqVFQdbRLZgYfJ8xGB878RENq3yQ39d8dVOkq4edbkzwcUmwwwkYVPIoDGsYLaRHnG+To7FvMeyO7xDVQkMKzopTQV8AuKpyvpqu0a9pWOMaiCyDytO7GGN you@me.com" + key2 := "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0/NDMj2wG6bSa6jbn6E3LYlUsYiWMp1CQ2sGAijPALW6OrSu30lz7nKpoh8Qdw7/A4nAJgweI5Oiiw5/BOaGENM70Go+VM8LQMSxJ4S7/8MIJEZQp5HcJZ7XDTcEwruknrd8mllEfGyFzPvJOx6QAQocFhXBW6+AlhM3gn/dvV5vdrO8ihjET2GoDUqXPYC57ZuY+/Fz6W3KV8V97BvNUhpY5yQrP5VpnyvvXNFQtzDfClTvZFPuoHQi3/KYPi6O0FSD74vo8JOBZZY09boInPejkm9fvHQqfh0bnN7B6XJoUwC1Qprrx+XIy7ust5AEn5XL7d4lOvcR14MxDDKEp you@me.com" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureSshPublicKeyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureSshPublicKey_template(data, key1), + Check: resource.ComposeTestCheckFunc( + testCheckAzureSshPublicKeyExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "public_key", key1), + ), + }, + data.ImportStep(), + { + Config: testAccAzureSshPublicKey_template(data, key2), + Check: resource.ComposeTestCheckFunc( + testCheckAzureSshPublicKeyExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "public_key", key2), + ), + }, + data.ImportStep(), + }, + }) +} + +func testCheckAzureSshPublicKeyExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).Compute.SSHPublicKeysClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + id, err := parse.SSHPublicKeyID(rs.Primary.ID) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: SSH Public Key %q (Resource Group: %q) does not exist", id.Name, id.ResourceGroup) + } + + return fmt.Errorf("Bad: Get on SSHPublicKeysClient: %+v", err) + } + + return nil + } +} + +func testCheckAzureSshPublicKeyDestroy(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).Compute.SSHPublicKeysClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_ssh_public_key" { + continue + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return nil + } + + if resp.StatusCode != http.StatusNotFound { + return fmt.Errorf("SSH Key still exists: \n%#v", resp.SSHPublicKeyResourceProperties) + } + } + + return nil +} + +func testAccAzureSshPublicKey_template(data acceptance.TestData, sshKey string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "ACCTESTRG-%d" + location = "%s" +} + +resource "azurerm_ssh_public_key" "test" { + name = "test-public-key-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + public_key = "%s" + tags = { + test-tag : "test-value-%d" + } + +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, sshKey, data.RandomInteger) +} diff --git a/azurerm/internal/services/compute/validate/ssh_public_key_id.go b/azurerm/internal/services/compute/validate/ssh_public_key_id.go new file mode 100644 index 000000000000..58724eb5f8d9 --- /dev/null +++ b/azurerm/internal/services/compute/validate/ssh_public_key_id.go @@ -0,0 +1,23 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/compute/parse" +) + +func SSHPublicKeyID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := parse.SSHPublicKeyID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/azurerm/internal/services/compute/validate/ssh_public_key_id_test.go b/azurerm/internal/services/compute/validate/ssh_public_key_id_test.go new file mode 100644 index 000000000000..9c205f3e5fec --- /dev/null +++ b/azurerm/internal/services/compute/validate/ssh_public_key_id_test.go @@ -0,0 +1,76 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestSSHPublicKeyID(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + + { + // empty + Input: "", + Valid: false, + }, + + { + // missing SubscriptionId + Input: "/", + Valid: false, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Valid: false, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Valid: false, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Valid: false, + }, + + { + // missing Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/", + Valid: false, + }, + + { + // missing value for Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/sshPublicKeys/", + Valid: false, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Compute/sshPublicKeys/sshpublickey1", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.COMPUTE/SSHPUBLICKEYS/SSHPUBLICKEY1", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := SSHPublicKeyID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/website/docs/d/ssh_public_key.html.markdown b/website/docs/d/ssh_public_key.html.markdown new file mode 100644 index 000000000000..948e0295cc54 --- /dev/null +++ b/website/docs/d/ssh_public_key.html.markdown @@ -0,0 +1,50 @@ +--- +subcategory: "Compute" +layout: "azurerm" +page_title: "Azure Resource Manager: Data Source: azurerm_ssh_public_key" +description: |- + Gets information about an existing SSH Public Key. +--- + +# Data Source: azurerm_ssh_public_key + +Use this data source to access information about an existing SSH Public Key. + +## Example Usage + +```hcl +data "azurerm_ssh_public_key" "example" { + name = "existing" + resource_group_name = "existing" +} + +output "id" { + value = data.azurerm_ssh_public_key.example.id +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name of this SSH Public Key. + +* `resource_group_name` - (Required) The name of the Resource Group where the SSH Public Key exists. + +--- + +* `tags` - (Optional) A mapping of tags which should be assigned to the SSH Public Key. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the SSH Public Key. + +* `public_key` - The SSH public key used to authenticate to a virtual machine through ssh. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `read` - (Defaults to 5 minutes) Used when retrieving the SSH Public Key. diff --git a/website/docs/r/ssh_public_key.html.markdown b/website/docs/r/ssh_public_key.html.markdown new file mode 100644 index 000000000000..6839e806e19a --- /dev/null +++ b/website/docs/r/ssh_public_key.html.markdown @@ -0,0 +1,61 @@ +--- +subcategory: "Compute" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_ssh_public_key" +description: |- + Manages a SSH Public Key. +--- + +# azurerm_ssh_public_key + +Manages a SSH Public Key. + +## Example Usage + +```hcl +resource "azurerm_ssh_public_key" "example" { + name = "example" + resource_group_name = "example" + location = "West Europe" + public_key = file("~/.ssh/id_rsa.pub") +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `location` - (Required) The Azure Region where the SSH Public Key should exist. Changing this forces a new SSH Public Key to be created. + +* `name` - (Required) The name which should be used for this SSH Public Key. Changing this forces a new SSH Public Key to be created. + +* `public_key` - (Required) SSH public key used to authenticate to a virtual machine through ssh. the provided public key needs to be at least 2048-bit and in ssh-rsa format. + +* `resource_group_name` - (Required) The name of the Resource Group where the SSH Public Key should exist. Changing this forces a new SSH Public Key to be created. + +--- + +* `tags` - (Optional) A mapping of tags which should be assigned to the SSH Public Key. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the SSH Public Key. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 45 minutes) Used when creating the SSH Public Key. +* `read` - (Defaults to 5 minutes) Used when retrieving the SSH Public Key. +* `update` - (Defaults to 45 minutes) Used when updating the SSH Public Key. +* `delete` - (Defaults to 45 minutes) Used when deleting the SSH Public Key. + +## Import + +SSH Public Keys can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_ssh_public_key.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Compute/SshPublicKeys/mySshPublicKeyName1 +```