Skip to content

Commit

Permalink
New Resource - azurerm_signalr_service_custom_certificate (#21112)
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaxyi authored Apr 1, 2023
1 parent 57076d1 commit e236dd2
Show file tree
Hide file tree
Showing 4 changed files with 521 additions and 0 deletions.
1 change: 1 addition & 0 deletions internal/services/signalr/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func (r Registration) AssociatedGitHubLabel() string {
func (r Registration) Resources() []sdk.Resource {
return []sdk.Resource{
CustomCertWebPubsubResource{},
CustomCertSignalrServiceResource{},
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package signalr

import (
"context"
"fmt"
"time"

"github.com/hashicorp/go-azure-helpers/lang/response"
"github.com/hashicorp/go-azure-sdk/resource-manager/signalr/2023-02-01/signalr"
"github.com/hashicorp/terraform-provider-azurerm/internal/sdk"
keyVaultParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse"
keyVaultValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/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/utils"
)

type CustomCertSignalrServiceResourceModel struct {
Name string `tfschema:"name"`
SignalRServiceId string `tfschema:"signalr_service_id"`
CustomCertId string `tfschema:"custom_certificate_id"`
CertificateVersion string `tfschema:"certificate_version"`
}

type CustomCertSignalrServiceResource struct{}

var _ sdk.Resource = CustomCertSignalrServiceResource{}

func (r CustomCertSignalrServiceResource) Arguments() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{
"name": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringIsNotEmpty,
},

"signalr_service_id": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: signalr.ValidateSignalRID,
},

"custom_certificate_id": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.Any(
keyVaultValidate.NestedItemId,
keyVaultValidate.NestedItemIdWithOptionalVersion,
),
},
}
}

func (r CustomCertSignalrServiceResource) Attributes() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{
"certificate_version": {
Type: pluginsdk.TypeString,
Computed: true,
},
}
}

func (r CustomCertSignalrServiceResource) ModelObject() interface{} {
return &CustomCertSignalrServiceResourceModel{}
}

func (r CustomCertSignalrServiceResource) ResourceType() string {
return "azurerm_signalr_service_custom_certificate"
}

func (r CustomCertSignalrServiceResource) Create() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 30 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
var customCertSignalrService CustomCertSignalrServiceResourceModel
if err := metadata.Decode(&customCertSignalrService); err != nil {
return err
}
client := metadata.Client.SignalR.SignalRClient

signalRServiceId, err := signalr.ParseSignalRID(metadata.ResourceData.Get("signalr_service_id").(string))
if err != nil {
return fmt.Errorf("parsing signalr service id error: %+v", err)
}

keyVaultCertificateId, err := keyVaultParse.ParseOptionallyVersionedNestedItemID(metadata.ResourceData.Get("custom_certificate_id").(string))
if err != nil {
return fmt.Errorf("parsing custom certificate id error: %+v", err)
}

keyVaultUri := keyVaultCertificateId.KeyVaultBaseUrl
keyVaultSecretName := keyVaultCertificateId.Name

id := signalr.NewCustomCertificateID(signalRServiceId.SubscriptionId, signalRServiceId.ResourceGroupName, signalRServiceId.SignalRName, metadata.ResourceData.Get("name").(string))

existing, err := client.CustomCertificatesGet(ctx, id)
if err != nil && !response.WasNotFound(existing.HttpResponse) {
return fmt.Errorf("checking for presence of existing SignalR service custom cert error %s: %+v", id, err)
}

if !response.WasNotFound(existing.HttpResponse) {
return metadata.ResourceRequiresImport(r.ResourceType(), id)
}

customCert := signalr.CustomCertificate{
Properties: signalr.CustomCertificateProperties{
KeyVaultBaseUri: keyVaultUri,
KeyVaultSecretName: keyVaultSecretName,
},
}

if certVersion := keyVaultCertificateId.Version; certVersion != "" {
if customCertSignalrService.CertificateVersion != "" && certVersion != customCertSignalrService.CertificateVersion {
return fmt.Errorf("certificate version in cert id is different from `certificate_version`")
}
customCert.Properties.KeyVaultSecretVersion = utils.String(certVersion)
}

if err := client.CustomCertificatesCreateOrUpdateThenPoll(ctx, id, customCert); err != nil {
return fmt.Errorf("creating signalR custom certificate: %s: %+v", id, err)
}

metadata.SetID(id)
return nil
},
}
}

func (r CustomCertSignalrServiceResource) Read() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 5 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
client := metadata.Client.SignalR.SignalRClient
keyVaultClient := metadata.Client.KeyVault
resourcesClient := metadata.Client.Resource
id, err := signalr.ParseCustomCertificateID(metadata.ResourceData.Id())
if err != nil {
return err
}

resp, err := client.CustomCertificatesGet(ctx, *id)
if err != nil {
if response.WasNotFound(resp.HttpResponse) {
return metadata.MarkAsGone(id)
}
return fmt.Errorf("reading SignalR custom certificate %s: %+v", id, err)
}

if resp.Model == nil {
return fmt.Errorf("retrieving %s: got nil model", *id)
}

