diff --git a/docs/data-sources/deployment_freezes.md b/docs/data-sources/deployment_freezes.md index b23bd14f..fdeff304 100644 --- a/docs/data-sources/deployment_freezes.md +++ b/docs/data-sources/deployment_freezes.md @@ -42,6 +42,7 @@ Read-Only: - `project_environment_scope` (Map of List of String) The project environment scope of the deployment freeze - `recurring_schedule` (Attributes) (see [below for nested schema](#nestedatt--deployment_freezes--recurring_schedule)) - `start` (String) The start time of the freeze +- `tenant_project_environment_scope` (Attributes List) The tenant project environment scope of the deployment freeze (see [below for nested schema](#nestedatt--deployment_freezes--tenant_project_environment_scope)) ### Nested Schema for `deployment_freezes.recurring_schedule` @@ -60,3 +61,13 @@ Read-Only: - `unit` (Number) The unit value for the schedule + +### Nested Schema for `deployment_freezes.tenant_project_environment_scope` + +Read-Only: + +- `environment_id` (String) The environment ID +- `project_id` (String) The project ID +- `tenant_id` (String) The tenant ID + + diff --git a/octopusdeploy_framework/datasource_deployment_freeze.go b/octopusdeploy_framework/datasource_deployment_freeze.go index b3fb8d3d..ab7288f1 100644 --- a/octopusdeploy_framework/datasource_deployment_freeze.go +++ b/octopusdeploy_framework/datasource_deployment_freeze.go @@ -14,28 +14,6 @@ import ( const deploymentFreezeDatasourceName = "deployment_freezes" -type recurringScheduleDatasourceModel struct { - Type types.String `tfsdk:"type"` - Unit types.Int64 `tfsdk:"unit"` - EndType types.String `tfsdk:"end_type"` - EndOnDate types.String `tfsdk:"end_on_date"` - EndAfterOccurrences types.Int64 `tfsdk:"end_after_occurrences"` - MonthlyScheduleType types.String `tfsdk:"monthly_schedule_type"` - DateOfMonth types.String `tfsdk:"date_of_month"` - DayNumberOfMonth types.String `tfsdk:"day_number_of_month"` - DaysOfWeek types.List `tfsdk:"days_of_week"` - DayOfWeek types.String `tfsdk:"day_of_week"` -} - -type deploymentFreezeDatasourceModel struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Start types.String `tfsdk:"start"` - End types.String `tfsdk:"end"` - ProjectEnvironmentScope types.Map `tfsdk:"project_environment_scope"` - RecurringSchedule *recurringScheduleDatasourceModel `tfsdk:"recurring_schedule"` -} - type deploymentFreezesDatasourceModel struct { ID types.String `tfsdk:"id"` IDs types.List `tfsdk:"ids"` @@ -114,27 +92,55 @@ var _ datasource.DataSource = &deploymentFreezeDataSource{} var _ datasource.DataSourceWithConfigure = &deploymentFreezeDataSource{} func mapFreezeToAttribute(ctx context.Context, freeze deploymentfreezes.DeploymentFreeze) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + projectScopes := make(map[string]attr.Value) for projectId, environmentScopes := range freeze.ProjectEnvironmentScope { projectScopes[projectId] = util.FlattenStringList(environmentScopes) } - scopeType, diags := types.MapValueFrom(ctx, types.ListType{ElemType: types.StringType}, projectScopes) - if diags.HasError() { + scopeType, scopeDiags := types.MapValueFrom(ctx, types.ListType{ElemType: types.StringType}, projectScopes) + if scopeDiags.HasError() { + diags.Append(scopeDiags...) + return nil, diags + } + + tenantScopes := make([]attr.Value, 0) + for _, scope := range freeze.TenantProjectEnvironmentScope { + tenantScope, tDiags := types.ObjectValue(tenantScopeObjectType(), map[string]attr.Value{ + "tenant_id": types.StringValue(scope.TenantId), + "project_id": types.StringValue(scope.ProjectId), + "environment_id": types.StringValue(scope.EnvironmentId), + }) + if tDiags.HasError() { + diags.Append(tDiags...) + return nil, diags + } + tenantScopes = append(tenantScopes, tenantScope) + } + + tenantScopesList, tsDiags := types.ListValue( + types.ObjectType{AttrTypes: tenantScopeObjectType()}, + tenantScopes, + ) + if tsDiags.HasError() { + diags.Append(tsDiags...) return nil, diags } attrs := map[string]attr.Value{ - "id": types.StringValue(freeze.ID), - "name": types.StringValue(freeze.Name), - "start": types.StringValue(freeze.Start.Format(time.RFC3339)), - "end": types.StringValue(freeze.End.Format(time.RFC3339)), - "project_environment_scope": scopeType, + "id": types.StringValue(freeze.ID), + "name": types.StringValue(freeze.Name), + "start": types.StringValue(freeze.Start.Format(time.RFC3339)), + "end": types.StringValue(freeze.End.Format(time.RFC3339)), + "project_environment_scope": scopeType, + "tenant_project_environment_scope": tenantScopesList, } if freeze.RecurringSchedule != nil { - daysOfWeek, diags := types.ListValueFrom(ctx, types.StringType, freeze.RecurringSchedule.DaysOfWeek) - if diags.HasError() { + daysOfWeek, daysDiags := types.ListValueFrom(ctx, types.StringType, freeze.RecurringSchedule.DaysOfWeek) + if daysDiags.HasError() { + diags.Append(daysDiags...) return nil, diags } @@ -181,8 +187,9 @@ func mapFreezeToAttribute(ctx context.Context, freeze deploymentfreezes.Deployme "day_of_week": dayOfWeek, } - recurringSchedule, diags := types.ObjectValue(freezeRecurringScheduleObjectType(), scheduleAttrs) - if diags.HasError() { + recurringSchedule, rsDiags := types.ObjectValue(freezeRecurringScheduleObjectType(), scheduleAttrs) + if rsDiags.HasError() { + diags.Append(rsDiags...) return nil, diags } @@ -209,13 +216,22 @@ func freezeRecurringScheduleObjectType() map[string]attr.Type { } } +func tenantScopeObjectType() map[string]attr.Type { + return map[string]attr.Type{ + "tenant_id": types.StringType, + "project_id": types.StringType, + "environment_id": types.StringType, + } +} + func freezeObjectType() map[string]attr.Type { return map[string]attr.Type{ - "id": types.StringType, - "name": types.StringType, - "start": types.StringType, - "end": types.StringType, - "project_environment_scope": types.MapType{ElemType: types.ListType{ElemType: types.StringType}}, - "recurring_schedule": types.ObjectType{AttrTypes: freezeRecurringScheduleObjectType()}, + "id": types.StringType, + "name": types.StringType, + "start": types.StringType, + "end": types.StringType, + "project_environment_scope": types.MapType{ElemType: types.ListType{ElemType: types.StringType}}, + "tenant_project_environment_scope": types.ListType{ElemType: types.ObjectType{AttrTypes: tenantScopeObjectType()}}, + "recurring_schedule": types.ObjectType{AttrTypes: freezeRecurringScheduleObjectType()}, } } diff --git a/octopusdeploy_framework/datasource_deployment_freeze_test.go b/octopusdeploy_framework/datasource_deployment_freeze_test.go index b5ad302b..55c1b359 100644 --- a/octopusdeploy_framework/datasource_deployment_freeze_test.go +++ b/octopusdeploy_framework/datasource_deployment_freeze_test.go @@ -11,9 +11,11 @@ import ( func TestAccDataSourceDeploymentFreezes(t *testing.T) { spaceName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) freezeName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) - resourceName := "data.octopusdeploy_deployment_freezes.test_freeze" + tenantName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + projectName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + environmentName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + dataSourceName := "data.octopusdeploy_deployment_freezes.test_freeze" - // Use future dates for the freeze period, ensuring UTC timezone startTime := time.Now().AddDate(1, 0, 0).UTC() endTime := startTime.Add(24 * time.Hour) @@ -22,69 +24,137 @@ func TestAccDataSourceDeploymentFreezes(t *testing.T) { ProtoV6ProviderFactories: ProtoV6ProviderFactories(), Steps: []resource.TestStep{ { - Config: testAccDataSourceDeploymentFreezesConfig(spaceName, freezeName, startTime, endTime), + Config: testAccDataSourceDeploymentFreezesConfig(spaceName, freezeName, startTime, endTime, false, projectName, environmentName, tenantName), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrSet(resourceName, "id"), - resource.TestCheckResourceAttr(resourceName, "partial_name", freezeName), - resource.TestCheckResourceAttr(resourceName, "deployment_freezes.#", "1"), - resource.TestCheckResourceAttrSet(resourceName, "deployment_freezes.0.id"), - resource.TestCheckResourceAttr(resourceName, "deployment_freezes.0.name", freezeName), - resource.TestCheckResourceAttr(resourceName, "deployment_freezes.0.start", startTime.Format(time.RFC3339)), - resource.TestCheckResourceAttr(resourceName, "deployment_freezes.0.end", endTime.Format(time.RFC3339)), - resource.TestCheckResourceAttrSet(resourceName, "deployment_freezes.0.project_environment_scope.%"), - resource.TestCheckResourceAttr(resourceName, "deployment_freezes.0.recurring_schedule.type", "DaysPerWeek"), - resource.TestCheckResourceAttr(resourceName, "deployment_freezes.0.recurring_schedule.unit", "24"), - resource.TestCheckResourceAttr(resourceName, "deployment_freezes.0.recurring_schedule.end_type", "AfterOccurrences"), - resource.TestCheckResourceAttr(resourceName, "deployment_freezes.0.recurring_schedule.end_after_occurrences", "5"), - resource.TestCheckResourceAttr(resourceName, "deployment_freezes.0.recurring_schedule.days_of_week.#", "3"), - testAccCheckOutputExists("octopus_space_id"), - testAccCheckOutputExists("octopus_freeze_id"), + resource.TestCheckResourceAttrSet(dataSourceName, "id"), + resource.TestCheckResourceAttr(dataSourceName, "partial_name", freezeName), + resource.TestCheckResourceAttr(dataSourceName, "deployment_freezes.#", "1"), + resource.TestCheckResourceAttr(dataSourceName, "deployment_freezes.0.name", freezeName), + resource.TestCheckResourceAttr(dataSourceName, "deployment_freezes.0.tenant_project_environment_scope.#", "0"), + ), + }, + { + Config: testAccDataSourceDeploymentFreezesConfig(spaceName, freezeName, startTime, endTime, true, projectName, environmentName, tenantName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dataSourceName, "id"), + resource.TestCheckResourceAttr(dataSourceName, "deployment_freezes.0.tenant_project_environment_scope.#", "1"), + resource.TestCheckResourceAttrSet(dataSourceName, "deployment_freezes.0.tenant_project_environment_scope.0.tenant_id"), + resource.TestCheckResourceAttrSet(dataSourceName, "deployment_freezes.0.tenant_project_environment_scope.0.project_id"), + resource.TestCheckResourceAttrSet(dataSourceName, "deployment_freezes.0.tenant_project_environment_scope.0.environment_id"), ), }, }, }) } -func testAccDataSourceDeploymentFreezesConfig(spaceName, freezeName string, startTime, endTime time.Time) string { - return fmt.Sprintf(` +func testAccDataSourceDeploymentFreezesConfig(spaceName, freezeName string, startTime, endTime time.Time, includeTenant bool, projectName, environmentName, tenantName string) string { + baseConfig := fmt.Sprintf(` resource "octopusdeploy_space" "test_space" { - name = "%s" - is_default = false - is_task_queue_stopped = false - description = "Test space for deployment freeze datasource" - space_managers_teams = ["teams-administrators"] + name = "%s" + is_default = false + is_task_queue_stopped = false + description = "Test space for deployment freeze datasource" + space_managers_teams = ["teams-administrators"] +} + +resource "octopusdeploy_environment" "test_environment" { + name = "%s" + description = "Test environment" + space_id = octopusdeploy_space.test_space.id +} + +resource "octopusdeploy_project_group" "test_project_group" { + name = "Test Project Group" + description = "Test project group for deployment freeze datasource" + space_id = octopusdeploy_space.test_space.id +} + +resource "octopusdeploy_lifecycle" "test_lifecycle" { + name = "Test Lifecycle" + space_id = octopusdeploy_space.test_space.id +} + +resource "octopusdeploy_project" "test_project" { + name = "%s" + lifecycle_id = octopusdeploy_lifecycle.test_lifecycle.id + project_group_id = octopusdeploy_project_group.test_project_group.id + space_id = octopusdeploy_space.test_space.id } resource "octopusdeploy_deployment_freeze" "test_freeze" { - name = "%s" - start = "%s" - end = "%s" - - recurring_schedule = { - type = "DaysPerWeek" - unit = 24 - end_type = "AfterOccurrences" - end_after_occurrences = 5 - days_of_week = ["Monday", "Wednesday", "Friday"] - } + name = "%s" + start = "%s" + end = "%s" + + recurring_schedule = { + type = "DaysPerWeek" + unit = 24 + end_type = "AfterOccurrences" + end_after_occurrences = 5 + days_of_week = ["Monday", "Wednesday", "Friday"] + } + + depends_on = [octopusdeploy_space.test_space] +} +`, spaceName, environmentName, projectName, freezeName, startTime.Format(time.RFC3339), endTime.Format(time.RFC3339)) + + if includeTenant { + tenantConfig := fmt.Sprintf(` +resource "octopusdeploy_tenant" "test_tenant" { + name = "%s" + space_id = octopusdeploy_space.test_space.id +} + +resource "octopusdeploy_tenant_project" "test_tenant_project" { + tenant_id = octopusdeploy_tenant.test_tenant.id + project_id = octopusdeploy_project.test_project.id + environment_ids = [octopusdeploy_environment.test_environment.id] + space_id = octopusdeploy_space.test_space.id +} + +resource "octopusdeploy_deployment_freeze_tenant" "test_tenant_scope" { + deploymentfreeze_id = octopusdeploy_deployment_freeze.test_freeze.id + tenant_id = octopusdeploy_tenant.test_tenant.id + project_id = octopusdeploy_project.test_project.id + environment_id = octopusdeploy_environment.test_environment.id - depends_on = [octopusdeploy_space.test_space] + depends_on = [ + octopusdeploy_tenant_project.test_tenant_project + ] } +`, tenantName) + baseConfig = baseConfig + tenantConfig + } + datasourceConfig := ` data "octopusdeploy_deployment_freezes" "test_freeze" { - ids = null - partial_name = "%s" - skip = 0 - take = 1 - depends_on = [octopusdeploy_deployment_freeze.test_freeze] + ids = null + partial_name = "%s" + skip = 0 + take = 1 + depends_on = [` + + if includeTenant { + datasourceConfig += ` + octopusdeploy_deployment_freeze.test_freeze, + octopusdeploy_deployment_freeze_tenant.test_tenant_scope + ` + } else { + datasourceConfig += ` + octopusdeploy_deployment_freeze.test_freeze + ` + } + + datasourceConfig += `] } output "octopus_space_id" { - value = octopusdeploy_space.test_space.id + value = octopusdeploy_space.test_space.id } output "octopus_freeze_id" { - value = data.octopusdeploy_deployment_freezes.test_freeze.deployment_freezes[0].id + value = data.octopusdeploy_deployment_freezes.test_freeze.deployment_freezes[0].id } -`, spaceName, freezeName, startTime.Format(time.RFC3339), endTime.Format(time.RFC3339), freezeName) +` + return baseConfig + fmt.Sprintf(datasourceConfig, freezeName) } diff --git a/octopusdeploy_framework/schemas/deployment_freeze.go b/octopusdeploy_framework/schemas/deployment_freeze.go index 3d986d7a..810c81d6 100644 --- a/octopusdeploy_framework/schemas/deployment_freeze.go +++ b/octopusdeploy_framework/schemas/deployment_freeze.go @@ -61,6 +61,7 @@ func (d DeploymentFreezeSchema) GetResourceSchema() resourceSchema.Schema { }, } } + func (d DeploymentFreezeSchema) GetDatasourceSchema() datasourceSchema.Schema { return datasourceSchema.Schema{ Description: "Provides information about deployment freezes", @@ -92,23 +93,39 @@ func (d DeploymentFreezeSchema) GetDatasourceSchema() datasourceSchema.Schema { "name": GetReadonlyNameDatasourceSchema(), "start": datasourceSchema.StringAttribute{ Description: "The start time of the freeze", - Optional: false, Computed: true, }, "end": datasourceSchema.StringAttribute{ Description: "The end time of the freeze", - Optional: false, Computed: true, }, "project_environment_scope": datasourceSchema.MapAttribute{ ElementType: types.ListType{ElemType: types.StringType}, Description: "The project environment scope of the deployment freeze", - Optional: false, Computed: true, }, + "tenant_project_environment_scope": datasourceSchema.ListNestedAttribute{ + Description: "The tenant project environment scope of the deployment freeze", + Computed: true, + NestedObject: datasourceSchema.NestedAttributeObject{ + Attributes: map[string]datasourceSchema.Attribute{ + "tenant_id": datasourceSchema.StringAttribute{ + Description: "The tenant ID", + Computed: true, + }, + "project_id": datasourceSchema.StringAttribute{ + Description: "The project ID", + Computed: true, + }, + "environment_id": datasourceSchema.StringAttribute{ + Description: "The environment ID", + Computed: true, + }, + }, + }, + }, "recurring_schedule": datasourceSchema.SingleNestedAttribute{ Computed: true, - Optional: false, Attributes: map[string]datasourceSchema.Attribute{ "type": datasourceSchema.StringAttribute{ Description: "Type of recurring schedule (OnceDaily, DaysPerWeek, DaysPerMonth, Annually)", @@ -155,7 +172,6 @@ func (d DeploymentFreezeSchema) GetDatasourceSchema() datasourceSchema.Schema { }, }, }, - Optional: false, Computed: true, }, },