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_signalr_service_custom_certificate #21112

Merged
merged 13 commits into from
Apr 1, 2023
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