diff --git a/internal/services/loganalytics/log_analytics_workspace_table_resource.go b/internal/services/loganalytics/log_analytics_workspace_table_resource.go index bee8106afab4..513942cca817 100644 --- a/internal/services/loganalytics/log_analytics_workspace_table_resource.go +++ b/internal/services/loganalytics/log_analytics_workspace_table_resource.go @@ -7,41 +7,89 @@ import ( "context" "fmt" "log" + "strings" "time" "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/go-azure-helpers/lang/response" "github.com/hashicorp/go-azure-sdk/resource-manager/operationalinsights/2022-10-01/tables" "github.com/hashicorp/go-azure-sdk/resource-manager/operationalinsights/2022-10-01/workspaces" + "github.com/hashicorp/terraform-provider-azurerm/internal/features" "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" - "github.com/hashicorp/terraform-provider-azurerm/utils" ) type LogAnalyticsWorkspaceTableResource struct{} var ( - _ sdk.ResourceWithUpdate = LogAnalyticsWorkspaceTableResource{} - _ sdk.ResourceWithCustomizeDiff = LogAnalyticsWorkspaceTableResource{} + _ sdk.ResourceWithUpdate = LogAnalyticsWorkspaceTableResource{} + _ sdk.ResourceWithCustomizeDiff = LogAnalyticsWorkspaceTableResource{} + useWorkspaceDefaultRetention = pointer.To(int64(-1)) ) type LogAnalyticsWorkspaceTableResourceModel struct { - Name string `tfschema:"name"` - WorkspaceId string `tfschema:"workspace_id"` - Plan string `tfschema:"plan"` - RetentionInDays int64 `tfschema:"retention_in_days"` - TotalRetentionInDays int64 `tfschema:"total_retention_in_days"` + WorkspaceId string `tfschema:"workspace_id"` + Name string `tfschema:"name"` + DisplayName string `tfschema:"display_name"` + Description string `tfschema:"description"` + Type string `tfschema:"type"` + SubType string `tfschema:"sub_type"` + Plan string `tfschema:"plan"` + Categories []string `tfschema:"categories"` + Columns []Column `tfschema:"column"` + Labels []string `tfschema:"labels"` + Solutions []string `tfschema:"solutions"` + StandardColumns []Column `tfschema:"standard_column"` + RetentionInDays int64 `tfschema:"retention_in_days"` + TotalRetentionInDays int64 `tfschema:"total_retention_in_days"` +} + +type Column struct { + Name string `tfschema:"name"` + DisplayName string `tfschema:"display_name"` + Description string `tfschema:"description"` + IsHidden bool `tfschema:"hidden"` + IsDefaultDisplay bool `tfschema:"display_by_default"` + Type string `tfschema:"type"` + TypeHint string `tfschema:"type_hint"` } func (r LogAnalyticsWorkspaceTableResource) CustomizeDiff() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { - rd := metadata.ResourceDiff + var table LogAnalyticsWorkspaceTableResourceModel + if err := metadata.DecodeDiff(&table); err != nil { + return err + } - if string(tables.TablePlanEnumBasic) == rd.Get("plan").(string) { - if _, ok := rd.GetOk("retention_in_days"); ok { + switch table.Type { + case string(tables.TableTypeEnumMicrosoft): + if strings.HasSuffix(table.Name, "_CL") { + return fmt.Errorf("name must not end with '_CL' for Microsoft tables") + } + + case string(tables.TableTypeEnumCustomLog): + if !strings.HasSuffix(table.Name, "_CL") { + return fmt.Errorf("name must end with '_CL' for CustomLog tables") + } + if table.SubType == "" { + return fmt.Errorf("sub_type must be set for CustomLog tables") + } + if table.SubType == string(tables.TableSubTypeEnumAny) { + return fmt.Errorf("sub_type cannot be 'Any' for CustomLog tables") + } + } + + for _, column := range table.Columns { + if column.TypeHint != "" && column.Type != string(tables.ColumnTypeEnumString) { + return fmt.Errorf("type_hint can only be set for columns of type 'string'") + } + } + + if table.Plan == string(tables.TablePlanEnumBasic) { + if _, ok := metadata.ResourceDiff.GetOk("retention_in_days"); ok { return fmt.Errorf("cannot set retention_in_days because the retention is fixed at eight days on Basic plan") } } @@ -56,22 +104,80 @@ func (r LogAnalyticsWorkspaceTableResource) Arguments() map[string]*pluginsdk.Sc "workspace_id": { Type: pluginsdk.TypeString, Required: true, + ForceNew: true, ValidateFunc: workspaces.ValidateWorkspaceID, }, "name": { Type: pluginsdk.TypeString, Required: true, + ForceNew: true, + }, + + "display_name": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "description": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "type": { + Type: pluginsdk.TypeString, + Required: features.FivePointOh(), + Optional: !features.FivePointOh(), + ForceNew: true, + ValidateFunc: validation.StringInSlice(tables.PossibleValuesForTableTypeEnum(), false), + Default: func() interface{} { + if !features.FivePointOh() { + return string(tables.TableTypeEnumMicrosoft) + } + return nil + }(), + }, + + "sub_type": { + Type: pluginsdk.TypeString, + Required: features.FivePointOh(), + Optional: !features.FivePointOh(), + Computed: !features.FivePointOh(), + ForceNew: true, + ValidateFunc: validation.StringInSlice(tables.PossibleValuesForTableSubTypeEnum(), false), }, "plan": { - Type: pluginsdk.TypeString, + Type: pluginsdk.TypeString, + Optional: true, + Default: string(tables.TablePlanEnumAnalytics), + ValidateFunc: validation.StringInSlice(tables.PossibleValuesForTablePlanEnum(), false), + }, + + "categories": { + Type: pluginsdk.TypeSet, Optional: true, - Default: string(tables.TablePlanEnumAnalytics), - ValidateFunc: validation.StringInSlice([]string{ - string(tables.TablePlanEnumAnalytics), - string(tables.TablePlanEnumBasic), - }, false), + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "column": { + Type: pluginsdk.TypeList, // Order matters for display in Log Analytics + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: columnSchema(), + }, + }, + + "labels": { + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, }, "retention_in_days": { @@ -89,7 +195,23 @@ func (r LogAnalyticsWorkspaceTableResource) Arguments() map[string]*pluginsdk.Sc } func (r LogAnalyticsWorkspaceTableResource) Attributes() map[string]*pluginsdk.Schema { - return map[string]*pluginsdk.Schema{} + return map[string]*pluginsdk.Schema{ + "solutions": { + Type: pluginsdk.TypeSet, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "standard_column": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: columnSchema(), + }, + }, + } } func (r LogAnalyticsWorkspaceTableResource) ModelObject() interface{} { @@ -123,19 +245,47 @@ func (r LogAnalyticsWorkspaceTableResource) Create() sdk.ResourceFunc { } id := tables.NewTableID(workspaceId.SubscriptionId, workspaceId.ResourceGroupName, workspaceId.WorkspaceName, tableName) + existing, err := client.Get(ctx, id) + if err != nil && !response.WasNotFound(existing.HttpResponse) { + return fmt.Errorf("error checking for presence of existing table %s: %v", tableName, err) + } + if !response.WasNotFound(existing.HttpResponse) { + return metadata.ResourceRequiresImport(r.ResourceType(), id) + } + + if model.SubType == string(tables.TableSubTypeEnumClassic) { + return fmt.Errorf("sub_type 'Classic' tables cannot be created with this resource") + } updateInput := tables.Table{ Properties: &tables.TableProperties{ Plan: pointer.To(tables.TablePlanEnum(model.Plan)), + Schema: &tables.Schema{ + Categories: pointer.To(model.Categories), + Columns: expandColumns(&model.Columns), + DisplayName: pointer.To(model.DisplayName), + Description: pointer.To(model.Description), + Labels: pointer.To(model.Labels), + Name: pointer.To(tableName), + TableSubType: pointer.To(tables.TableSubTypeEnum(model.SubType)), + TableType: pointer.To(tables.TableTypeEnum(model.Type)), + }, }, } if model.Plan == string(tables.TablePlanEnumAnalytics) { - updateInput.Properties.RetentionInDays = pointer.To(model.RetentionInDays) - } - - if model.TotalRetentionInDays != 0 { - updateInput.Properties.TotalRetentionInDays = pointer.To(model.TotalRetentionInDays) + if model.RetentionInDays == 0 { + // Set the retention period to the workspace default + updateInput.Properties.RetentionInDays = useWorkspaceDefaultRetention + } else { + updateInput.Properties.RetentionInDays = pointer.To(model.RetentionInDays) + } + if model.TotalRetentionInDays == 0 { + // Set the retention period to the workspace default + updateInput.Properties.TotalRetentionInDays = useWorkspaceDefaultRetention + } else { + updateInput.Properties.TotalRetentionInDays = pointer.To(model.TotalRetentionInDays) + } } if err := client.CreateOrUpdateThenPoll(ctx, id, updateInput); err != nil { @@ -173,6 +323,14 @@ func (r LogAnalyticsWorkspaceTableResource) Update() sdk.ResourceFunc { updateInput := tables.Table{ Properties: &tables.TableProperties{ Plan: props.Plan, + Schema: &tables.Schema{ + Categories: props.Schema.Categories, + Columns: props.Schema.Columns, + DisplayName: props.Schema.DisplayName, + Description: props.Schema.Description, + Labels: props.Schema.Labels, + Name: props.Schema.Name, + }, }, } @@ -181,15 +339,43 @@ func (r LogAnalyticsWorkspaceTableResource) Update() sdk.ResourceFunc { } if state.Plan == string(tables.TablePlanEnumAnalytics) { - updateInput.Properties.RetentionInDays = existing.Model.Properties.RetentionInDays - if metadata.ResourceData.HasChange("retention_in_days") { - updateInput.Properties.RetentionInDays = pointer.To(state.RetentionInDays) + if state.RetentionInDays == 0 { + // Set the retention period to the workspace default + updateInput.Properties.RetentionInDays = useWorkspaceDefaultRetention + } else { + // Set the retention period to the workspace default + updateInput.Properties.RetentionInDays = pointer.To(state.RetentionInDays) + } + } + + if metadata.ResourceData.HasChange("total_retention_in_days") { + if state.TotalRetentionInDays == 0 { + updateInput.Properties.TotalRetentionInDays = useWorkspaceDefaultRetention + } else { + updateInput.Properties.TotalRetentionInDays = pointer.To(state.TotalRetentionInDays) + } } } - if metadata.ResourceData.HasChange("total_retention_in_days") { - updateInput.Properties.TotalRetentionInDays = pointer.To(state.TotalRetentionInDays) + if metadata.ResourceData.HasChange("display_name") { + updateInput.Properties.Schema.DisplayName = pointer.To(state.DisplayName) + } + + if metadata.ResourceData.HasChange("description") { + updateInput.Properties.Schema.Description = pointer.To(state.Description) + } + + if metadata.ResourceData.HasChange("categories") { + updateInput.Properties.Schema.Categories = pointer.To(state.Categories) + } + + if metadata.ResourceData.HasChange("labels") { + updateInput.Properties.Schema.Labels = pointer.To(state.Labels) + } + + if metadata.ResourceData.HasChange("column") { + updateInput.Properties.Schema.Columns = expandColumns(&state.Columns) } if err := client.CreateOrUpdateThenPoll(ctx, *id, updateInput); err != nil { @@ -212,9 +398,14 @@ func (r LogAnalyticsWorkspaceTableResource) Read() sdk.ResourceFunc { return fmt.Errorf("while parsing resource ID: %+v", err) } - workspaceId, err := workspaces.ParseWorkspaceID(metadata.ResourceData.Get("workspace_id").(string)) - if err != nil { - return fmt.Errorf("while parsing resource ID: %+v", err) + var workspaceId *workspaces.WorkspaceId + if workspaceStateId, ok := metadata.ResourceData.GetOk("workspace_id"); ok { + workspaceId, err = workspaces.ParseWorkspaceID(workspaceStateId.(string)) + if err != nil { + return fmt.Errorf("while parsing resource ID: %+v", err) + } + } else { + workspaceId = pointer.To(workspaces.NewWorkspaceID(id.SubscriptionId, id.ResourceGroupName, id.WorkspaceName)) } client := metadata.Client.LogAnalytics.TablesClient @@ -235,10 +426,33 @@ func (r LogAnalyticsWorkspaceTableResource) Read() sdk.ResourceFunc { if model := resp.Model; model != nil { if props := model.Properties; props != nil { if pointer.From(props.Plan) == tables.TablePlanEnumAnalytics { - state.RetentionInDays = pointer.From(props.RetentionInDays) + if !pointer.From(props.RetentionInDaysAsDefault) { + state.RetentionInDays = pointer.From(props.RetentionInDays) + } + if !pointer.From(props.TotalRetentionInDaysAsDefault) { + state.TotalRetentionInDays = pointer.From(props.TotalRetentionInDays) + } } state.TotalRetentionInDays = pointer.From(props.TotalRetentionInDays) state.Plan = string(pointer.From(props.Plan)) + + if props.Schema != nil { + state.DisplayName = pointer.From(props.Schema.DisplayName) + state.Description = pointer.From(props.Schema.Description) + state.Type = string(pointer.From(props.Schema.TableType)) + state.SubType = string(pointer.From(props.Schema.TableSubType)) + state.Categories = pointer.From(props.Schema.Categories) + state.Labels = pointer.From(props.Schema.Labels) + state.Solutions = pointer.From(props.Schema.Solutions) + + if props.Schema.Columns != nil { + state.Columns = flattenColumns(props.Schema.Columns) + } + + if props.Schema.StandardColumns != nil { + state.StandardColumns = flattenColumns(props.Schema.StandardColumns) + } + } } } @@ -261,23 +475,107 @@ func (r LogAnalyticsWorkspaceTableResource) Delete() sdk.ResourceFunc { return fmt.Errorf("while parsing resource ID: %+v", err) } - // We do not delete the resource here, just set the retention to workspace default value, which is - // achieved by setting the value to `-1` - retentionInDays := utils.Int64(-1) - totalRetentionInDays := utils.Int64(-1) - - updateInput := tables.Table{ - Properties: &tables.TableProperties{ - RetentionInDays: retentionInDays, - TotalRetentionInDays: totalRetentionInDays, - }, - } + if model.Type == string(tables.TableTypeEnumMicrosoft) { + // We can't delete Microsoft tables, so we'll just set the retention to workspace default + updateInput := tables.Table{ + Properties: &tables.TableProperties{ + RetentionInDays: useWorkspaceDefaultRetention, + TotalRetentionInDays: useWorkspaceDefaultRetention, + Schema: &tables.Schema{ + Name: pointer.To(id.TableName), + Columns: &[]tables.Column{}, + }, + }, + } - if err := client.CreateOrUpdateThenPoll(ctx, *id, updateInput); err != nil { - return fmt.Errorf("failed to update table %s in workspace %s in resource group %s: %s", id.TableName, id.WorkspaceName, id.ResourceGroupName, err) + if err := client.CreateOrUpdateThenPoll(ctx, *id, updateInput); err != nil { + return fmt.Errorf("failed to update table %s in workspace %s in resource group %s: %s", id.TableName, id.WorkspaceName, id.ResourceGroupName, err) + } + } else { + if err := client.DeleteThenPoll(ctx, *id); err != nil { + return fmt.Errorf("deleting Log Analytics Workspace Table %s: %v", id, err) + } } return nil }, } } + +func columnSchema() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "display_name": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "description": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "type": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(tables.PossibleValuesForColumnTypeEnum(), false), + }, + + "type_hint": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(tables.PossibleValuesForColumnDataTypeHintEnum(), false), + }, + + "hidden": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "display_by_default": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + }, + } +} + +func expandColumns(columns *[]Column) *[]tables.Column { + result := make([]tables.Column, 0, len(*columns)) + for _, column := range *columns { + result = append(result, tables.Column{ + Name: pointer.To(column.Name), + DisplayName: pointer.To(column.DisplayName), + Description: pointer.To(column.Description), + IsHidden: pointer.To(column.IsHidden), + IsDefaultDisplay: pointer.To(column.IsDefaultDisplay), + Type: pointer.To(tables.ColumnTypeEnum(column.Type)), + DataTypeHint: pointer.To(tables.ColumnDataTypeHintEnum(column.TypeHint)), + }) + } + return pointer.To(result) +} + +func flattenColumns(columns *[]tables.Column) []Column { + result := make([]Column, 0, len(*columns)) + for _, column := range *columns { + result = append(result, Column{ + Name: pointer.From(column.Name), + DisplayName: pointer.From(column.DisplayName), + Description: pointer.From(column.Description), + IsHidden: pointer.From(column.IsHidden), + IsDefaultDisplay: pointer.From(column.IsDefaultDisplay), + Type: string(pointer.From(column.Type)), + TypeHint: string(pointer.From(column.DataTypeHint)), + }) + } + return result +} diff --git a/internal/services/loganalytics/log_analytics_workspace_table_resource_test.go b/internal/services/loganalytics/log_analytics_workspace_table_resource_test.go index 9e6adf3b2cf4..421142861d17 100644 --- a/internal/services/loganalytics/log_analytics_workspace_table_resource_test.go +++ b/internal/services/loganalytics/log_analytics_workspace_table_resource_test.go @@ -19,34 +19,82 @@ import ( type LogAnalyticsWorkspaceTableResource struct{} func TestAccLogAnalyticsWorkspaceTable_updateTableRetention(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_log_analytics_workspace_table", "test") + data := acceptance.BuildTestData(t, "azurerm_log_analytics_workspace", "test") r := LogAnalyticsWorkspaceTableResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.updateRetention(data), + Config: r.base(data), + }, + { + Config: r.updateRetentionImport(data), + ExpectError: acceptance.RequiresImportError("azurerm_log_analytics_workspace_table"), + }, + data.ImportStep(), + { + Config: r.updateRetentionUpdate(data), Check: acceptance.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("id").Exists(), - check.That(data.ResourceName).Key("name").HasValue("AppEvents"), - check.That(data.ResourceName).Key("retention_in_days").HasValue("7"), - check.That(data.ResourceName).Key("total_retention_in_days").HasValue("32"), + check.That("azurerm_log_analytics_workspace_table.test").ExistsInAzure(r), + check.That("azurerm_log_analytics_workspace_table.test").Key("id").Exists(), + check.That("azurerm_log_analytics_workspace_table.test").Key("name").HasValue("AppEvents"), + check.That("azurerm_log_analytics_workspace_table.test").Key("retention_in_days").HasValue("7"), + check.That("azurerm_log_analytics_workspace_table.test").Key("total_retention_in_days").HasValue("32"), ), }, }) } func TestAccLogAnalyticsWorkspaceTable_plan(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_log_analytics_workspace", "test") + r := LogAnalyticsWorkspaceTableResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.base(data), + }, + { + Config: r.planImport(data), + ExpectError: acceptance.RequiresImportError("azurerm_log_analytics_workspace_table"), + }, + data.ImportStep(), + { + Config: r.planUpdate(data), + Check: acceptance.ComposeTestCheckFunc( + check.That("azurerm_log_analytics_workspace_table.test").ExistsInAzure(r), + ), + }, + }) +} + +func TestAccLogAnalyticsWorkspaceTable_customDcr(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_log_analytics_workspace_table", "test") r := LogAnalyticsWorkspaceTableResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.plan(data), + Config: r.custom(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("id").Exists(), + check.That(data.ResourceName).Key("name").HasValue("CustomTable_CL"), + check.That(data.ResourceName).Key("column.0.name").HasValue("CompanyName"), + check.That(data.ResourceName).Key("retention_in_days").HasValue("7"), + check.That(data.ResourceName).Key("total_retention_in_days").HasValue("32"), ), }, + data.ImportStep(), + { + Config: r.customUpdate(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("id").Exists(), + check.That(data.ResourceName).Key("name").HasValue("CustomTable_CL"), + check.That(data.ResourceName).Key("column.0.name").HasValue("LogName"), + check.That(data.ResourceName).Key("retention_in_days").HasValue("0"), + check.That(data.ResourceName).Key("total_retention_in_days").HasValue("30"), + ), + }, + data.ImportStep(), }) } @@ -64,31 +112,115 @@ func (t LogAnalyticsWorkspaceTableResource) Exists(ctx context.Context, clients return utils.Bool(resp.Model.Id != nil), nil } -func (LogAnalyticsWorkspaceTableResource) updateRetention(data acceptance.TestData) string { +func (t LogAnalyticsWorkspaceTableResource) updateRetentionImport(data acceptance.TestData) string { return fmt.Sprintf(` -provider "azurerm" { - features {} +%s +import { + id = "${azurerm_log_analytics_workspace.test.id}/tables/AppEvents" + to = azurerm_log_analytics_workspace_table.test } -resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" +resource "azurerm_log_analytics_workspace_table" "test" { + name = "AppEvents" + type = "Microsoft" + sub_type = "DataCollectionRuleBased" + workspace_id = azurerm_log_analytics_workspace.test.id + total_retention_in_days = 90 } -resource "azurerm_log_analytics_workspace" "test" { - name = "acctestLAW-%d" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - retention_in_days = 30 +`, t.base(data)) } + +func (t LogAnalyticsWorkspaceTableResource) updateRetentionUpdate(data acceptance.TestData) string { + return fmt.Sprintf(` +%s resource "azurerm_log_analytics_workspace_table" "test" { name = "AppEvents" + type = "Microsoft" + sub_type = "DataCollectionRuleBased" workspace_id = azurerm_log_analytics_workspace.test.id retention_in_days = 7 total_retention_in_days = 32 } -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +`, t.base(data)) +} + +func (t LogAnalyticsWorkspaceTableResource) planImport(data acceptance.TestData) string { + return fmt.Sprintf(` +%s +import { + id = "${azurerm_log_analytics_workspace.test.id}/tables/AppTraces" + to = azurerm_log_analytics_workspace_table.test +} +resource "azurerm_log_analytics_workspace_table" "test" { + name = "AppTraces" + type = "Microsoft" + sub_type = "DataCollectionRuleBased" + workspace_id = azurerm_log_analytics_workspace.test.id + plan = "Analytics" + total_retention_in_days = 90 +} +`, t.base(data)) } -func (LogAnalyticsWorkspaceTableResource) plan(data acceptance.TestData) string { +func (t LogAnalyticsWorkspaceTableResource) planUpdate(data acceptance.TestData) string { + return fmt.Sprintf(` +%s +resource "azurerm_log_analytics_workspace_table" "test" { + name = "AppTraces" + type = "Microsoft" + sub_type = "DataCollectionRuleBased" + workspace_id = azurerm_log_analytics_workspace.test.id + plan = "Basic" + total_retention_in_days = 90 +} +`, t.base(data)) +} + +func (t LogAnalyticsWorkspaceTableResource) custom(data acceptance.TestData) string { + return fmt.Sprintf(` +%s +resource "azurerm_log_analytics_workspace_table" "test" { + name = "CustomTable_CL" + type = "CustomLog" + sub_type = "DataCollectionRuleBased" + workspace_id = azurerm_log_analytics_workspace.test.id + retention_in_days = 7 + total_retention_in_days = 32 + + column { + name = "CompanyName" + type = "string" + } + column { + name = "TimeGenerated" + type = "dateTime" + } +} +`, t.base(data)) +} + +func (t LogAnalyticsWorkspaceTableResource) customUpdate(data acceptance.TestData) string { + return fmt.Sprintf(` +%s +resource "azurerm_log_analytics_workspace_table" "test" { + name = "CustomTable_CL" + type = "CustomLog" + sub_type = "DataCollectionRuleBased" + workspace_id = azurerm_log_analytics_workspace.test.id + total_retention_in_days = 30 + + column { + name = "LogName" + type = "string" + } + column { + name = "TimeGenerated" + type = "dateTime" + } +} +`, t.base(data)) +} + +func (t LogAnalyticsWorkspaceTableResource) base(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -103,11 +235,5 @@ resource "azurerm_log_analytics_workspace" "test" { resource_group_name = azurerm_resource_group.test.name retention_in_days = 30 } -resource "azurerm_log_analytics_workspace_table" "test" { - name = "AppTraces" - workspace_id = azurerm_log_analytics_workspace.test.id - plan = "Basic" - total_retention_in_days = 32 -} `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } diff --git a/website/docs/r/log_analytics_workspace_table.html.markdown b/website/docs/r/log_analytics_workspace_table.html.markdown index 17089b4fc020..cf8da6f2388a 100644 --- a/website/docs/r/log_analytics_workspace_table.html.markdown +++ b/website/docs/r/log_analytics_workspace_table.html.markdown @@ -10,10 +10,12 @@ description: |- Manages a Table in a Log Analytics (formally Operational Insights) Workspace. -~> **Note:** This resource does not create or destroy tables. This resource is used to update attributes (currently only retention_in_days) of the tables created when a Log Analytics Workspace is created. Deleting an azurerm_log_analytics_workspace_table resource will not delete the table. Instead, the table's retention_in_days field will be set to the value of azurerm_log_analytics_workspace retention_in_days +-> **Note:** Only `CustomLog` tables with a `DataCollectionRuleBased` sub-type are supported for creation and deletion with this resource. For `Microsoft` and `Classic` tables, they can be imported and managed, but if removed from the Terraform configuration they will have their retention policy set to the workspace default and be removed from state. ## Example Usage +Update the retention policy on an inbuilt table + ```hcl resource "azurerm_resource_group" "example" { name = "example-resources" @@ -29,48 +31,119 @@ resource "azurerm_log_analytics_workspace" "example" { resource "azurerm_log_analytics_workspace_table" "example" { workspace_id = azurerm_log_analytics_workspace.example.id name = "AppMetrics" + type = "Microsoft" + sub_type = "DataCollectionRuleBased" retention_in_days = 60 total_retention_in_days = 180 } ``` +Create a new Custom Log Table + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} +resource "azurerm_log_analytics_workspace" "example" { + name = "example" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + sku = "PerGB2018" + retention_in_days = 30 +} +resource "azurerm_log_analytics_workspace_table" "example" { + name = "CustomTable_CL" + type = "CustomLog" + sub_type = "DataCollectionRuleBased" + workspace_id = azurerm_log_analytics_workspace.example.id + + column { + name = "ServiceName" + type = "string" + } + column { + name = "TimeGenerated" + type = "dateTime" + } +} +``` + ## Argument Reference The following arguments are supported: -* `name` - (Required) Specifies the name of a table in a Log Analytics Workspace. +- `name` - (Required) Specifies the name of the table in a Log Analytics Workspace. Must end in `_CL` for custom tables. + +- `workspace_id` - (Required) The object ID of the Log Analytics Workspace that will contain the table. -* `workspace_id` - (Required) The object ID of the Log Analytics Workspace that contains the table. +- `type` - (Required) The type of table. Must be either of `Microsoft` for inbuilt tables, or `CustomLog` for custom tables. -* `plan` - (Optional) Specify the system how to handle and charge the logs ingested to the table. Possible values are `Analytics` and `Basic`. Defaults to `Analytics`. +- `sub_type` - (Required) The sub type of table. Must be one of `Any`, `Classic`, or `DataCollectionRuleBased`. + +- `display_name` - (Optional) The display name of the table in a Log Analytics Workspace. + +- `description` - (Optional) The description of the table in a Log Analytics Workspace. + +- `categories` - (Optional) The categories applied to the table. + +- `column` - (Optional) One or more `column` blocks detailed below. + +-> **Note:** The order of the columns will match the display order in Log Analytics. + +- `labels` - (Optional) The labels applied to the table. + +- `plan` - (Optional) Specify the system how to handle and charge the logs ingested to the table. Possible values are `Analytics` and `Basic`. Defaults to `Analytics`. -> **Note:** The `name` of tables currently supported by the `Basic` plan can be found [here](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/basic-logs-configure?tabs=portal-1#supported-tables). -* `retention_in_days` - (Optional) The table's retention in days. Possible values are either `8` (Basic Tier only) or range between `4` and `730`. +- `retention_in_days` - (Optional) The table's retention in days. Possible values are either `8` (Basic Tier only) or range between `4` and `730`. -* `total_retention_in_days` - (Optional) The table's total retention in days. Possible values range between `4` and `730`; or `1095`, `1460`, `1826`, `2191`, `2556`, `2922`, `3288`, `3653`, `4018`, or `4383`. +- `total_retention_in_days` - (Optional) The table's total retention in days. Possible values range between `4` and `730`; or `1095`, `1460`, `1826`, `2191`, `2556`, `2922`, `3288`, `3653`, `4018`, or `4383`. --> **Note:** `retention_in_days` and `total_retention_in_days` will revert back to the value of azurerm_log_analytics_workspace retention_in_days when a azurerm_log_analytics_workspace_table is deleted. +-> **Note:** `retention_in_days` and `total_retention_in_days` will revert back to the value of azurerm_log_analytics_workspace retention_in_days when a Microsoft or Classic azurerm_log_analytics_workspace_table is deleted. -> **Note:** The `retention_in_days` cannot be specified when `plan` is `Basic` because the retention is fixed at eight days. +--- + +A `column` block supports the following: + +- `name` - (Required) The name of the column. + +- `type` - (Required) The type of data stored in the column. Must be one of `boolean`, `dateTime`, `dynamic`, `guid"`, `int`, `long`, `real` , or `string`. + +- `display_name` - (Optional) The display name of the column. + +- `description` - (Optional) A description of the column. + +- `display_by_default` - (Optional) Is the column displayed by default. Defaults to `true`. + +- `type_hint` - (Optional) A hint as to what kind of data is stored in a `string` column. Must be one of `armpath`, `guid`, `ip`, or `uri`. + +- `hidden` - (Optional) Is the column hidden? Defaults to `false`. + ## Attributes Reference The following attributes are exported: -* `id` - The Log Analytics Workspace Table ID. +- `id` - The Log Analytics Workspace Table ID. + +- `workspace_id` - The Workspace (or Customer) ID for the Log Analytics Workspace. + +- `retention_in_days` - The table's data retention in days. -* `workspace_id` - The Workspace (or Customer) ID for the Log Analytics Workspace. +- `solutions` - The list of solutions associated with this table. -* `retention_in_days` - The table's data retention in days. +- `standard_column` - The details of the standard columns in this table. -* `total_retention_in_days` - The table's total data retention in days. +- `total_retention_in_days` - The table's total data retention in days. ## Timeouts The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: -* `create` - (Defaults to 5 minutes) Used when creating the Log Analytics Workspace. -* `update` - (Defaults to 5 minutes) Used when updating the Log Analytics Workspace. -* `read` - (Defaults to 5 minutes) Used when retrieving the Log Analytics Workspace. -* `delete` - (Defaults to 30 minutes) Used when deleting the Log Analytics Workspace. +- `create` - (Defaults to 5 minutes) Used when creating the Log Analytics Workspace. +- `update` - (Defaults to 5 minutes) Used when updating the Log Analytics Workspace. +- `read` - (Defaults to 5 minutes) Used when retrieving the Log Analytics Workspace. +- `delete` - (Defaults to 30 minutes) Used when deleting the Log Analytics Workspace.