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

azurerm_stack_hci_cluster - support for identity, export cloud_id, service_endpoint, resource_provider_object_id #25031

Merged
merged 6 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-helpers/lang/response"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema"
"github.com/hashicorp/go-azure-helpers/resourcemanager/identity"
"github.com/hashicorp/go-azure-helpers/resourcemanager/location"
"github.com/hashicorp/go-azure-helpers/resourcemanager/tags"
"github.com/hashicorp/go-azure-sdk/resource-manager/azurestackhci/2023-08-01/clusters"
Expand All @@ -35,13 +36,17 @@ func (r StackHCIClusterDataSource) ModelObject() interface{} {
}

type StackHCIClusterDataSourceModel struct {
Name string `tfschema:"name"`
ResourceGroupName string `tfschema:"resource_group_name"`
Location string `tfschema:"location"`
ClientId string `tfschema:"client_id"`
TenantId string `tfschema:"tenant_id"`
AutomanageConfigurationId string `tfschema:"automanage_configuration_id"`
Tags map[string]interface{} `tfschema:"tags"`
Name string `tfschema:"name"`
ResourceGroupName string `tfschema:"resource_group_name"`
Location string `tfschema:"location"`
ClientId string `tfschema:"client_id"`
TenantId string `tfschema:"tenant_id"`
AutomanageConfigurationId string `tfschema:"automanage_configuration_id"`
CloudId string `tfschema:"cloud_id"`
ServiceEndpoint string `tfschema:"service_endpoint"`
ResourceProviderObjectId string `tfschema:"resource_provider_object_id"`
Identity []identity.ModelSystemAssigned `tfschema:"identity"`
Tags map[string]interface{} `tfschema:"tags"`
}

func (r StackHCIClusterDataSource) Arguments() map[string]*schema.Schema {
Expand Down Expand Up @@ -75,6 +80,23 @@ func (r StackHCIClusterDataSource) Attributes() map[string]*schema.Schema {
Computed: true,
},

"cloud_id": {
Type: pluginsdk.TypeString,
Computed: true,
},

"service_endpoint": {
Type: pluginsdk.TypeString,
Computed: true,
},

"resource_provider_object_id": {
Type: pluginsdk.TypeString,
Computed: true,
},

"identity": commonschema.SystemAssignedIdentityComputed(),

"tags": commonschema.TagsDataSource(),
}
}
Expand Down Expand Up @@ -108,6 +130,10 @@ func (r StackHCIClusterDataSource) Read() sdk.ResourceFunc {

if props := model.Properties; props != nil {
cluster.ClientId = pointer.From(props.AadClientId)
cluster.CloudId = pointer.From(props.CloudId)
cluster.Identity = flattenSystemAssignedToModel(model.Identity)
cluster.ResourceProviderObjectId = pointer.From(props.ResourceProviderObjectId)
cluster.ServiceEndpoint = pointer.From(props.ServiceEndpoint)
cluster.TenantId = pointer.From(props.AadTenantId)

assignmentResp, err := hciAssignmentClient.Get(ctx, id.ResourceGroupName, id.ClusterName, "default")
Expand All @@ -132,3 +158,21 @@ func (r StackHCIClusterDataSource) Read() sdk.ResourceFunc {
},
}
}

func flattenSystemAssignedToModel(input *identity.SystemAndUserAssignedMap) []identity.ModelSystemAssigned {
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason we can't use the existing flatten functions in the identity package?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The service API only supports systemAssigned Identity, which means there should be no identity_ids field in the tf schema, but the swagger and SDK uses identity.SystemAndUserAssignedMap, so need mannually skip the identity_ids in expand/flatten function.

Copy link
Member

Choose a reason for hiding this comment

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

Then we should raise an issue on the swagger since the definition doesn't align with the API behaviour and link it here. Can you please do that?

Copy link
Contributor Author

@teowa teowa Mar 11, 2024

Choose a reason for hiding this comment

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

change this in swagger would be considered as breaking change, and from service team the user assigned identity will be supported in the future, can we leave it for now, and remove this when user assigned identity is supported later.

Copy link
Member

Choose a reason for hiding this comment

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

@teowa we should always raise an issue on the swagger spec when what is defined in the spec does not correspond to the behaviour of the API, which is the case here. Whether the service team actually make the breaking change to fix it or not is another question, this is about tracking for when the service is brought in line. Please open an issue on the swagger spec and link it here, like we do for all other discrepancies.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@stephybun thanks for clarifying , I thought I need to modify the swagger. The issue has been raised: Azure/azure-rest-api-specs#28260

if input == nil {
return []identity.ModelSystemAssigned{}
}

if input.Type == identity.TypeNone {
return []identity.ModelSystemAssigned{}
}

return []identity.ModelSystemAssigned{
{
Type: input.Type,
PrincipalId: input.PrincipalId,
TenantId: input.TenantId,
},
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,25 @@ func TestAccStackHCIClusterDataSource_basic(t *testing.T) {
check.That(data.ResourceName).Key("location").IsNotEmpty(),
check.That(data.ResourceName).Key("client_id").IsNotEmpty(),
check.That(data.ResourceName).Key("tenant_id").IsNotEmpty(),
check.That(data.ResourceName).Key("cloud_id").IsNotEmpty(),
check.That(data.ResourceName).Key("service_endpoint").IsNotEmpty(),
check.That(data.ResourceName).Key("resource_provider_object_id").IsNotEmpty(),
),
},
})
}