vaultBasedUri := resp.Model.Properties.KeyVaultBaseUri
certName := resp.Model.Properties.KeyVaultSecretName

keyVaultIdRaw, err := keyVaultClient.KeyVaultIDFromBaseUrl(ctx, resourcesClient, vaultBasedUri)
if err != nil {
return fmt.Errorf("getting key vault base uri from %s: %+v", id, err)
}
vaultId, err := keyVaultParse.VaultID(*keyVaultIdRaw)
if err != nil {
return fmt.Errorf("parsing key vault %s: %+v", vaultId, err)
}

signalrServiceId := signalr.NewSignalRID(id.SubscriptionId, id.ResourceGroupName, id.SignalRName).ID()

certVersion := ""
if resp.Model.Properties.KeyVaultSecretVersion != nil {
certVersion = *resp.Model.Properties.KeyVaultSecretVersion
}
nestedItem, err := keyVaultParse.NewNestedItemID(vaultBasedUri, "certificates", certName, certVersion)
if err != nil {
return err
}

certId := nestedItem.ID()

state := CustomCertSignalrServiceResourceModel{
Name: id.CustomCertificateName,
CustomCertId: certId,
SignalRServiceId: signalrServiceId,
CertificateVersion: utils.NormalizeNilableString(resp.Model.Properties.KeyVaultSecretVersion),
}

return metadata.Encode(&state)
},
}
}

func (r CustomCertSignalrServiceResource) Delete() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 30 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
client := metadata.Client.SignalR.SignalRClient

id, err := signalr.ParseCustomCertificateID(metadata.ResourceData.Id())
if err != nil {
return err
}
if _, err := client.CustomCertificatesDelete(ctx, *id); err != nil {
return fmt.Errorf("deleting %s: %+v", id, err)
}
return nil
},
}
}

func (r CustomCertSignalrServiceResource) IDValidationFunc() pluginsdk.SchemaValidateFunc {
return signalr.ValidateCustomCertificateID
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package signalr_test

import (
"context"
"fmt"
"testing"

"github.com/hashicorp/go-azure-helpers/lang/response"
"github.com/hashicorp/go-azure-sdk/resource-manager/signalr/2023-02-01/signalr"
"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/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/utils"
)

type CustomCertSignalrServiceResource struct{}

func TestAccCustomCertSignalrService_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_signalr_service_custom_certificate", "test")
r := CustomCertSignalrServiceResource{}

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

func TestAccCustomCertSignalrService_requiresImport(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_signalr_service_custom_certificate", "test")
r := CustomCertSignalrServiceResource{}

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

func (r CustomCertSignalrServiceResource) basic(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}
data "azurerm_client_config" "current" {
}
resource "azurerm_resource_group" "test" {
name = "acctestRG-%d"
location = "%s"
}
resource "azurerm_signalr_service" "test" {
name = "acctestSignalR-%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
sku {
name = "Premium_P1"
capacity = 1
}
identity {
type = "SystemAssigned"
}
}
resource "azurerm_key_vault" "test" {
name = "acctestkeyvault%s"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
soft_delete_retention_days = 7
access_policy {
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = data.azurerm_client_config.current.object_id
certificate_permissions = [
"Create",
"Delete",
"Get",
"Import",
"Purge",
"Recover",
"Update",
"List",
]
secret_permissions = [
"Get",
"Set",
]
}
access_policy {
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = azurerm_signalr_service.test.identity[0].principal_id
certificate_permissions = [
"Create",
"Delete",
"Get",
"Import",
"Purge",
"Recover",
"Update",
"List",
]
secret_permissions = [
"Get",
"Set",
]
}
}
resource "azurerm_key_vault_certificate" "test" {
name = "acctestcert%s"
key_vault_id = azurerm_key_vault.test.id
certificate {
contents = filebase64("testdata/certificate-to-import.pfx")
password = ""
}
}
resource "azurerm_signalr_service_custom_certificate" "test" {
name = "signalr-cert-%s"
signalr_service_id = azurerm_signalr_service.test.id
custom_certificate_id = azurerm_key_vault_certificate.test.id
depends_on = [azurerm_key_vault.test]
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomString, data.RandomString, data.RandomString)
}

func (r CustomCertSignalrServiceResource) requiresImport(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
resource "azurerm_signalr_service_custom_certificate" "import" {
name = azurerm_signalr_service_custom_certificate.test.name
signalr_service_id = azurerm_signalr_service_custom_certificate.test.signalr_service_id
custom_certificate_id = azurerm_signalr_service_custom_certificate.test.custom_certificate_id
}
`, r.basic(data))
}

func (r CustomCertSignalrServiceResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) {
id, err := signalr.ParseCustomCertificateID(state.ID)
if err != nil {
return nil, err
}
resp, err := client.SignalR.SignalRClient.CustomCertificatesGet(ctx, *id)
if err != nil {
if response.WasNotFound(resp.HttpResponse) {
return utils.Bool(false), nil
}
return nil, fmt.Errorf("retrieving %s: %+v", *id, err)
}
return utils.Bool(true), nil
}
Loading

0 comments on commit e236dd2

Please sign in to comment.