Skip to content

Commit

Permalink
Merge pull request #14354 from myc2h6o/iothub_identity
Browse files Browse the repository at this point in the history
`r\iothub`: Support managed service identity
  • Loading branch information
tombuildsstuff authored Dec 20, 2021
2 parents bb9aa61 + 44df39d commit 190c4e0
Show file tree
Hide file tree
Showing 3 changed files with 329 additions and 1 deletion.
77 changes: 76 additions & 1 deletion internal/services/iothub/iothub_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ import (
"time"

"github.com/Azure/azure-sdk-for-go/services/iothub/mgmt/2021-03-31/devices"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema"
"github.com/hashicorp/go-azure-helpers/resourcemanager/identity"
"github.com/hashicorp/terraform-provider-azurerm/helpers/azure"
"github.com/hashicorp/terraform-provider-azurerm/helpers/tf"
"github.com/hashicorp/terraform-provider-azurerm/helpers/validate"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
"github.com/hashicorp/terraform-provider-azurerm/internal/locks"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/iothub/parse"
iothubValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/iothub/validate"
msiparse "github.com/hashicorp/terraform-provider-azurerm/internal/services/msi/parse"
"github.com/hashicorp/terraform-provider-azurerm/internal/tags"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/suppress"
Expand Down Expand Up @@ -538,6 +541,8 @@ func resourceIotHub() *pluginsdk.Resource {
Computed: true,
},

"identity": commonschema.SystemAssignedUserAssignedIdentity(),

"tags": tags.Schema(),
},
}
Expand Down Expand Up @@ -608,6 +613,12 @@ func resourceIotHubCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) err
cloudToDeviceProperties = expandIoTHubCloudToDevice(d)
}

identityRaw := d.Get("identity").([]interface{})
identity, err := expandIotHubIdentity(identityRaw)
if err != nil {
return fmt.Errorf("expanding `identity`: %+v", err)
}

props := devices.IotHubDescription{
Name: utils.String(id.Name),
Location: utils.String(azure.NormalizeLocation(d.Get("location").(string))),
Expand All @@ -620,7 +631,8 @@ func resourceIotHubCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) err
EnableFileUploadNotifications: &enableFileUploadNotifications,
CloudToDevice: cloudToDeviceProperties,
},
Tags: tags.Expand(d.Get("tags").(map[string]interface{})),
Identity: identity,
Tags: tags.Expand(d.Get("tags").(map[string]interface{})),
}

// nolint staticcheck
Expand Down Expand Up @@ -767,6 +779,14 @@ func resourceIotHubRead(d *pluginsdk.ResourceData, meta interface{}) error {
d.Set("min_tls_version", properties.MinTLSVersion)
}

identity, err := flattenIotHubIdentity(hub.Identity)
if err != nil {
return err
}
if err := d.Set("identity", identity); err != nil {
return fmt.Errorf("setting `identity`: %+v", err)
}