func TestAccStackHCIClusterDataSource_identity(t *testing.T) {
data := acceptance.BuildTestData(t, "data.azurerm_stack_hci_cluster", "test")
r := StackHCIClusterDataSource{}

data.DataSourceTest(t, []acceptance.TestStep{
{
Config: r.identity(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"),
check.That(data.ResourceName).Key("identity.0.principal_id").IsNotEmpty(),
check.That(data.ResourceName).Key("identity.0.tenant_id").IsNotEmpty(),
),
},
})
Expand All @@ -39,3 +58,14 @@ data "azurerm_stack_hci_cluster" "test" {
}
`, StackHCIClusterResource{}.basic(data))
}

func (d StackHCIClusterDataSource) identity(data acceptance.TestData) string {
return fmt.Sprintf(`
%s

data "azurerm_stack_hci_cluster" "test" {
name = azurerm_stack_hci_cluster.test.name
resource_group_name = azurerm_stack_hci_cluster.test.resource_group_name
}
`, StackHCIClusterResource{}.systemAssignedIdentity(data))
}
62 changes: 61 additions & 1 deletion internal/services/azurestackhci/stack_hci_cluster_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/hashicorp/go-azure-helpers/lang/response"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema"
"github.com/hashicorp/go-azure-helpers/resourcemanager/identity"
"github.com/hashicorp/go-azure-helpers/resourcemanager/location"
"github.com/hashicorp/go-azure-helpers/resourcemanager/tags"
"github.com/hashicorp/go-azure-sdk/resource-manager/azurestackhci/2023-08-01/clusters"
Expand Down Expand Up @@ -77,6 +78,23 @@ func resourceArmStackHCICluster() *pluginsdk.Resource {
ValidateFunc: autoVal.AutomanageConfigurationID,
},

"cloud_id": {
Type: pluginsdk.TypeString,
Computed: true,
},

"service_endpoint": {
Type: pluginsdk.TypeString,
Computed: true,
},

"resource_provider_object_id": {
Type: pluginsdk.TypeString,
Computed: true,
},

"identity": commonschema.SystemAssignedIdentityOptional(),

"tags": commonschema.Tags(),
},
}
Expand Down Expand Up @@ -108,6 +126,10 @@ func resourceArmStackHCIClusterCreate(d *pluginsdk.ResourceData, meta interface{
Tags: tags.Expand(d.Get("tags").(map[string]interface{})),
}

if v, ok := d.GetOk("identity"); ok {
cluster.Identity = expandSystemAssigned(v.([]interface{}))
}

if v, ok := d.GetOk("tenant_id"); ok {
cluster.Properties.AadTenantId = utils.String(v.(string))
} else {
Expand Down Expand Up @@ -185,10 +207,14 @@ func resourceArmStackHCIClusterRead(d *pluginsdk.ResourceData, meta interface{})

if model := resp.Model; model != nil {
d.Set("location", location.Normalize(model.Location))
d.Set("identity", flattenSystemAssigned(model.Identity))

if props := model.Properties; props != nil {
d.Set("client_id", props.AadClientId)
d.Set("tenant_id", props.AadTenantId)
d.Set("cloud_id", props.CloudId)
d.Set("service_endpoint", props.ServiceEndpoint)
d.Set("resource_provider_object_id", props.ResourceProviderObjectId)

assignmentResp, err := hciAssignmentClient.Get(ctx, id.ResourceGroupName, id.ClusterName, "default")
if err != nil && !utils.ResponseWasNotFound(assignmentResp.Response) {
Expand Down Expand Up @@ -231,6 +257,10 @@ func resourceArmStackHCIClusterUpdate(d *pluginsdk.ResourceData, meta interface{
cluster.Tags = tags.Expand(d.Get("tags").(map[string]interface{}))
}

if d.HasChange("identity") {
cluster.Identity = expandSystemAssigned(d.Get("identity").([]interface{}))
}

if _, err := client.Update(ctx, *id, cluster); err != nil {
return fmt.Errorf("updating %s: %+v", *id, err)
}
Expand Down Expand Up @@ -274,7 +304,6 @@ func resourceArmStackHCIClusterUpdate(d *pluginsdk.ResourceData, meta interface{
}
}
}

}

return resourceArmStackHCIClusterRead(d, meta)
Expand Down Expand Up @@ -308,3 +337,34 @@ func resourceArmStackHCIClusterDelete(d *pluginsdk.ResourceData, meta interface{

return nil
}

// API does not accept userAssignedIdentity as in swagger https://github.com/Azure/azure-rest-api-specs/issues/28260
func expandSystemAssigned(input []interface{}) *identity.SystemAndUserAssignedMap {
if len(input) == 0 || input[0] == nil {
return &identity.SystemAndUserAssignedMap{
Type: identity.TypeNone,
}
}

return &identity.SystemAndUserAssignedMap{
Type: identity.TypeSystemAssigned,
}
}

func flattenSystemAssigned(input *identity.SystemAndUserAssignedMap) []interface{} {
if input == nil {
return []interface{}{}
}

if input.Type == identity.TypeNone {
return []interface{}{}
}

return []interface{}{
map[string]interface{}{
"type": input.Type,
"principal_id": input.PrincipalId,
"tenant_id": input.TenantId,
},
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ func TestAccStackHCICluster_basic(t *testing.T) {
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("cloud_id").IsNotEmpty(),
check.That(data.ResourceName).Key("service_endpoint").IsNotEmpty(),
check.That(data.ResourceName).Key("resource_provider_object_id").IsNotEmpty(),
),
},
data.ImportStep(),
Expand All @@ -49,6 +52,53 @@ func TestAccStackHCICluster_requiresImport(t *testing.T) {
})
}

func TestAccStackHCICluster_systemAssignedIdentity(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_stack_hci_cluster", "test")
r := StackHCIClusterResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.systemAssignedIdentity(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"),
check.That(data.ResourceName).Key("identity.0.principal_id").IsNotEmpty(),
check.That(data.ResourceName).Key("identity.0.tenant_id").IsNotEmpty(),
),
},
data.ImportStep(),
})
}

func TestAccStackHCICluster_systemAssignedIdentityUpdate(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_stack_hci_cluster", "test")
r := StackHCIClusterResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.systemAssignedIdentity(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 TestAccStackHCICluster_complete(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_stack_hci_cluster", "test")
r := StackHCIClusterResource{}
Expand Down Expand Up @@ -158,6 +208,24 @@ resource "azurerm_stack_hci_cluster" "import" {
`, config)
}

