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_hci_cluster #9134

Merged
merged 19 commits into from
Dec 9, 2020
1 change: 1 addition & 0 deletions .teamcity/components/generated/services.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var services = mapOf(
"attestation" to "Attestation",
"authorization" to "Authorization",
"automation" to "Automation",
"azurestackhci" to "Azure Stack HCI",
"batch" to "Batch",
"blueprints" to "Blueprints",
"bot" to "Bot",
Expand Down
3 changes: 3 additions & 0 deletions azurerm/internal/clients/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
attestation "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/attestation/client"
authorization "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/authorization/client"
automation "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/automation/client"
azureStackHCI "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/azurestackhci/client"
batch "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/batch/client"
blueprints "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/blueprints/client"
bot "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/bot/client"
Expand Down Expand Up @@ -105,6 +106,7 @@ type Client struct {
Attestation *attestation.Client
Authorization *authorization.Client
Automation *automation.Client
AzureStackHCI *azureStackHCI.Client
Batch *batch.Client
Blueprints *blueprints.Client
Bot *bot.Client
Expand Down Expand Up @@ -196,6 +198,7 @@ func (client *Client) Build(ctx context.Context, o *common.ClientOptions) error
client.Attestation = attestation.NewClient(o)
client.Authorization = authorization.NewClient(o)
client.Automation = automation.NewClient(o)
client.AzureStackHCI = azureStackHCI.NewClient(o)
client.Batch = batch.NewClient(o)
client.Blueprints = blueprints.NewClient(o)
client.Bot = bot.NewClient(o)
Expand Down
2 changes: 2 additions & 0 deletions azurerm/internal/provider/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/attestation"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/authorization"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/automation"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/azurestackhci"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/batch"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/blueprints"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/bot"
Expand Down Expand Up @@ -98,6 +99,7 @@ func SupportedServices() []common.ServiceRegistration {
attestation.Registration{},
authorization.Registration{},
automation.Registration{},
azurestackhci.Registration{},
batch.Registration{},
blueprints.Registration{},
bot.Registration{},
Expand Down
19 changes: 19 additions & 0 deletions azurerm/internal/services/azurestackhci/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package client

import (
"github.com/Azure/azure-sdk-for-go/services/azurestackhci/mgmt/2020-10-01/azurestackhci"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/common"
)

type Client struct {
ClusterClient *azurestackhci.ClustersClient
}

func NewClient(o *common.ClientOptions) *Client {
clusterClient := azurestackhci.NewClustersClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&clusterClient.Client, o.ResourceManagerAuthorizer)

return &Client{
ClusterClient: &clusterClient,
}
}
189 changes: 189 additions & 0 deletions azurerm/internal/services/azurestackhci/hci_cluster_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package azurestackhci

import (
"fmt"
"log"
"time"

"github.com/Azure/azure-sdk-for-go/services/azurestackhci/mgmt/2020-10-01/azurestackhci"
"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/location"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/azurestackhci/parse"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/azurestackhci/validate"
"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 resourceArmHCICluster() *schema.Resource {
return &schema.Resource{
Create: resourceArmHCIClusterCreate,
Read: resourceArmHCIClusterRead,
Update: resourceArmHCIClusterUpdate,
Delete: resourceArmHCIClusterDelete,

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(30 * time.Minute),
Read: schema.DefaultTimeout(5 * time.Minute),
Update: schema.DefaultTimeout(30 * time.Minute),
Delete: schema.DefaultTimeout(30 * time.Minute),
},

Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error {
_, err := parse.HCIClusterID(id)
return err
}),

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validate.HCIClusterName,
},

"resource_group_name": azure.SchemaResourceGroupName(),

"location": azure.SchemaLocation(),

"client_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.IsUUID,
},

"tenant_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

isn't this statistically likely to be the current Tenant ID, so shouldn't this be Optional?

Copy link
Contributor

Choose a reason for hiding this comment

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

Optional (and Computed*) and ForceNew, actually - since this could be defaulted and overwritten

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated

ValidateFunc: validation.IsUUID,
},

"tags": tags.Schema(),
},
}
}
func resourceArmHCIClusterCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).AzureStackHCI.ClusterClient
ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d)
defer cancel()

name := d.Get("name").(string)
resourceGroup := d.Get("resource_group_name").(string)

existing, err := client.Get(ctx, resourceGroup, name)
if err != nil {
if !utils.ResponseWasNotFound(existing.Response) {
return fmt.Errorf("checking for present of existing HCI Cluster %q (Resource Group %q): %+v", name, resourceGroup, err)
}
}

if existing.ID != nil && *existing.ID != "" {
return tf.ImportAsExistsError("azurerm_hci_cluster", *existing.ID)
Copy link
Contributor

Choose a reason for hiding this comment

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

this should be using the Resource ID formatter's ID here: id.ID("")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated

}

cluster := azurestackhci.Cluster{
Location: utils.String(location.Normalize(d.Get("location").(string))),
ClusterProperties: &azurestackhci.ClusterProperties{
AadClientID: utils.String(d.Get("client_id").(string)),
AadTenantID: utils.String(d.Get("tenant_id").(string)),
},
Tags: tags.Expand(d.Get("tags").(map[string]interface{})),
}

if _, err := client.Create(ctx, resourceGroup, name, cluster); err != nil {
return fmt.Errorf("creating HCI Cluster %q (Resource Group %q): %+v", name, resourceGroup, err)
}

