-
Notifications
You must be signed in to change notification settings - Fork 4.6k
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
Changes from 13 commits
d094c11
a723ed1
9949171
2d947f6
806292c
ab1487f
0eda876
f81fa6f
c201418
ce1fcff
2d3a247
56a9600
4d50408
1a05691
c296f72
63ec3c2
885f6da
d82fe48
42e8231
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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, | ||
} | ||
} |
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, | ||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be using the Resource ID formatter's ID here: There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} |
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 | ||
} |
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) | ||
} | ||
} | ||
} |
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(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. updated |
||
} | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated