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 resources: azurerm_log_analytics_cluster and azurerm_log_analytics_cluster_customer_managed_key #8946

Merged
merged 53 commits into from
Nov 17, 2020
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
72be377
Initial checkin wait on sub list
WodansSon Oct 19, 2020
9309d66
Initial checkin of the resource
WodansSon Oct 24, 2020
688b49c
Correcting schema updates
WodansSon Oct 25, 2020
34b7228
Working resource
WodansSon Oct 26, 2020
5da7caa
Updates and tests
WodansSon Oct 27, 2020
3b4d10e
Terrafmt
WodansSon Oct 27, 2020
8578765
Terrafmt
WodansSon Oct 27, 2020
3490a30
Merge branch 'master' into nr_logAnalytics_cluster
WodansSon Oct 27, 2020
5ca15d4
Update azurerm/internal/services/loganalytics/client/client.go
WodansSon Nov 5, 2020
e821b39
Updates per PR review...
WodansSon Nov 5, 2020
108db93
Merge branch 'nr_logAnalytics_storageInsightConfigs' of https://githu…
WodansSon Nov 5, 2020
2cec366
Merge branch 'master' of https://github.com/terraform-providers/terra…
WodansSon Nov 5, 2020
68676d7
Fix test lint errors
WodansSon Nov 5, 2020
248d000
rename var in parse to match resource
jackofallops Nov 5, 2020
7bca8cd
use storage account id validation from storage package
jackofallops Nov 5, 2020
4911148
update to new ID pattern
jackofallops Nov 5, 2020
d2a1f3d
terrafmt log analytics docs
jackofallops Nov 5, 2020
3ba8c3a
review updates and fixes
jackofallops Nov 5, 2020
777938f
case sensitivity back into id paths
jackofallops Nov 5, 2020
9121432
further review updates
jackofallops Nov 5, 2020
631b019
ToLower'ed WorkspaceId
WodansSon Nov 5, 2020
7765b5c
Merge branch 'master' of https://github.com/terraform-providers/terra…
WodansSon Nov 5, 2020
1ea6577
Merge branch 'nr_logAnalytics_storageInsightConfigs' of https://githu…
WodansSon Nov 5, 2020
aef745a
Update schema
WodansSon Nov 11, 2020
b20be14
Finish Test Cases
WodansSon Nov 12, 2020
7f9154f
Update test names
WodansSon Nov 12, 2020
b90c49f
Update test cases
WodansSon Nov 12, 2020
1ca342c
Merge branch 'master' of https://github.com/terraform-providers/terra…
WodansSon Nov 12, 2020
a229c2f
Fix test lint issue
WodansSon Nov 12, 2020
7fb34d9
Another lint issue
WodansSon Nov 12, 2020
c83f745
Add nolint exception for R001
WodansSon Nov 12, 2020
f342d34
Terrafmt
WodansSon Nov 12, 2020
80bbf10
Update test
WodansSon Nov 12, 2020
810edc7
Updates per PR comments
WodansSon Nov 12, 2020
fc5d161
Added cluster resize test case
WodansSon Nov 12, 2020
9430a46
Update docs to reflect new behavior
WodansSon Nov 12, 2020
c586d01
Add key property check again
WodansSon Nov 13, 2020
ceaf5eb
Merge branch 'master' of https://github.com/terraform-providers/terra…
WodansSon Nov 13, 2020
1d10ed4
Add test cases for suppress function
WodansSon Nov 13, 2020
caf0a7d
reworked for meta resource for CMK
jackofallops Nov 13, 2020
03e1690
fix test config
jackofallops Nov 13, 2020
6670164
fix validation for id in CMK
jackofallops Nov 16, 2020
2cbfbdb
make fmt
jackofallops Nov 16, 2020
4e0b3ab
website link typo
jackofallops Nov 16, 2020
c5e51ce
fix panic
jackofallops Nov 16, 2020
4a4a141
rationalise for when API response contains keyvault tcp port
jackofallops Nov 16, 2020
00980c9
import passthrough for CMK meta resouce
jackofallops Nov 16, 2020
3afc65b
LRO wait in create, missed setting cluster_id in read for import
jackofallops Nov 16, 2020
4eba6d5
refactor poller for multiple timeouts
jackofallops Nov 16, 2020
be477de
fix crash in checkDestroy
jackofallops Nov 16, 2020
06952df
Update CustomerManagedKey test case
WodansSon Nov 17, 2020
deadd59
review changes
jackofallops Nov 17, 2020
c93f10f
update TC config for lognalytics tests
jackofallops Nov 17, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions azurerm/helpers/azure/key_vault_child.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ type KeyVaultChildID struct {
Version string
}