func (r StackHCIClusterResource) systemAssignedIdentity(data acceptance.TestData) string {
template := r.template(data)
return fmt.Sprintf(`
%s

resource "azurerm_stack_hci_cluster" "test" {
name = "acctest-StackHCICluster-%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
client_id = azuread_application.test.application_id
tenant_id = data.azurerm_client_config.current.tenant_id
identity {
type = "SystemAssigned"
}
}
`, template, data.RandomInteger)
}

func (r StackHCIClusterResource) complete(data acceptance.TestData) string {
template := r.template(data)
return fmt.Sprintf(`
Expand All @@ -169,6 +237,9 @@ resource "azurerm_stack_hci_cluster" "test" {
location = azurerm_resource_group.test.location
client_id = azuread_application.test.application_id
tenant_id = data.azurerm_client_config.current.tenant_id
identity {
type = "SystemAssigned"
}

tags = {
ENV = "Test"
Expand Down
22 changes: 20 additions & 2 deletions website/docs/d/stack_hci_cluster.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,33 @@ In addition to the Arguments listed above - the following Attributes are exporte

* `id` - The ID of the Azure Stack HCI Cluster.

* `location` - The Azure Region where the Azure Stack HCI Cluster exists.
* `automanage_configuration_id` - The ID of the Automanage Configuration assigned to the Azure Stack HCI Cluster.

* `client_id` - The Client ID of the Azure Active Directory used by the Azure Stack HCI Cluster.

* `cloud_id` - An immutable UUID for the Azure Stack HCI Cluster.

* `identity` - An `identity` block as defined below.

* `location` - The Azure Region where the Azure Stack HCI Cluster exists.

* `resource_provider_object_id` - The object ID of the Resource Provider Service Principal.

* `service_endpoint` - The region specific Data Path Endpoint of the Azure Stack HCI Cluster.

* `tenant_id` - The Tenant ID of the Azure Active Directory used by the Azure Stack HCI Cluster.

* `tags` - A mapping of tags assigned to the Azure Stack HCI Cluster.

* `automanage_configuration_id` - The ID of the Automanage Configuration assigned to the Azure Stack HCI Cluster.
---

An `identity` block exports the following:

* `type` - (Required) The type of Managed Service Identity that configured on the Azure Stack HCI Cluster.
teowa marked this conversation as resolved.
Show resolved Hide resolved

* `principal_id` - The Principal ID associated with this Managed Service Identity.

* `tenant_id` - The Tenant ID associated with this Managed Service Identity.

## Timeouts

Expand Down
Loading
Loading