resp, err := client.Get(ctx, resourceGroup, name)
if err != nil {
return fmt.Errorf("retrieving HCI Cluster %q (Resource Group %q): %+v", name, resourceGroup, err)
}

if resp.ID == nil || *resp.ID == "" {
return fmt.Errorf("empty or nil ID returned for HCI Cluster %q (Resource Group %q) ID", name, resourceGroup)
}

d.SetId(*resp.ID)
neil-yechenwei marked this conversation as resolved.
Show resolved Hide resolved

return resourceArmHCIClusterRead(d, meta)
}

func resourceArmHCIClusterRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).AzureStackHCI.ClusterClient
ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
defer cancel()

id, err := parse.HCIClusterID(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("[INFO] HCI Cluster %q does not exist - removing from state", d.Id())
d.SetId("")
return nil
}

return fmt.Errorf("retrieving HCI Cluster %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err)
}

d.Set("name", id.Name)
d.Set("resource_group_name", id.ResourceGroup)
d.Set("location", location.NormalizeNilable(resp.Location))

if props := resp.ClusterProperties; props != nil {
d.Set("client_id", props.AadClientID)
d.Set("tenant_id", props.AadTenantID)
}

return tags.FlattenAndSet(d, resp.Tags)
}

func resourceArmHCIClusterUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).AzureStackHCI.ClusterClient
ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d)
defer cancel()

id, err := parse.HCIClusterID(d.Id())
if err != nil {
return err
}

cluster := azurestackhci.ClusterUpdate{}

if d.HasChange("tags") {
cluster.Tags = tags.Expand(d.Get("tags").(map[string]interface{}))
}

if _, err := client.Update(ctx, id.ResourceGroup, id.Name, cluster); err != nil {
return fmt.Errorf("updating HCI Cluster %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err)
}

return resourceArmHCIClusterRead(d, meta)
}

func resourceArmHCIClusterDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).AzureStackHCI.ClusterClient
ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d)
defer cancel()

id, err := parse.HCIClusterID(d.Id())
if err != nil {
return err
}

if _, err := client.Delete(ctx, id.ResourceGroup, id.Name); err != nil {
return fmt.Errorf("deleting HCI Cluster %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err)
}

return nil
}
33 changes: 33 additions & 0 deletions azurerm/internal/services/azurestackhci/parse/hci_cluster.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package parse
magodo marked this conversation as resolved.
Show resolved Hide resolved

import (
"fmt"

"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure"
)

type HCIClusterId struct {
ResourceGroup string
Name string
}

func HCIClusterID(input string) (*HCIClusterId, error) {
id, err := azure.ParseAzureResourceID(input)
if err != nil {
return nil, fmt.Errorf("parsing hciCluster ID %q: %+v", input, err)
}

hciCluster := HCIClusterId{
ResourceGroup: id.ResourceGroup,
}

if hciCluster.Name, err = id.PopSegment("clusters"); err != nil {
return nil, err
}

if err := id.ValidateNoEmptySegments(input); err != nil {
return nil, err
}

return &hciCluster, nil
}
72 changes: 72 additions & 0 deletions azurerm/internal/services/azurestackhci/parse/hci_cluster_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package parse
neil-yechenwei marked this conversation as resolved.
Show resolved Hide resolved

import (
"testing"
)

func TestHCIClusterID(t *testing.T) {
testData := []struct {
Name string
Input string
Expected *HCIClusterId
}{
{
Name: "Empty",
Input: "",
Expected: nil,
},
{
Name: "No Resource Groups Segment",
Input: "/subscriptions/00000000-0000-0000-0000-000000000000",
Expected: nil,
},
{
Name: "No Resource Groups Value",
Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/",
Expected: nil,
},
{
Name: "Resource Group ID",
Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/",
Expected: nil,
},
{
Name: "Missing HCI Cluster Value",
Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.AzureStackHCI/clusters",
Expected: nil,
},
{
Name: "HCI Cluster ID",
Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.AzureStackHCI/clusters/cluster1",
Expected: &HCIClusterId{
ResourceGroup: "resourceGroup1",
Name: "cluster1",
},
},
{
Name: "Wrong Casing",
Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.AzureStackHCI/Clusters/cluster1",
Expected: nil,
},
}

for _, v := range testData {
t.Logf("[DEBUG] Testing %q..", v.Name)

actual, err := HCIClusterID(v.Input)
if err != nil {
if v.Expected == nil {
continue
}
t.Fatalf("Expected a value but got an error: %s", err)
}

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)
}
}
}
29 changes: 29 additions & 0 deletions azurerm/internal/services/azurestackhci/registration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package azurestackhci

import "github.com/hashicorp/terraform-plugin-sdk/helper/schema"

type Registration struct{}

// Name is the name of this Service
func (r Registration) Name() string {
return "Azure Stack HCI"
}

// WebsiteCategories returns a list of categories which can be used for the sidebar
func (r Registration) WebsiteCategories() []string {
return []string{
"Azure Stack HCI",
}
}

// SupportedDataSources returns the supported Data Sources supported by this Service
func (r Registration) SupportedDataSources() map[string]*schema.Resource {
return map[string]*schema.Resource{}
}

// SupportedResources returns the supported Resources supported by this Service
func (r Registration) SupportedResources() map[string]*schema.Resource {
return map[string]*schema.Resource{
"azurerm_hci_cluster": resourceArmHCICluster(),
Copy link
Contributor

Choose a reason for hiding this comment

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

this name isn't clear this is for Azure Stack HCI directly, should this become azurerm_stack_hci_cluster?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated

}
}
Loading