func NewKeyVaultChildResourceID(keyVaultBaseUrl, childType, name, version string) (string, error) {
fmtString := "%s/%s/%s/%s"
keyVaultUrl, err := url.Parse(keyVaultBaseUrl)
if err != nil || keyVaultBaseUrl == "" {
return "", fmt.Errorf("failed to parse Key Vault Base URL %q: %+v", keyVaultBaseUrl, err)
}
// (@jackofallops) - Log Analytics service adds the port number to the API returns, so we strip it here
if hostParts := strings.Split(keyVaultUrl.Host, ":"); len(hostParts) > 1 {
keyVaultUrl.Host = hostParts[0]
}

return fmt.Sprintf(fmtString, keyVaultUrl.String(), childType, name, version), nil
}

func ParseKeyVaultChildID(id string) (*KeyVaultChildID, error) {
// example: https://tharvey-keyvault.vault.azure.net/type/bird/fdf067c93bbb4b22bff4d8b7a9a56217
idURL, err := url.ParseRequestURI(id)
Expand Down
47 changes: 46 additions & 1 deletion azurerm/helpers/azure/key_vault_child_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package azure

import "testing"
import (
"testing"
)

func TestAccAzureRMValidateKeyVaultChildID(t *testing.T) {
cases := []struct {
Expand Down Expand Up @@ -320,3 +322,46 @@ func TestAccAzureRMKeyVaultChild_validateName(t *testing.T) {
}
}
}

func TestNewKeyVaultChildResourceID(t *testing.T) {
childType := "keys"
childName := "test"
childVersion := "testVersionString"
cases := []struct {
Scenario string
keyVaultBaseUrl string
Expected string
ExpectError bool
}{
{
Scenario: "empty values",
keyVaultBaseUrl: "",
Expected: "",
ExpectError: true,
},
{
Scenario: "valid, no port",
keyVaultBaseUrl: "https://test.vault.azure.net",
Expected: "https://test.vault.azure.net/keys/test/testVersionString",
ExpectError: false,
},
{
Scenario: "valid, with port",
keyVaultBaseUrl: "https://test.vault.azure.net:443",
Expected: "https://test.vault.azure.net/keys/test/testVersionString",
ExpectError: false,
},
}
for _, tc := range cases {
id, err := NewKeyVaultChildResourceID(tc.keyVaultBaseUrl, childType, childName, childVersion)
if err != nil {
if !tc.ExpectError {
t.Fatalf("Got error for New Resource ID '%s': %+v", tc.keyVaultBaseUrl, err)
return
}
}
if id != tc.Expected {
t.Fatalf("Expected id for %q to be %q, got %q", tc.keyVaultBaseUrl, tc.Expected, id)
}
}
}
5 changes: 5 additions & 0 deletions azurerm/internal/services/loganalytics/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
)

type Client struct {
ClusterClient *operationalinsights.ClustersClient
DataExportClient *operationalinsights.DataExportsClient
DataSourcesClient *operationalinsights.DataSourcesClient
LinkedServicesClient *operationalinsights.LinkedServicesClient
Expand All @@ -19,6 +20,9 @@ type Client struct {
}

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

DataExportClient := operationalinsights.NewDataExportsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&DataExportClient.Client, o.ResourceManagerAuthorizer)