d.Set("name", id.Name)
d.Set("resource_group_name", id.ResourceGroup)
if location := hub.Location; location != nil {
Expand Down Expand Up @@ -1422,6 +1442,61 @@ func flattenIPFilterRules(in *[]devices.IPFilterRule) []interface{} {
return rules
}

func expandIotHubIdentity(input []interface{}) (*devices.ArmIdentity, error) {
config, err := identity.ExpandSystemAndUserAssignedMap(input)
if err != nil {
return nil, err
}

identity := devices.ArmIdentity{
Type: devices.ResourceIdentityType(config.Type),
}

if len(config.IdentityIds) != 0 {
identityIds := make(map[string]*devices.ArmUserIdentity, len(config.IdentityIds))
for id := range config.IdentityIds {
identityIds[id] = &devices.ArmUserIdentity{}
}
identity.UserAssignedIdentities = identityIds
}

return &identity, nil
}

func flattenIotHubIdentity(input *devices.ArmIdentity) (*[]interface{}, error) {
var config *identity.SystemAndUserAssignedMap
if input != nil {
identityIds := map[string]identity.UserAssignedIdentityDetails{}
for id := range input.UserAssignedIdentities {
parsedId, err := msiparse.UserAssignedIdentityIDInsensitively(id)
if err != nil {
return nil, err
}
identityIds[parsedId.ID()] = identity.UserAssignedIdentityDetails{
// intentionally empty
}
}

principalId := ""
if input.PrincipalID != nil {
principalId = *input.PrincipalID
}

tenantId := ""
if input.TenantID != nil {
tenantId = *input.TenantID
}

config = &identity.SystemAndUserAssignedMap{
Type: identity.Type(string(input.Type)),
PrincipalId: principalId,
TenantId: tenantId,
IdentityIds: identityIds,
}
}
return identity.FlattenSystemAndUserAssignedMap(config)
}

func fileUploadConnectionStringDiffSuppress(k, old, new string, d *pluginsdk.ResourceData) bool {
// The access keys are always masked by Azure and the ordering of the parameters in the connection string
// differs across services, so we will compare the fields individually instead.
Expand Down
233 changes: 233 additions & 0 deletions internal/services/iothub/iothub_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,101 @@ func TestAccIotHub_cloudToDevice(t *testing.T) {
})
}

func TestAccIotHub_identitySystemAssigned(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_iothub", "test")
r := IotHubResource{}

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

func TestAccIotHub_identitySystemAssignedUserAssigned(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_iothub", "test")
r := IotHubResource{}

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

func TestAccIotHub_identityUserAssigned(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_iothub", "test")
r := IotHubResource{}

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

func TestAccIotHub_identityUpdate(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_iothub", "test")
r := IotHubResource{}

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

func (t IotHubResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) {
id, err := parse.IotHubID(state.ID)
if err != nil {
Expand Down Expand Up @@ -1084,3 +1179,141 @@ resource "azurerm_iothub" "test" {
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger)
}

func (IotHubResource) identitySystemAssigned(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "test" {
name = "acctestRG-iothub-%d"
location = "%s"
}
resource "azurerm_iothub" "test" {
name = "acctestIoTHub-%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
sku {
name = "B1"
capacity = "1"
}
identity {
type = "SystemAssigned"
}
tags = {
purpose = "testing"
}
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger)
}

func (IotHubResource) identitySystemAssignedUserAssigned(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "test" {
name = "acctestRG-iothub-%d"
location = "%s"
}
resource "azurerm_user_assigned_identity" "test" {
name = "acctestuai-%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
}
resource "azurerm_iothub" "test" {
name = "acctestIoTHub-%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
sku {
name = "B1"
capacity = "1"
}
identity {
type = "SystemAssigned, UserAssigned"
identity_ids = [
azurerm_user_assigned_identity.test.id,
]
}
tags = {
purpose = "testing"
}
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger)
}

func (IotHubResource) identityUserAssigned(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "test" {
name = "acctestRG-iothub-%d"
location = "%s"
}
resource "azurerm_user_assigned_identity" "test" {
name = "acctestuai-%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
}
resource "azurerm_iothub" "test" {
name = "acctestIoTHub-%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
sku {
name = "B1"
capacity = "1"
}
identity {
type = "UserAssigned"
identity_ids = [
azurerm_user_assigned_identity.test.id,
]
}
tags = {
purpose = "testing"
}
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger)
}

func (IotHubResource) identityUserAssignedUpdated(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "test" {
name = "acctestRG-iothub-%d"
location = "%s"
}
resource "azurerm_user_assigned_identity" "test" {
name = "acctestuai-%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
}
resource "azurerm_user_assigned_identity" "other" {
name = "acctestuai2-%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
}
resource "azurerm_iothub" "test" {
name = "acctestIoTHub-%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
sku {
name = "B1"
capacity = "1"
}
identity {
type = "UserAssigned"
identity_ids = [
azurerm_user_assigned_identity.test.id,
azurerm_user_assigned_identity.other.id,
]
}
tags = {
purpose = "testing"
}
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger)
}
Loading

0 comments on commit 190c4e0

Please sign in to comment.