diff --git a/internal/services/devcenter/dev_center_project_environment_type.go b/internal/services/devcenter/dev_center_project_environment_type.go new file mode 100644 index 000000000000..0b01e724190a --- /dev/null +++ b/internal/services/devcenter/dev_center_project_environment_type.go @@ -0,0 +1,410 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package devcenter + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-helpers/lang/response" + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonids" + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" + "github.com/hashicorp/go-azure-helpers/resourcemanager/identity" + "github.com/hashicorp/go-azure-helpers/resourcemanager/location" + "github.com/hashicorp/go-azure-sdk/resource-manager/devcenter/2023-04-01/environmenttypes" + "github.com/hashicorp/go-azure-sdk/resource-manager/devcenter/2023-04-01/projects" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/devcenter/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +var _ sdk.Resource = DevCenterProjectEnvironmentTypeResource{} +var _ sdk.ResourceWithUpdate = DevCenterProjectEnvironmentTypeResource{} + +type DevCenterProjectEnvironmentTypeResource struct{} + +func (r DevCenterProjectEnvironmentTypeResource) ModelObject() interface{} { + return &DevCenterProjectEnvironmentTypeResourceModel{} +} + +type DevCenterProjectEnvironmentTypeResourceModel struct { + Name string `tfschema:"name"` + Location string `tfschema:"location"` + DevCenterProjectId string `tfschema:"dev_center_project_id"` + DeploymentTargetId string `tfschema:"deployment_target_id"` + CreatorRoleAssignmentRoles []string `tfschema:"creator_role_assignment_roles"` + Identity []identity.ModelSystemAssignedUserAssigned `tfschema:"identity"` + UserRoleAssignment []DevCenterProjectEnvironmentTypeUserRoleAssignment `tfschema:"user_role_assignment"` + Tags map[string]string `tfschema:"tags"` +} + +type DevCenterProjectEnvironmentTypeUserRoleAssignment struct { + UserId string `tfschema:"user_id"` + Roles []string `tfschema:"roles"` +} + +func (r DevCenterProjectEnvironmentTypeResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return environmenttypes.ValidateEnvironmentTypeID +} + +func (r DevCenterProjectEnvironmentTypeResource) ResourceType() string { + return "azurerm_dev_center_project_environment_type" +} + +func (r DevCenterProjectEnvironmentTypeResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.DevCenterProjectEnvironmentTypeName, + }, + + "location": commonschema.Location(), + + "dev_center_project_id": commonschema.ResourceIDReferenceRequiredForceNew(&environmenttypes.ProjectId{}), + + "deployment_target_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: commonids.ValidateSubscriptionID, + }, + + "identity": commonschema.SystemAssignedUserAssignedIdentityRequired(), + + "creator_role_assignment_roles": { + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.IsUUID, + }, + }, + + "user_role_assignment": { + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "user_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.IsUUID, + }, + + "roles": { + Type: pluginsdk.TypeSet, + Required: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.IsUUID, + }, + }, + }, + }, + }, + + "tags": commonschema.Tags(), + } +} + +func (r DevCenterProjectEnvironmentTypeResource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{} +} + +func (r DevCenterProjectEnvironmentTypeResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.DevCenter.V20230401.EnvironmentTypes + subscriptionId := metadata.Client.Account.SubscriptionId + + var model DevCenterProjectEnvironmentTypeResourceModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + devCenterProjectId, err := projects.ParseProjectID(model.DevCenterProjectId) + if err != nil { + return err + } + + id := environmenttypes.NewEnvironmentTypeID(subscriptionId, devCenterProjectId.ResourceGroupName, devCenterProjectId.ProjectName, model.Name) + + existing, err := client.ProjectEnvironmentTypesGet(ctx, id) + if err != nil { + if !response.WasNotFound(existing.HttpResponse) { + return fmt.Errorf("checking for the presence of an existing %s: %+v", id, err) + } + } + if !response.WasNotFound(existing.HttpResponse) { + return metadata.ResourceRequiresImport(r.ResourceType(), id) + } + + identity, err := identity.ExpandSystemAndUserAssignedMapFromModel(model.Identity) + if err != nil { + return err + } + + parameters := environmenttypes.ProjectEnvironmentType{ + Location: pointer.To(location.Normalize(model.Location)), + Properties: &environmenttypes.ProjectEnvironmentTypeProperties{ + DeploymentTargetId: pointer.To(model.DeploymentTargetId), + CreatorRoleAssignment: &environmenttypes.ProjectEnvironmentTypeUpdatePropertiesCreatorRoleAssignment{ + Roles: expandDevCenterProjectEnvironmentTypeCreatorRoleAssignmentRoles(model.CreatorRoleAssignmentRoles), + }, + Status: pointer.To(environmenttypes.EnvironmentTypeEnableStatusEnabled), + }, + Identity: identity, + Tags: pointer.To(model.Tags), + } + + userRoleAssignment, err := expandDevCenterProjectEnvironmentTypeUserRoleAssignment(model.UserRoleAssignment) + if err != nil { + return err + } + parameters.Properties.UserRoleAssignments = userRoleAssignment + + if _, err := client.ProjectEnvironmentTypesCreateOrUpdate(ctx, id, parameters); err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + metadata.SetID(id) + return nil + }, + } +} + +func (r DevCenterProjectEnvironmentTypeResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.DevCenter.V20230401.EnvironmentTypes + + id, err := environmenttypes.ParseEnvironmentTypeID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + resp, err := client.ProjectEnvironmentTypesGet(ctx, *id) + if err != nil { + if response.WasNotFound(resp.HttpResponse) { + return metadata.MarkAsGone(*id) + } + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + state := DevCenterProjectEnvironmentTypeResourceModel{ + Name: id.EnvironmentTypeName, + DevCenterProjectId: projects.NewProjectID(id.SubscriptionId, id.ResourceGroupName, id.ProjectName).ID(), + } + + if model := resp.Model; model != nil { + state.Location = location.Normalize(pointer.From(model.Location)) + state.Tags = pointer.From(model.Tags) + + identity, err := identity.FlattenSystemAndUserAssignedMapToModel(model.Identity) + if err != nil { + return err + } + state.Identity = pointer.From(identity) + + if props := model.Properties; props != nil { + state.DeploymentTargetId = pointer.From(props.DeploymentTargetId) + state.UserRoleAssignment = flattenDevCenterProjectEnvironmentTypeUserRoleAssignment(props.UserRoleAssignments) + + if v := props.CreatorRoleAssignment; v != nil { + state.CreatorRoleAssignmentRoles = flattenDevCenterProjectEnvironmentTypeCreatorRoleAssignmentRoles(v.Roles) + } + } + } + + return metadata.Encode(&state) + }, + } +} + +func (r DevCenterProjectEnvironmentTypeResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.DevCenter.V20230401.EnvironmentTypes + + id, err := environmenttypes.ParseEnvironmentTypeID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + var model DevCenterProjectEnvironmentTypeResourceModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + resp, err := client.ProjectEnvironmentTypesGet(ctx, *id) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", id, err) + } + + if resp.Model == nil { + return fmt.Errorf("retrieving %s: `model` was nil", id) + } + if resp.Model.Properties == nil { + return fmt.Errorf("retrieving %s: `properties` was nil", id) + } + payload := resp.Model + + if metadata.ResourceData.HasChange("creator_role_assignment_roles") { + payload.Properties.CreatorRoleAssignment.Roles = expandDevCenterProjectEnvironmentTypeCreatorRoleAssignmentRoles(model.CreatorRoleAssignmentRoles) + } + + if metadata.ResourceData.HasChange("deployment_target_id") { + payload.Properties.DeploymentTargetId = pointer.To(model.DeploymentTargetId) + } + + if metadata.ResourceData.HasChange("identity") { + identity, err := identity.ExpandSystemAndUserAssignedMapFromModel(model.Identity) + if err != nil { + return err + } + payload.Identity = identity + } + + if metadata.ResourceData.HasChange("user_role_assignment") { + userRoleAssignment, err := expandDevCenterProjectEnvironmentTypeUserRoleAssignment(model.UserRoleAssignment) + if err != nil { + return err + } + payload.Properties.UserRoleAssignments = userRoleAssignment + } + + if metadata.ResourceData.HasChange("tags") { + payload.Tags = pointer.To(model.Tags) + } + + if _, err := client.ProjectEnvironmentTypesCreateOrUpdate(ctx, *id, *payload); err != nil { + return fmt.Errorf("updating %s: %+v", *id, err) + } + + return nil + }, + } +} + +func (r DevCenterProjectEnvironmentTypeResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.DevCenter.V20230401.EnvironmentTypes + + id, err := environmenttypes.ParseEnvironmentTypeID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + if _, err := client.ProjectEnvironmentTypesDelete(ctx, *id); err != nil { + return fmt.Errorf("deleting %s: %+v", *id, err) + } + + return nil + }, + } +} + +func expandDevCenterProjectEnvironmentTypeCreatorRoleAssignmentRoles(input []string) *map[string]environmenttypes.EnvironmentRole { + if len(input) == 0 { + return nil + } + + result := map[string]environmenttypes.EnvironmentRole{} + + for _, v := range input { + result[v] = environmenttypes.EnvironmentRole{} + } + + return &result +} + +func expandDevCenterProjectEnvironmentTypeUserRoleAssignment(input []DevCenterProjectEnvironmentTypeUserRoleAssignment) (*map[string]environmenttypes.UserRoleAssignment, error) { + if len(input) == 0 { + return nil, nil + } + + result := map[string]environmenttypes.UserRoleAssignment{} + + for _, v := range input { + if _, exists := result[v.UserId]; exists { + return nil, fmt.Errorf("`user_id` is duplicate") + } + + result[v.UserId] = environmenttypes.UserRoleAssignment{ + Roles: expandDevCenterProjectEnvironmentTypeUserRoleAssignmentRoles(v.Roles), + } + } + + return &result, nil +} + +func expandDevCenterProjectEnvironmentTypeUserRoleAssignmentRoles(input []string) *map[string]environmenttypes.EnvironmentRole { + if len(input) == 0 { + return nil + } + + result := map[string]environmenttypes.EnvironmentRole{} + + for _, v := range input { + result[v] = environmenttypes.EnvironmentRole{} + } + + return &result +} + +func flattenDevCenterProjectEnvironmentTypeCreatorRoleAssignmentRoles(input *map[string]environmenttypes.EnvironmentRole) []string { + result := make([]string, 0) + + if input == nil { + return result + } + + for k := range *input { + result = append(result, k) + } + + return result +} + +func flattenDevCenterProjectEnvironmentTypeUserRoleAssignment(input *map[string]environmenttypes.UserRoleAssignment) []DevCenterProjectEnvironmentTypeUserRoleAssignment { + results := make([]DevCenterProjectEnvironmentTypeUserRoleAssignment, 0) + + if input == nil { + return results + } + + for k, v := range *input { + result := DevCenterProjectEnvironmentTypeUserRoleAssignment{ + UserId: k, + Roles: flattenDevCenterProjectEnvironmentTypeUserRoleAssignmentRoles(v.Roles), + } + + results = append(results, result) + } + + return results +} + +func flattenDevCenterProjectEnvironmentTypeUserRoleAssignmentRoles(input *map[string]environmenttypes.EnvironmentRole) []string { + result := make([]string, 0) + + if input == nil { + return result + } + + for k := range *input { + result = append(result, k) + } + + return result +} diff --git a/internal/services/devcenter/dev_center_project_environment_type_resource_test.go b/internal/services/devcenter/dev_center_project_environment_type_resource_test.go new file mode 100644 index 000000000000..8c6326ede294 --- /dev/null +++ b/internal/services/devcenter/dev_center_project_environment_type_resource_test.go @@ -0,0 +1,288 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package devcenter_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-sdk/resource-manager/devcenter/2023-04-01/environmenttypes" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" +) + +type DevCenterProjectEnvironmentTypeTestResource struct{} + +func TestAccDevCenterProjectEnvironmentType_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_dev_center_project_environment_type", "test") + r := DevCenterProjectEnvironmentTypeTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccDevCenterProjectEnvironmentType_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_dev_center_project_environment_type", "test") + r := DevCenterProjectEnvironmentTypeTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func TestAccDevCenterProjectEnvironmentType_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_dev_center_project_environment_type", "test") + r := DevCenterProjectEnvironmentTypeTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccDevCenterProjectEnvironmentType_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_dev_center_project_environment_type", "test") + r := DevCenterProjectEnvironmentTypeTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.update(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (r DevCenterProjectEnvironmentTypeTestResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := environmenttypes.ParseEnvironmentTypeID(state.ID) + if err != nil { + return nil, err + } + + resp, err := clients.DevCenter.V20230401.EnvironmentTypes.ProjectEnvironmentTypesGet(ctx, *id) + if err != nil { + return nil, fmt.Errorf("retrieving %s: %+v", *id, err) + } + + return pointer.To(resp.Model != nil), nil +} + +func (r DevCenterProjectEnvironmentTypeTestResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +provider "azurerm" { + features {} +} + +resource "azurerm_dev_center_project_environment_type" "test" { + name = "acctest-et-%s" + location = azurerm_resource_group.test.location + dev_center_project_id = azurerm_dev_center_project.test.id + deployment_target_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}" + + identity { + type = "SystemAssigned" + } +} +`, r.template(data), data.RandomString) +} + +func (r DevCenterProjectEnvironmentTypeTestResource) requiresImport(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_dev_center_project_environment_type" "import" { + name = azurerm_dev_center_project_environment_type.test.name + location = azurerm_dev_center_project_environment_type.test.location + dev_center_project_id = azurerm_dev_center_project_environment_type.test.dev_center_project_id + deployment_target_id = azurerm_dev_center_project_environment_type.test.deployment_target_id + + identity { + type = "SystemAssigned" + } +} +`, r.basic(data)) +} + +func (r DevCenterProjectEnvironmentTypeTestResource) complete(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +provider "azurerm" { + features {} +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acctestuai%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +data "azurerm_role_definition" "test" { + name = "Owner" +} + +resource "azurerm_dev_center_project_environment_type" "test" { + name = "acctest-et-%s" + location = azurerm_resource_group.test.location + dev_center_project_id = azurerm_dev_center_project.test.id + deployment_target_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}" + + identity { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.test.id] + } + + creator_role_assignment_roles = [split("/", data.azurerm_role_definition.test.id)[length(split("/", data.azurerm_role_definition.test.id)) - 1]] + + user_role_assignment { + user_id = azurerm_user_assigned_identity.test.principal_id + roles = [split("/", data.azurerm_role_definition.test.id)[length(split("/", data.azurerm_role_definition.test.id)) - 1]] + } + + tags = { + Env = "Test" + } +} +`, r.template(data), data.RandomString, data.RandomString) +} + +func (r DevCenterProjectEnvironmentTypeTestResource) update(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +provider "azurerm" { + features {} +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acctestuai%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_user_assigned_identity" "test2" { + name = "acctestuai2%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +data "azurerm_role_definition" "test" { + name = "Owner" +} + +data "azurerm_role_definition" "test2" { + name = "Contributor" +} + +resource "azurerm_dev_center_project_environment_type" "test" { + name = "acctest-et-%s" + location = azurerm_resource_group.test.location + dev_center_project_id = azurerm_dev_center_project.test.id + deployment_target_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}" + + identity { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.test.id, azurerm_user_assigned_identity.test2.id] + } + + creator_role_assignment_roles = [split("/", data.azurerm_role_definition.test.id)[length(split("/", data.azurerm_role_definition.test.id)) - 1], split("/", data.azurerm_role_definition.test2.id)[length(split("/", data.azurerm_role_definition.test2.id)) - 1]] + + user_role_assignment { + user_id = azurerm_user_assigned_identity.test.principal_id + roles = [split("/", data.azurerm_role_definition.test.id)[length(split("/", data.azurerm_role_definition.test.id)) - 1], split("/", data.azurerm_role_definition.test2.id)[length(split("/", data.azurerm_role_definition.test2.id)) - 1]] + } + + user_role_assignment { + user_id = azurerm_user_assigned_identity.test2.principal_id + roles = [split("/", data.azurerm_role_definition.test2.id)[length(split("/", data.azurerm_role_definition.test2.id)) - 1]] + } + + tags = { + Env = "Test2" + } +} +`, r.template(data), data.RandomString, data.RandomString, data.RandomString) +} + +func (r DevCenterProjectEnvironmentTypeTestResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestrg-dcpet-%d" + location = "%s" +} + +resource "azurerm_dev_center" "test" { + name = "acctest-dc-%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + + identity { + type = "SystemAssigned" + } +} + +resource "azurerm_dev_center_environment_type" "test" { + name = "acctest-et-%s" + dev_center_id = azurerm_dev_center.test.id +} + +resource "azurerm_dev_center_project" "test" { + name = "acctest-dcp-%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + dev_center_id = azurerm_dev_center.test.id + + depends_on = [azurerm_dev_center_environment_type.test] +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomString, data.RandomString) +} diff --git a/internal/services/devcenter/registration.go b/internal/services/devcenter/registration.go index 5a16d408433d..4d14e520bfad 100644 --- a/internal/services/devcenter/registration.go +++ b/internal/services/devcenter/registration.go @@ -36,6 +36,7 @@ func (r Registration) Resources() []sdk.Resource { DevCenterDevBoxDefinitionResource{}, DevCenterEnvironmentTypeResource{}, DevCenterNetworkConnectionResource{}, + DevCenterProjectEnvironmentTypeResource{}, } return append(resources, r.autoRegistration.Resources()...) } diff --git a/internal/services/devcenter/validate/dev_center_project_environment_type_name.go b/internal/services/devcenter/validate/dev_center_project_environment_type_name.go new file mode 100644 index 000000000000..ca32018b2f6e --- /dev/null +++ b/internal/services/devcenter/validate/dev_center_project_environment_type_name.go @@ -0,0 +1,23 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validate + +import ( + "fmt" + "regexp" +) + +func DevCenterProjectEnvironmentTypeName(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return + } + + if !regexp.MustCompile("^[a-zA-Z0-9][a-zA-Z0-9-_.]{2,62}$").MatchString(v) { + errors = append(errors, fmt.Errorf("%q must start with an alphanumeric character, may contain alphanumeric characters, dashes, underscores or periods and must be between 3 and 63 characters long", k)) + } + + return warnings, errors +} diff --git a/internal/services/devcenter/validate/dev_center_project_environment_type_name_test.go b/internal/services/devcenter/validate/dev_center_project_environment_type_name_test.go new file mode 100644 index 000000000000..95d30e4e34c4 --- /dev/null +++ b/internal/services/devcenter/validate/dev_center_project_environment_type_name_test.go @@ -0,0 +1,61 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validate + +import ( + "strings" + "testing" +) + +func TestDevCenterProjectEnvironmentTypeName(t *testing.T) { + testCases := []struct { + Input string + Expected bool + }{ + { + Input: "", + Expected: false, + }, + { + Input: "a", + Expected: false, + }, + { + Input: "a8a", + Expected: true, + }, + { + Input: "a-8.a", + Expected: true, + }, + { + Input: "aa-", + Expected: true, + }, + { + Input: "aa.", + Expected: true, + }, + { + Input: strings.Repeat("s", 62), + Expected: true, + }, + { + Input: strings.Repeat("s", 63), + Expected: true, + }, + { + Input: strings.Repeat("s", 64), + Expected: false, + }, + } + + for _, v := range testCases { + _, errors := DevCenterProjectEnvironmentTypeName(v.Input, "name") + result := len(errors) == 0 + if result != v.Expected { + t.Fatalf("Expected the result to be %t but got %t (and %d errors)", v.Expected, result, len(errors)) + } + } +} diff --git a/website/docs/r/dev_center_project_environment_type.html.markdown b/website/docs/r/dev_center_project_environment_type.html.markdown new file mode 100644 index 000000000000..c067d4507072 --- /dev/null +++ b/website/docs/r/dev_center_project_environment_type.html.markdown @@ -0,0 +1,118 @@ +--- +subcategory: "Dev Center" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_dev_center_project_environment_type" +description: |- + Manages a Dev Center Project Environment Type. +--- + +# azurerm_dev_center_project_environment_type + +Manages a Dev Center Project Environment Type. + +## Example Usage + +```hcl +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_dev_center" "example" { + name = "example-dc" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + + identity { + type = "SystemAssigned" + } +} + +resource "azurerm_dev_center_environment_type" "example" { + name = "example-et" + dev_center_id = azurerm_dev_center.example.id +} + +resource "azurerm_dev_center_project" "example" { + name = "example-dcp" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + dev_center_id = azurerm_dev_center.example.id + + depends_on = [azurerm_dev_center_environment_type.example] +} + +resource "azurerm_dev_center_project_environment_type" "example" { + name = "example-et" + location = azurerm_resource_group.example.location + dev_center_project_id = azurerm_dev_center_project.example.id + deployment_target_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}" + + identity { + type = "SystemAssigned" + } +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) Specifies the name of this Dev Center Project Environment Type. Changing this forces a new resource to be created. + +* `location` - (Required) The Azure Region where the Dev Center Project Environment Type should exist. Changing this forces a new resource to be created. + +* `dev_center_project_id` - (Required) The ID of the associated Dev Center Project. Changing this forces a new resource to be created. + +* `deployment_target_id` - (Required) The ID of the subscription that the Environment Type will be mapped to. The environment's resources will be deployed into this subscription. + +* `identity` - (Required) An `identity` block as defined below. + +* `creator_role_assignment_roles` - (Optional) A list of roles to assign to the environment creator. + +* `user_role_assignment` - (Optional) A `user_role_assignment` block as defined below. + +* `tags` - (Optional) A mapping of tags which should be assigned to the Dev Center Project Environment Type. + +--- + +An `identity` block supports the following: + +* `type` - (Required) The type of identity used for this Dev Center Project Environment Type. Possible values are `SystemAssigned`, `UserAssigned` and `SystemAssigned, UserAssigned`. + +* `identity_ids` - (Optional) The ID of the User Assigned Identity which should be assigned to this Dev Center Project Environment Type. + +-> **Note:** `identity_ids` is required when `type` is set to `UserAssigned` or `SystemAssigned, UserAssigned`. + +--- + +A `user_role_assignment` block supports the following: + +* `user_id` - (Required) The user object ID that is assigned roles. + +* `roles` - (Required) A list of roles to assign to the `user_id`. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Dev Center Project Environment Type. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating this Dev Center Project Environment Type. +* `delete` - (Defaults to 30 minutes) Used when deleting this Dev Center Project Environment Type. +* `read` - (Defaults to 5 minutes) Used when retrieving this Dev Center Project Environment Type. +* `update` - (Defaults to 30 minutes) Used when updating this Dev Center Project Environment Type. + +## Import + +An existing Dev Center Project Environment Type can be imported into Terraform using the `resource id`, e.g. + +```shell +terraform import azurerm_dev_center_project_environment_type.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.DevCenter/projects/project1/environmentTypes/et1 +```