Expand Down Expand Up @@ -47,6 +51,7 @@ func NewClient(o *common.ClientOptions) *Client {
o.ConfigureClient(&LinkedStorageAccountClient.Client, o.ResourceManagerAuthorizer)

return &Client{
ClusterClient: &ClusterClient,
DataExportClient: &DataExportClient,
DataSourcesClient: &DataSourcesClient,
LinkedServicesClient: &LinkedServicesClient,
Expand Down
46 changes: 46 additions & 0 deletions azurerm/internal/services/loganalytics/log_analytics_cluster.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package loganalytics

import (
"context"
"fmt"
"log"
"time"

"github.com/Azure/azure-sdk-for-go/services/preview/operationalinsights/mgmt/2020-03-01-preview/operationalinsights"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients"
)

func logAnalyticsClusterWaitForState(ctx context.Context, meta interface{}, timeout time.Duration, resourceGroup string, clusterName string) *resource.StateChangeConf {
return &resource.StateChangeConf{
Pending: []string{string(operationalinsights.Updating)},
Target: []string{string(operationalinsights.Succeeded)},
MinTimeout: 1 * time.Minute,
Timeout: timeout,
Refresh: logAnalyticsClusterRefresh(ctx, meta, resourceGroup, clusterName),
}
}

func logAnalyticsClusterRefresh(ctx context.Context, meta interface{}, resourceGroup string, clusterName string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
client := meta.(*clients.Client).LogAnalytics.ClusterClient

log.Printf("[INFO] checking on state of Log Analytics Cluster %q", clusterName)

resp, err := client.Get(ctx, resourceGroup, clusterName)
if err != nil {
return nil, "nil", fmt.Errorf("polling for the status of Log Analytics Cluster %q (Resource Group %q): %v", clusterName, resourceGroup, err)
}

if resp.ClusterProperties != nil {
if resp.ClusterProperties.ProvisioningState != operationalinsights.Updating && resp.ClusterProperties.ProvisioningState != operationalinsights.Succeeded {
return nil, "nil", fmt.Errorf("Log Analytics Cluster %q (Resource Group %q) unexpected Provisioning State encountered: %q", clusterName, resourceGroup, string(resp.ClusterProperties.ProvisioningState))
}

return resp, string(resp.ClusterProperties.ProvisioningState), nil
}

// I am not returning an error here as this might have just been a bad get
return resp, "nil", nil
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package loganalytics

import (
"fmt"
"log"
"strings"
"time"

"github.com/Azure/azure-sdk-for-go/services/preview/operationalinsights/mgmt/2020-03-01-preview/operationalinsights"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"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/services/loganalytics/parse"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/loganalytics/validate"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

func resourceArmLogAnalyticsClusterCustomerManagedKey() *schema.Resource {
return &schema.Resource{
Create: resourceArmLogAnalyticsClusterCustomerManagedKeyCreate,
Read: resourceArmLogAnalyticsClusterCustomerManagedKeyRead,
Update: resourceArmLogAnalyticsClusterCustomerManagedKeyUpdate,
Delete: resourceArmLogAnalyticsClusterCustomerManagedKeyDelete,

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

Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

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

"key_vault_key_id": {
Type: schema.TypeString,
Required: true,
ValidateFunc: azure.ValidateKeyVaultChildIdVersionOptional,
},
},
}
}

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

clusterIdRaw := d.Get("log_analytics_cluster_id").(string)
clusterId, err := parse.LogAnalyticsClusterID(clusterIdRaw)
if err != nil {
return err
}

resp, err := client.Get(ctx, clusterId.ResourceGroup, clusterId.Name)
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
return fmt.Errorf("Log Analytics Cluster %q (resource group %q) was not found", clusterId.Name, clusterId.ResourceGroup)
}
return fmt.Errorf("failed to get details of Log Analytics Cluster %q (resource group %q): %+v", clusterId.Name, clusterId.ResourceGroup, err)
}
if resp.ClusterProperties != nil && resp.ClusterProperties.KeyVaultProperties != nil {
keyProps := *resp.ClusterProperties.KeyVaultProperties
if keyProps.KeyName != nil && *keyProps.KeyName != "" {
return tf.ImportAsExistsError("azurerm_log_analytics_cluster_customer_managed_key", fmt.Sprintf("%s/CMK", clusterIdRaw))
}
}

d.SetId(fmt.Sprintf("%s/CMK", clusterIdRaw))
return resourceArmLogAnalyticsClusterCustomerManagedKeyUpdate(d, meta)
}

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

keyId, err := azure.ParseKeyVaultChildIDVersionOptional(d.Get("key_vault_key_id").(string))
if err != nil {
return fmt.Errorf("could not parse Key Vault Key ID: %+v", err)
}

clusterId, err := parse.LogAnalyticsClusterID(d.Get("log_analytics_cluster_id").(string))
if err != nil {
return err
}

clusterPatch := operationalinsights.ClusterPatch{
ClusterPatchProperties: &operationalinsights.ClusterPatchProperties{
KeyVaultProperties: &operationalinsights.KeyVaultProperties{
KeyVaultURI: utils.String(keyId.KeyVaultBaseUrl),
KeyName: utils.String(keyId.Name),
KeyVersion: utils.String(keyId.Version),
},
},
}

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

updateWait := logAnalyticsClusterWaitForState(ctx, meta, d.Timeout(schema.TimeoutUpdate), clusterId.ResourceGroup, clusterId.Name)

if _, err := updateWait.WaitForState(); err != nil {
return fmt.Errorf("waiting for Log Analytics Cluster to finish updating %q (Resource Group %q): %v", clusterId.Name, clusterId.ResourceGroup, err)
}

return resourceArmLogAnalyticsClusterCustomerManagedKeyRead(d, meta)
}

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

idRaw := strings.TrimRight(d.Id(), "/CMK")

id, err := parse.LogAnalyticsClusterID(idRaw)
if err != nil {
return err
}

d.Set("log_analytics_cluster_id", idRaw)

resp, err := client.Get(ctx, id.ResourceGroup, id.Name)
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
log.Printf("[INFO] Log Analytics %q does not exist - removing from state", d.Id())
d.SetId("")
return nil
}
return fmt.Errorf("retrieving Log Analytics Cluster %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err)
}

if props := resp.ClusterProperties; props != nil {
if kvProps := props.KeyVaultProperties; kvProps != nil {
var keyVaultUri, keyName, keyVersion string
if kvProps.KeyVaultURI != nil && *kvProps.KeyVaultURI != "" {
keyVaultUri = *kvProps.KeyVaultURI
} else {
return fmt.Errorf("empty value returned for Key Vault URI")
}
if kvProps.KeyName != nil && *kvProps.KeyName != "" {
keyName = *kvProps.KeyName
} else {
return fmt.Errorf("empty value returned for Key Vault Key Name")
}
if kvProps.KeyVersion != nil {
keyVersion = *kvProps.KeyVersion
}
keyVaultKeyId, err := azure.NewKeyVaultChildResourceID(keyVaultUri, "keys", keyName, keyVersion)
if err != nil {
return err
}
d.Set("key_vault_key_id", keyVaultKeyId)
}
}

return nil
}

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

clusterId, err := parse.LogAnalyticsClusterID(d.Get("log_analytics_cluster_id").(string))
if err != nil {
return err
}

clusterPatch := operationalinsights.ClusterPatch{
ClusterPatchProperties: &operationalinsights.ClusterPatchProperties{
KeyVaultProperties: &operationalinsights.KeyVaultProperties{
KeyVaultURI: nil,
KeyName: nil,
KeyVersion: nil,
},
},
}

_, err = client.Update(ctx, clusterId.ResourceGroup, clusterId.Name, clusterPatch)
if err != nil {
jackofallops marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("removing Log Analytics Cluster Customer Managed Key from cluster %q (resource group %q)", clusterId.Name, clusterId.ResourceGroup)
}

deleteWait := logAnalyticsClusterWaitForState(ctx, meta, d.Timeout(schema.TimeoutDelete), clusterId.ResourceGroup, clusterId.Name)

if _, err := deleteWait.WaitForState(); err != nil {
return fmt.Errorf("waiting for Log Analytics Cluster to finish updating %q (Resource Group %q): %v", clusterId.Name, clusterId.ResourceGroup, err)
}

return nil
}