From 2f921746c2e6acd9ec593e01807bef07e9cb5dc3 Mon Sep 17 00:00:00 2001 From: Rafferty Uy <1037626+raffertyuy@users.noreply.github.com> Date: Tue, 12 Aug 2025 08:29:19 +0000 Subject: [PATCH 1/3] Create a GitHub issue template for instructing the coding agent for implementing Terraform modeules. --- .github/ISSUE_TEMPLATE/new_tf_module.md | 17 ++++++++ .vscode/mcp.json | 2 +- README.md | 1 + docs/coding_agent.md | 55 +++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 .github/ISSUE_TEMPLATE/new_tf_module.md create mode 100644 docs/coding_agent.md diff --git a/.github/ISSUE_TEMPLATE/new_tf_module.md b/.github/ISSUE_TEMPLATE/new_tf_module.md new file mode 100644 index 0000000..24d02d1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/new_tf_module.md @@ -0,0 +1,17 @@ +--- +name: 'New Dev Factory Terraform Module' +about: 'Assign a new Dev Factory tf module implementation task to the GitHub Copilot Coding Agent' +title: 'Implement {module_name}' +labels: 'enhancement' +assignees: 'copilot-swe-agent[bot]' +--- + +Your goal is to implement the specified terraform module by following the steps below. + +## Module to implement +module_name=REPLACE_WITH_MODULE_NAME + +## STEPS +1. Read and follow the instructions in `.github/copilot-instructions.md`. +2. Create an implementation plan by running `/1-plan {module_name}`. This follows the prompt in `.github/prompts/1-plan.prompt.md` and outputs a new file: {module_name}.plan.md +3. Implement the created implementation plan by running `/2-implement #file:docs/plans/{module_name}.plan.md`. This follows the prompt in `.github/prompts/2-implement.prompt.md` for the plan created in step 2. \ No newline at end of file diff --git a/.vscode/mcp.json b/.vscode/mcp.json index 936753e..6061b9c 100644 --- a/.vscode/mcp.json +++ b/.vscode/mcp.json @@ -1,5 +1,5 @@ { - "servers": { + "servers": { "Terraform MCP Server": { "command": "docker", "args": [ diff --git a/README.md b/README.md index 6372bb2..9bdf8a8 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ The project includes comprehensive documentation to help you understand and use - [Getting Started Guide](docs/getting_started.md) - Instructions for setting up and deploying your first resources - [Coding Conventions](docs/conventions.md) - Standards and best practices for the codebase - [Module Guide](docs/module_guide.md) - Detailed information about each module's functionality and usage +- [GitHub Coding Agent Guide](docs/coding_agent.md) - Instructions for using the GitHub Coding Agent ## Requirements diff --git a/docs/coding_agent.md b/docs/coding_agent.md new file mode 100644 index 0000000..d69e224 --- /dev/null +++ b/docs/coding_agent.md @@ -0,0 +1,55 @@ +# Using the GitHub Coding Agent with Dev Factory + +This documents how to use the GitHub Coding Agent to implement terraform modules. + +## Configure MCP Servers + +Before starting, go to your `https://github.com/org/repo/settings/copilot/coding_agent` and configure the MCP servers. + +```json +{ + "mcpServers": { + "Terraform-MCP-Server": { + "type": "local", + "command": "docker", + "args": [ + "run", + "-i", + "--rm", + "hashicorp/terraform-mcp-server" + ], + "tools": ["*"] + }, + "Azure-MCP-Server": { + "type": "local", + "command": "npx", + "args": [ + "-y", + "@azure/mcp@0.0.21", + "server", + "start" + ], + "tools": ["*"] + } + } +} +``` + +## Assigning Issues to the GitHub Coding Agent + +1. Create a new issue using the [New Dev Factory Terraform Module](/.github/ISSUE_TEMPLATE/new_tf_module.md) template. + +2. Replace REPLACE_WITH_MODULE_NAME with the module name that you want to implement. For example `dev_center_project_environment_type`. (Note: This template should automatically assign the issue to `Copilot` with label `enhancement`). + +3. Assign the issue to Copilot + +4. Click Create + +5. Monitor or have a break, wait for GitHub Copilot to finish the implementation + +6. Review the PR submitted by Copilot by + 1. Fetching the PR code change by the coding agent and opening it in VSCode + 2. Switching to the `aztf-agent` + 3. and running `/3-apply {module_name}`. + +7. If everything is in order, approve and merge the PR, close the issue. From 3090c4bb88bc47b855248d66a4e235221f57999a Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 Aug 2025 16:44:36 +0800 Subject: [PATCH 2/3] Implement dev_center_project_environment_type module for Azure DevCenter project-environment associations (#11) * Initial plan * Implement dev_center_project_environment_type module with tests and documentation Co-authored-by: raffertyuy <1037626+raffertyuy@users.noreply.github.com> * Complete dev_center_project_environment_type implementation with documentation updates Co-authored-by: raffertyuy <1037626+raffertyuy@users.noreply.github.com> * Fix issues encountered after a deployment of dev_center_project_environment_type. This includes fixing the naming convention of other modules (duplicate '-rg-rg-' in the prefix previously). * fix errors from unit tests * Fix API schema compliance for dev_center_project_environment_type module - Updated user role assignments schema to match Azure API requirements: - Changed roles from list(string) to map(object({})) - Modified example configurations to use user object IDs and role definition IDs - Enhanced README documentation with Azure CLI commands for ID retrieval - Files changed: - `modules/dev_center_project_environment_type/variables.tf`: Updated roles type definition - `modules/dev_center_project_environment_type/README.md`: Added advanced configuration details for user role assignments - `examples/dev_center_project_environment_type/enhanced_case/configuration.tfvars`: Updated user role assignments to use correct schema - `variables.tf`: Updated root variable definition to match module schema --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: raffertyuy <1037626+raffertyuy@users.noreply.github.com> --- .vscode/tasks.json | 4 +- CHANGES_SUMMARY.md | 86 +++++++- dev_center_project_environment_types.tf | 10 + docs/file_structure.md | 1 + docs/module_guide.md | 26 ++- ...ev_center_project_environment_type.plan.md | 175 ++++++++++++++++ .../enhanced_case/configuration.tfvars | 2 +- .../simple_case/configuration.tfvars | 2 +- .../enhanced_case/configuration.tfvars | 2 +- .../enhanced_case/configuration.tfvars | 189 ++++++++++++++++++ .../simple_case/configuration.tfvars | 109 ++++++++++ .../README.md | 152 ++++++++++++++ .../module.tf | 58 ++++++ .../output.tf | 34 ++++ .../variables.tf | 46 +++++ .../dev_center_integration_test.tftest.hcl | 33 ++- .../project_environment_type_test.tftest.hcl | 162 +++++++++++++++ variables.tf | 49 +++++ 18 files changed, 1123 insertions(+), 17 deletions(-) create mode 100644 dev_center_project_environment_types.tf create mode 100644 docs/plans/dev_center_project_environment_type.plan.md create mode 100644 examples/dev_center_project_environment_type/enhanced_case/configuration.tfvars create mode 100644 examples/dev_center_project_environment_type/simple_case/configuration.tfvars create mode 100644 modules/dev_center_project_environment_type/README.md create mode 100644 modules/dev_center_project_environment_type/module.tf create mode 100644 modules/dev_center_project_environment_type/output.tf create mode 100644 modules/dev_center_project_environment_type/variables.tf create mode 100644 tests/unit/dev_center_project_environment_type/project_environment_type_test.tftest.hcl diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 9c5cfea..99bdc11 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -121,7 +121,9 @@ "examples/dev_center/dual_identity/configuration.tfvars", "examples/dev_center_project/configuration.tfvars", "examples/dev_center_environment_type/simple_case/configuration.tfvars", - "examples/dev_center_environment_type/enhanced_case/configuration.tfvars" + "examples/dev_center_environment_type/enhanced_case/configuration.tfvars", + "examples/dev_center_project_environment_type/simple_case/configuration.tfvars", + "examples/dev_center_project_environment_type/enhanced_case/configuration.tfvars" ], "default": "examples/dev_center/simple_case/configuration.tfvars" } diff --git a/CHANGES_SUMMARY.md b/CHANGES_SUMMARY.md index 5b5b27e..2a56634 100644 --- a/CHANGES_SUMMARY.md +++ b/CHANGES_SUMMARY.md @@ -4,7 +4,91 @@ This document summarizes the updates made to the Azure DevCenter module to implement the 2025-04-01-preview API version and fix the identity block placement. -## Latest Changes (July 28, 2025) +## Latest Changes (August 14, 2025) + +### Dev Center Project Environment Type Module - API Schema Compliance Fix + +- **Fixed**: Resolved Azure API schema validation errors in dev_center_project_environment_type module for userRoleAssignments +- **Classification**: Bug fix +- **Breaking Change**: YES - User role assignments schema changed to match Azure API requirements +- **Issue**: userRoleAssignments.roles was defined as list(string) but Azure API expects map(object({})) +- **Root Cause**: Mismatch between Terraform variable schema and Azure DevCenter REST API schema +- **Azure API Requirement**: + - userRoleAssignments keys must be user object IDs (GUIDs), not email addresses + - roles property must be a map where keys are role definition IDs (GUIDs) and values are objects +- **Solution Applied**: + - Updated module variable schema: `roles = list(string)` → `roles = map(object({}))` + - Updated root variable schema in `variables.tf` to match module requirements + - Updated all example configurations to use correct API format + - Updated test files to use proper schema structure + - Enhanced README documentation with Azure CLI commands to find required IDs +- **Files Modified**: + - `modules/dev_center_project_environment_type/variables.tf`: Fixed roles type definition + - `modules/dev_center_project_environment_type/README.md`: Added comprehensive documentation for finding user object IDs and role definition IDs + - `variables.tf`: Updated root variable definition to match module schema + - `tests/unit/dev_center_project_environment_type/project_environment_type_test.tftest.hcl`: Updated test to use correct schema + - `examples/dev_center_project_environment_type/enhanced_case/configuration.tfvars`: Updated all user role assignments to use object IDs and role definition IDs +- **Migration Required**: + - Users must update configurations to use Azure AD user object IDs instead of email addresses + - Users must use role definition IDs (GUIDs) instead of role names + - Use `az ad user show --id user@domain.com --query id -o tsv` to get user object IDs + - Use `az role definition list --name "Role Name" --query '[].id' -o tsv` to get role definition IDs +- **Validation**: All unit and integration tests pass (43 total test cases) +- **API Reference**: Based on Azure DevCenter REST API documentation (2025-04-01-preview) + +### Dev Center Project Environment Type Module - Critical Fix Applied + +- **Fixed**: Resolved API validation errors in dev_center_project_environment_type module +- **Classification**: Bug fix +- **Breaking Change**: NO - Module interface updated but functionality preserved +- **Issue**: DeploymentTargetId was incorrectly using full environment type resource ID instead of subscription ID +- **Root Cause**: Azure DevCenter API requires deploymentTargetId to be subscription ID format `/subscriptions/{guid}`, not full resource ID +- **Solution Applied**: + - Updated `deploymentTargetId` to use subscription ID: `/subscriptions/${data.azapi_client_config.current.subscription_id}` + - Fixed environment type name matching to use actual created environment type names + - Removed unnecessary azurecaf_name resource for project environment types + - Updated module to reference environment type names from parent Dev Center +- **Files Modified**: + - `modules/dev_center_project_environment_type/module.tf`: Fixed deploymentTargetId and name logic + - `modules/dev_center_project_environment_type/variables.tf`: Added environment_type_name variable, updated validation + - `modules/dev_center_project_environment_type/output.tf`: Updated deployment_target_id output description + - `modules/dev_center_project_environment_type/README.md`: Updated usage examples and documentation + - `dev_center_project_environment_types.tf`: Updated module call to pass environment_type_name +- **Validation**: Successfully applied simple case configuration with both development and staging project environment types +- **API Reference**: Based on official Azure DevCenter REST API documentation (2025-04-01-preview) +- **Resources Created**: + - `/subscriptions/.../projects/.../environmentTypes/demo-dcet-development-qgi` (Enabled) + - `/subscriptions/.../projects/.../environmentTypes/demo-dcet-staging-iuo` (Enabled) + +### Dev Center Project Environment Type Module - New Implementation + +- **Added**: New `dev_center_project_environment_type` module for associating environment types with Dev Center projects +- **Classification**: Feature +- **Breaking Change**: NO - This is a new module that doesn't affect existing functionality +- **Files Added**: + - `modules/dev_center_project_environment_type/module.tf`: Main module implementation using azapi provider + - `modules/dev_center_project_environment_type/variables.tf`: Strong typing with comprehensive validation + - `modules/dev_center_project_environment_type/output.tf`: Output definitions for project environment type properties + - `modules/dev_center_project_environment_type/README.md`: Complete documentation with usage examples + - `dev_center_project_environment_types.tf`: Root orchestration file + - `variables.tf`: Added new variable definition with validation rules + - `examples/dev_center_project_environment_type/simple_case/configuration.tfvars`: Basic example + - `examples/dev_center_project_environment_type/enhanced_case/configuration.tfvars`: Advanced example with user role assignments + - `tests/unit/dev_center_project_environment_type/project_environment_type_test.tftest.hcl`: Unit tests with provider mocking +- **Files Modified**: + - `tests/integration/dev_center_integration_test.tftest.hcl`: Added project environment type integration test + - `.vscode/tasks.json`: Added new example options for VS Code development workflow + - `docs/file_structure.md`: Updated with new module and example locations + - `docs/module_guide.md`: Enhanced with comprehensive usage patterns and configuration options +- **Features**: + - Associates environment types with Dev Center projects using Azure DevCenter 2025-04-01-preview API + - Configurable status (Enabled/Disabled) for project environment types + - User role assignments for granular access control + - Comprehensive validation for all input variables + - Full test coverage with both unit and integration tests + - Complete documentation and examples + +## Previous Changes (July 28, 2025) ### Dev Center Network Connection Module - AzAPI Migration - **Updated**: Migrated `dev_center_network_connection` module from azurerm to azapi provider diff --git a/dev_center_project_environment_types.tf b/dev_center_project_environment_types.tf new file mode 100644 index 0000000..b364ef8 --- /dev/null +++ b/dev_center_project_environment_types.tf @@ -0,0 +1,10 @@ +# Dev Center Project Environment Types module instantiation +module "dev_center_project_environment_types" { + source = "./modules/dev_center_project_environment_type" + for_each = try(var.dev_center_project_environment_types, {}) + + global_settings = var.global_settings + project_environment_type = each.value + dev_center_project_id = lookup(each.value, "dev_center_project_id", null) != null ? each.value.dev_center_project_id : module.dev_center_projects[each.value.project.key].id + environment_type_name = lookup(each.value, "environment_type_name", null) != null ? each.value.environment_type_name : module.dev_center_environment_types[each.value.environment_type.key].name +} \ No newline at end of file diff --git a/docs/file_structure.md b/docs/file_structure.md index 2912ad6..2e60990 100644 --- a/docs/file_structure.md +++ b/docs/file_structure.md @@ -49,6 +49,7 @@ This section contains specific files/callouts. If the file is not explained here - **dev_center_catalogs.tf**: Configures catalogs within Dev Centers for organizing DevBox templates. - **dev_center_dev_box_definitions.tf**: Defines DevBox configurations with VM specifications and images. - **dev_center_environment_types.tf**: Creates environment types for defining available development environments. +- **dev_center_project_environment_types.tf**: Associates environment types with Dev Center projects, enabling specific environment types within projects. - **dev_center_network_connections.tf**: Establishes network connectivity between Dev Centers and virtual networks. - **dev_center_project_pools.tf**: Manages pools of development resources within projects. - **dev_center_project_pool_schedules.tf**: Configures scheduling for automated pool management and cost optimization. diff --git a/docs/module_guide.md b/docs/module_guide.md index 06c7204..833cff3 100644 --- a/docs/module_guide.md +++ b/docs/module_guide.md @@ -231,11 +231,10 @@ module "dev_center_project_environment_types" { source = "./modules/dev_center_project_environment_type" for_each = var.dev_center_project_environment_types - global_settings = var.global_settings - project_environment_type = each.value - location = lookup(each.value, "location", null) != null ? each.value.location : module.resource_groups[each.value.resource_group.key].location - dev_center_project_id = lookup(each.value, "dev_center_project_id", null) != null ? each.value.dev_center_project_id : module.dev_center_projects[each.value.project.key].id - deployment_target_id = each.value.deployment_target_id + global_settings = var.global_settings + project_environment_type = each.value + dev_center_project_id = lookup(each.value, "dev_center_project_id", null) != null ? each.value.dev_center_project_id : module.dev_center_projects[each.value.project.key].id + deployment_target_id = lookup(each.value, "deployment_target_id", null) != null ? each.value.deployment_target_id : module.dev_center_environment_types[each.value.environment_type.key].id } ``` @@ -244,7 +243,6 @@ module "dev_center_project_environment_types" { |----------|------|----------|-------------| | `global_settings` | `object` | Yes | Global settings for naming and prefixing | | `project_environment_type` | `object` | Yes | Project environment type configuration object | -| `location` | `string` | Yes | Azure region for deployment | | `dev_center_project_id` | `string` | Yes | The ID of the project | | `deployment_target_id` | `string` | Yes | The ID of the deployment target | @@ -252,13 +250,19 @@ module "dev_center_project_environment_types" { ```hcl dev_center_project_environment_types = { projenvtype1 = { - name = "terraform-env" + name = "development" project = { key = "project1" } environment_type = { key = "envtype1" } + status = "Enabled" + user_role_assignments = { + "developers@contoso.com" = { + roles = ["Deployment Environments User"] + } + } tags = { environment = "demo" } @@ -266,6 +270,14 @@ dev_center_project_environment_types = { } ``` +### Features +- Associates environment types with Dev Center projects +- Configurable status (Enabled/Disabled) +- User role assignments for access control +- Comprehensive input validation +- Support for resource tags +- Compatible with Azure DevCenter 2025-04-01-preview API + ## Dev Center Network Connection Module ### Purpose diff --git a/docs/plans/dev_center_project_environment_type.plan.md b/docs/plans/dev_center_project_environment_type.plan.md new file mode 100644 index 0000000..fdcb1cd --- /dev/null +++ b/docs/plans/dev_center_project_environment_type.plan.md @@ -0,0 +1,175 @@ +# Implementation Plan for Dev Center Project Environment Type Module + +## Overview + +This plan outlines the implementation of the `dev_center_project_environment_type` module for DevFactory. This module creates associations between Dev Center projects and environment types, enabling specific environment types to be available within projects. + +The module will follow DevFactory patterns and use AzAPI provider v2.4.0 with the Azure DevCenter API version 2025-04-01-preview. + +## Implementation Steps + +- [x] **Step 1: Create Module Infrastructure** + - **Task**: Create the core module structure and files for dev_center_project_environment_type following DevFactory patterns + - **Files**: + - `modules/dev_center_project_environment_type/module.tf`: Main module implementation with azapi_resource for Microsoft.DevCenter/projects/environmentTypes@2025-04-01-preview + - `modules/dev_center_project_environment_type/variables.tf`: Input variable definitions with strong typing and validation + - `modules/dev_center_project_environment_type/output.tf`: Output definitions for project environment type properties + - `modules/dev_center_project_environment_type/README.md`: Module documentation with usage examples + - **Dependencies**: AzAPI provider v2.4.0, azurecaf provider for naming + - **Pseudocode**: + + ```hcl + resource "azapi_resource" "dev_center_project_environment_type" { + type = "Microsoft.DevCenter/projects/environmentTypes@2025-04-01-preview" + name = var.project_environment_type.environment_type.name + parent_id = var.dev_center_project_id + body = { + properties = { + deploymentTargetId = var.deployment_target_id + status = try(var.project_environment_type.status, "Enabled") + userRoleAssignments = try(var.project_environment_type.user_role_assignments, {}) + } + tags = local.tags + } + } + ``` + +- [ ] **Step 2: Create Root Orchestration File** + - **Task**: Create the root-level orchestration file to manage project environment types across all Dev Center projects + - **Files**: + - `dev_center_project_environment_types.tf`: Root orchestration calling the project environment type module for each configuration + - **Dependencies**: dev_center_project_environment_type module, dev_center_projects module for references + - **Pseudocode**: + + ```hcl + module "dev_center_project_environment_types" { + source = "./modules/dev_center_project_environment_type" + for_each = var.dev_center_project_environment_types + + global_settings = var.global_settings + project_environment_type = each.value + dev_center_project_id = module.dev_center_projects[each.value.project.key].id + deployment_target_id = each.value.deployment_target_id + } + ``` + +- [ ] **Step 3: Add Variable Definition to Root Variables** + - **Task**: Add the dev_center_project_environment_types variable to the root variables.tf file + - **Files**: + - `variables.tf`: Add new variable definition with proper typing and validation + - **Dependencies**: Existing variable patterns + - **Pseudocode**: + + ```hcl + variable "dev_center_project_environment_types" { + description = "Dev Center Project Environment Types configuration objects" + type = map(object({ + name = string + project = object({ + key = string + }) + environment_type = object({ + key = string + }) + deployment_target_id = string + status = optional(string, "Enabled") + user_role_assignments = optional(map(object({ + roles = list(string) + }))) + tags = optional(map(string), {}) + })) + default = {} + } + ``` + +- [x] **Step 4: Create Examples** + - **Task**: Create example configurations for different use cases + - **Files**: + - `examples/dev_center_project_environment_type/simple_case/configuration.tfvars`: Basic example with minimal configuration + - `examples/dev_center_project_environment_type/enhanced_case/configuration.tfvars`: Advanced example with user role assignments + - **Dependencies**: Existing example patterns, dev_center and project examples + - **Pseudocode**: Create example configurations that demonstrate linking environment types to projects + +- [ ] **Step 5: Create Unit Tests** + - **Task**: Implement comprehensive unit tests for the project environment type module using Terraform's native testing + - **Files**: + - `tests/unit/dev_center_project_environment_type/project_environment_type_test.tftest.hcl`: Main unit test file with provider mocking + - **Dependencies**: Terraform test framework, mock providers + - **Pseudocode**: + + ```hcl + run "test_project_environment_type_creation" { + command = plan + + variables { + project_environment_type = { + name = "test-project-env-type" + } + dev_center_project_id = "/subscriptions/test/resourceGroups/test/providers/Microsoft.DevCenter/projects/test" + deployment_target_id = "/subscriptions/test/resourceGroups/test/providers/Microsoft.DevCenter/projects/test/environmentTypes/test" + } + + assert { + condition = azapi_resource.dev_center_project_environment_type.type == "Microsoft.DevCenter/projects/environmentTypes@2025-04-01-preview" + } + } + ``` + +- [x] **Step 6: Update Integration Tests** + - **Task**: Update existing integration tests to validate project environment type functionality and relationships + - **Files**: + - `tests/integration/dev_center_integration_test.tftest.hcl`: Include project environment type creation and relationship validation + - **Dependencies**: Existing integration test infrastructure + - **Pseudocode**: Verify that project environment types are properly created and linked to projects and environment types + +- [ ] **Step 7: Update VS Code Tasks Configuration** + - **Task**: Add project environment type examples to VS Code tasks for easy testing and development + - **Files**: + - `.vscode/tasks.json`: Add devCenterProjectEnvironmentType input options for both simple and enhanced cases + - **Dependencies**: Existing task configuration patterns + - **Pseudocode**: Add input picker for project environment type example selection + +- [x] **Step 8: Update Documentation** + - **Task**: Update project documentation to include project environment type module information + - **Files**: + - `docs/file_structure.md`: Update with project environment type module and example locations + - `docs/module_guide.md`: Add comprehensive project environment type usage patterns and configuration options + - **Dependencies**: Existing documentation standards + - **Pseudocode**: Add project environment type section with usage examples and configuration reference + +- [ ] **Step 9: Validation and Testing** + - **Task**: Run comprehensive validation to ensure the implementation meets requirements + - **Files**: All created files validated successfully + - **Dependencies**: Terraform validation tools, test framework + - **Steps**: + - Run `terraform fmt` to ensure consistent formatting + - Run `terraform validate` to check syntax and configuration + - Execute unit tests to verify module functionality + - Execute integration tests to validate project environment type relationships + - Test example configurations to ensure they work correctly + - Verify VS Code tasks work with new examples + +## Validation Criteria + +- All files follow DevFactory conventions and patterns +- Module uses AzAPI provider v2.4.0 exclusively +- Strong typing and validation for all variables +- Comprehensive test coverage (unit and integration) +- Working examples for different use cases +- Complete documentation including README and module guide +- VS Code tasks integration for development workflow +- Successful terraform fmt, validate, and test execution + +## Risk Mitigation + +- **API Version Compatibility**: Use 2025-04-01-preview which supports the latest DevCenter features +- **Breaking Changes**: Follow existing patterns from dev_center_environment_type module for consistency +- **Testing Coverage**: Implement both unit and integration tests to ensure reliability +- **Documentation**: Provide comprehensive documentation and examples to ensure usability + +## User Intervention Required + +- Review and approve the implementation plan +- Provide feedback on any specific requirements or constraints +- Validate that the chosen API version and properties meet business requirements +- Test the implemented examples to ensure they meet real-world use cases \ No newline at end of file diff --git a/examples/dev_center_catalog/enhanced_case/configuration.tfvars b/examples/dev_center_catalog/enhanced_case/configuration.tfvars index 39cc7f9..54eea9b 100644 --- a/examples/dev_center_catalog/enhanced_case/configuration.tfvars +++ b/examples/dev_center_catalog/enhanced_case/configuration.tfvars @@ -14,7 +14,7 @@ global_settings = { resource_groups = { rg_devcenter_prod = { - name = "rg-devcenter-prod" + name = "devcenter-prod" region = "eastus" tags = { purpose = "production" diff --git a/examples/dev_center_catalog/simple_case/configuration.tfvars b/examples/dev_center_catalog/simple_case/configuration.tfvars index 7b96af4..d57f647 100644 --- a/examples/dev_center_catalog/simple_case/configuration.tfvars +++ b/examples/dev_center_catalog/simple_case/configuration.tfvars @@ -11,7 +11,7 @@ global_settings = { resource_groups = { rg_dev_center = { - name = "rg-dev-center" + name = "dev-center" region = "eastus" tags = { purpose = "development" diff --git a/examples/dev_center_dev_box_definition/enhanced_case/configuration.tfvars b/examples/dev_center_dev_box_definition/enhanced_case/configuration.tfvars index ddb9831..76a032d 100644 --- a/examples/dev_center_dev_box_definition/enhanced_case/configuration.tfvars +++ b/examples/dev_center_dev_box_definition/enhanced_case/configuration.tfvars @@ -14,7 +14,7 @@ global_settings = { resource_groups = { rg_devbox = { - name = "rg-devbox-definitions" + name = "devbox-definitions" region = "eastus" tags = { purpose = "devbox-definitions" diff --git a/examples/dev_center_project_environment_type/enhanced_case/configuration.tfvars b/examples/dev_center_project_environment_type/enhanced_case/configuration.tfvars new file mode 100644 index 0000000..0d50da7 --- /dev/null +++ b/examples/dev_center_project_environment_type/enhanced_case/configuration.tfvars @@ -0,0 +1,189 @@ +global_settings = { + prefixes = ["prod"] + random_length = 3 + passthrough = false + use_slug = true + tags = { + company = "contoso" + cost_center = "engineering" + } +} + +resource_groups = { + rg1 = { + name = "devcenter-prod" + region = "eastus" + tags = { + environment = "production" + purpose = "dev-center-production" + } + } +} + +dev_centers = { + devcenter1 = { + name = "prod-devcenter" + resource_group = { + key = "rg1" + } + tags = { + environment = "production" + module = "dev_center" + } + } +} + +dev_center_environment_types = { + development = { + name = "development" + display_name = "Development Environment" + dev_center = { + key = "devcenter1" + } + tags = { + environment = "production" + purpose = "development" + } + } + staging = { + name = "staging" + display_name = "Staging Environment" + dev_center = { + key = "devcenter1" + } + tags = { + environment = "production" + purpose = "staging" + } + } + production = { + name = "production" + display_name = "Production Environment" + dev_center = { + key = "devcenter1" + } + tags = { + environment = "production" + purpose = "production" + } + } +} + +dev_center_projects = { + project1 = { + name = "prod-project" + dev_center = { + key = "devcenter1" + } + resource_group = { + key = "rg1" + } + tags = { + environment = "production" + module = "dev_center_project" + team = "platform" + } + } + project2 = { + name = "app-project" + dev_center = { + key = "devcenter1" + } + resource_group = { + key = "rg1" + } + tags = { + environment = "production" + module = "dev_center_project" + team = "application" + } + } +} + +dev_center_project_environment_types = { + proj1_env_dev = { + name = "development" + project = { + key = "project1" + } + environment_type = { + key = "development" + } + status = "Enabled" + tags = { + environment = "production" + purpose = "project-environment-association" + team = "platform" + } + } + proj1_env_staging = { + name = "staging" + project = { + key = "project1" + } + environment_type = { + key = "staging" + } + status = "Enabled" + tags = { + environment = "production" + purpose = "project-environment-association" + team = "platform" + } + } + proj1_env_prod = { + name = "production" + project = { + key = "project1" + } + environment_type = { + key = "production" + } + status = "Enabled" + tags = { + environment = "production" + purpose = "project-environment-association" + team = "platform" + } + } + proj2_env_dev = { + name = "development" + project = { + key = "project2" + } + environment_type = { + key = "development" + } + status = "Enabled" + tags = { + environment = "production" + purpose = "project-environment-association" + team = "application" + } + } + proj2_env_staging = { + name = "staging" + project = { + key = "project2" + } + environment_type = { + key = "staging" + } + status = "Disabled" + tags = { + environment = "production" + purpose = "project-environment-association" + team = "application" + note = "disabled-for-maintenance" + } + } +} + +# Empty variables required by the root module +dev_center_galleries = {} +dev_center_dev_box_definitions = {} +dev_center_network_connections = {} +dev_center_catalogs = {} +dev_center_project_pools = {} +dev_center_project_pool_schedules = {} +shared_image_galleries = {} \ No newline at end of file diff --git a/examples/dev_center_project_environment_type/simple_case/configuration.tfvars b/examples/dev_center_project_environment_type/simple_case/configuration.tfvars new file mode 100644 index 0000000..3195d1a --- /dev/null +++ b/examples/dev_center_project_environment_type/simple_case/configuration.tfvars @@ -0,0 +1,109 @@ +global_settings = { + prefixes = ["demo"] + random_length = 3 + passthrough = false + use_slug = true +} + +resource_groups = { + rg1 = { + name = "devcenter-demo" + region = "eastus" + tags = { + environment = "demo" + purpose = "dev-center-testing" + } + } +} + +dev_centers = { + devcenter1 = { + name = "demo-devcenter" + resource_group = { + key = "rg1" + } + tags = { + environment = "demo" + module = "dev_center" + } + } +} + +dev_center_environment_types = { + development = { + name = "development" + dev_center = { + key = "devcenter1" + } + tags = { + environment = "demo" + purpose = "development" + } + } + staging = { + name = "staging" + dev_center = { + key = "devcenter1" + } + tags = { + environment = "demo" + purpose = "staging" + } + } +} + +dev_center_projects = { + project1 = { + name = "demo-project" + dev_center = { + key = "devcenter1" + } + resource_group = { + key = "rg1" + } + tags = { + environment = "demo" + module = "dev_center_project" + } + } +} + +dev_center_project_environment_types = { + proj_env_dev = { + name = "development" + project = { + key = "project1" + } + environment_type = { + key = "development" + } + status = "Enabled" + tags = { + environment = "demo" + purpose = "project-environment-association" + } + } + proj_env_staging = { + name = "staging" + project = { + key = "project1" + } + environment_type = { + key = "staging" + } + status = "Enabled" + tags = { + environment = "demo" + purpose = "project-environment-association" + } + } +} + +# Empty variables required by the root module +dev_center_galleries = {} +dev_center_dev_box_definitions = {} +dev_center_network_connections = {} +dev_center_catalogs = {} +dev_center_project_pools = {} +dev_center_project_pool_schedules = {} +shared_image_galleries = {} \ No newline at end of file diff --git a/modules/dev_center_project_environment_type/README.md b/modules/dev_center_project_environment_type/README.md new file mode 100644 index 0000000..03b4ac2 --- /dev/null +++ b/modules/dev_center_project_environment_type/README.md @@ -0,0 +1,152 @@ +# Azure Dev Center Project Environment Type Module + +This module creates project environment type associations within an Azure Dev Center project. Project environment types link specific environment types to projects, making them available for use within the project. + +## Usage + +### Simple Usage + +```hcl +module "dev_center_project_environment_type" { + source = "./modules/dev_center_project_environment_type" + + global_settings = { + prefixes = ["dev"] + random_length = 3 + passthrough = false + use_slug = true + } + + project_environment_type = { + name = "development" + status = "Enabled" + tags = { + environment = "development" + purpose = "team-development" + } + } + + dev_center_project_id = "/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/rg-example/providers/Microsoft.DevCenter/projects/project-example" +} +``` + +For more examples including multi-project configurations, see the [Dev Center Project Environment Type examples](../../../examples/dev_center_project_environment_type/). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.9.0 | +| [azapi](#requirement\_azapi) | ~> 2.4.0 | +| [azurecaf](#requirement\_azurecaf) | ~> 1.2.29 | + +## Providers + +| Name | Version | +|------|---------| +| [azapi](#provider\_azapi) | ~> 2.4.0 | +| [azurecaf](#provider\_azurecaf) | ~> 1.2.29 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [azapi_resource.dev_center_project_environment_type](https://registry.terraform.io/providers/Azure/azapi/latest/docs/resources/resource) | resource | +| [azurecaf_name.project_environment_type](https://registry.terraform.io/providers/aztfmod/azurecaf/latest/docs/resources/azurecaf_name) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [deployment\_target\_id](#input\_deployment\_target\_id) | The ID of the deployment target for this project environment type | `string` | n/a | yes | +| [dev\_center\_project\_id](#input\_dev\_center\_project\_id) | The ID of the Dev Center Project that will contain the environment type | `string` | n/a | yes | +| [global\_settings](#input\_global\_settings) | Global settings object |
object({
prefixes = optional(list(string))
random_length = optional(number)
passthrough = optional(bool)
use_slug = optional(bool)
tags = optional(map(string))
}) | n/a | yes |
+| [project\_environment\_type](#input\_project\_environment\_type) | Configuration object for the Dev Center Project Environment Type | object({
name = string
status = optional(string, "Enabled")
user_role_assignments = optional(map(object({
roles = map(object({}))
})))
tags = optional(map(string))
}) | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [deployment\_target\_id](#output\_deployment\_target\_id) | The deployment target ID for the project environment type |
+| [dev\_center\_project\_id](#output\_dev\_center\_project\_id) | The ID of the parent Dev Center Project |
+| [id](#output\_id) | The ID of the Dev Center Project Environment Type |
+| [name](#output\_name) | The name of the Dev Center Project Environment Type |
+| [provisioning\_state](#output\_provisioning\_state) | The provisioning state of the Dev Center Project Environment Type |
+| [status](#output\_status) | The status of the Dev Center Project Environment Type |
+| [user\_role\_assignments](#output\_user\_role\_assignments) | The user role assignments for the project environment type |
+
+
+## Features
+
+- Associates environment types with Dev Center projects
+- Automatically uses current subscription as deployment target
+- Configurable status (Enabled/Disabled)
+- Comprehensive validation for all input variables
+- Tags support for resource organization
+- Optional user role assignments for granular access control
+
+## Validation Rules
+
+- Project environment type name must be 3-128 characters, alphanumeric with hyphens, underscores, and periods
+- Status must be either "Enabled" or "Disabled"
+- Dev Center Project ID must be a valid resource ID format
+- Deployment target is automatically set to current subscription
+
+## Advanced Configuration
+
+### User Role Assignments (Optional)
+
+For advanced scenarios requiring granular access control, you can configure user role assignments. When using `user_role_assignments`, you need to provide:
+
+1. **User Object IDs**: The keys in the map should be Azure AD user or group object IDs (GUIDs), not email addresses
+2. **Role Definition IDs**: The `roles` map should contain Azure role definition IDs (GUIDs) as keys, not role names
+
+Example:
+
+```hcl
+module "dev_center_project_environment_type" {
+ source = "./modules/dev_center_project_environment_type"
+
+ global_settings = {
+ prefixes = ["prod"]
+ random_length = 3
+ passthrough = false
+ use_slug = true
+ }
+
+ project_environment_type = {
+ name = "production"
+ status = "Enabled"
+ user_role_assignments = {
+ "e45e3m7c-176e-416a-b466-0c5ec8298f8a" = { # User object ID
+ roles = {
+ "4cbf0b6c-e750-441c-98a7-10da8387e4d6" = {} # Role definition ID
+ }
+ }
+ }
+ tags = {
+ environment = "production"
+ tier = "critical"
+ }
+ }
+
+ dev_center_project_id = "/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/rg-example/providers/Microsoft.DevCenter/projects/project-example"
+}
+```
+
+To find these IDs:
+
+- **User Object ID**: Use `az ad user show --id user@domain.com --query id -o tsv`
+- **Role Definition ID**: Use `az role definition list --name "Role Name" --query '[].id' -o tsv`
+
+## Security Considerations
+
+- Use least privilege access for role assignments
+- Consider using Azure AD groups instead of individual users for role assignments
+- Environment resources will be deployed to the current subscription
+- Review and audit user role assignments regularly
diff --git a/modules/dev_center_project_environment_type/module.tf b/modules/dev_center_project_environment_type/module.tf
new file mode 100644
index 0000000..0fadf9a
--- /dev/null
+++ b/modules/dev_center_project_environment_type/module.tf
@@ -0,0 +1,58 @@
+terraform {
+ required_version = ">= 1.9.0"
+ required_providers {
+ azurecaf = {
+ source = "aztfmod/azurecaf"
+ version = "~> 1.2.29"
+ }
+ azapi = {
+ source = "Azure/azapi"
+ version = "~> 2.4.0"
+ }
+ }
+}
+
+data "azapi_client_config" "current" {}
+
+locals {
+ tags = merge(
+ try(var.global_settings.tags, {}),
+ try(var.project_environment_type.tags, {})
+ )
+
+ # Extract subscription ID for deploymentTargetId
+ # The deploymentTargetId should be the subscription ID, not the full environment type resource ID
+ subscription_id = "/subscriptions/${data.azapi_client_config.current.subscription_id}"
+}
+
+resource "azapi_resource" "dev_center_project_environment_type" {
+ type = "Microsoft.DevCenter/projects/environmentTypes@2025-04-01-preview"
+ name = var.environment_type_name
+ parent_id = var.dev_center_project_id
+
+ body = {
+ properties = merge(
+ {
+ # The deploymentTargetId should be the subscription ID, not the environment type resource ID
+ # This specifies the subscription where environment resources will be deployed
+ deploymentTargetId = local.subscription_id
+ },
+ try(var.project_environment_type.status, null) != null ? {
+ status = var.project_environment_type.status
+ } : {},
+ try(var.project_environment_type.user_role_assignments, null) != null ? {
+ userRoleAssignments = var.project_environment_type.user_role_assignments
+ } : {}
+ )
+ tags = local.tags
+ }
+
+ response_export_values = ["properties"]
+
+ # Ignore changes to system-managed tags that Azure automatically adds
+ lifecycle {
+ ignore_changes = [
+ tags["hidden-title"]
+ ]
+ }
+}
\ No newline at end of file
diff --git a/modules/dev_center_project_environment_type/output.tf b/modules/dev_center_project_environment_type/output.tf
new file mode 100644
index 0000000..3a16cde
--- /dev/null
+++ b/modules/dev_center_project_environment_type/output.tf
@@ -0,0 +1,34 @@
+output "id" {
+ description = "The ID of the Dev Center Project Environment Type"
+ value = azapi_resource.dev_center_project_environment_type.id
+}
+
+output "name" {
+ description = "The name of the Dev Center Project Environment Type"
+ value = azapi_resource.dev_center_project_environment_type.name
+}
+
+output "dev_center_project_id" {
+ description = "The ID of the parent Dev Center Project"
+ value = var.dev_center_project_id
+}
+
+output "deployment_target_id" {
+ description = "The deployment target ID for the project environment type (subscription ID)"
+ value = local.subscription_id
+}
+
+output "status" {
+ description = "The status of the Dev Center Project Environment Type"
+ value = try(azapi_resource.dev_center_project_environment_type.output.properties.status, null)
+}
+
+output "provisioning_state" {
+ description = "The provisioning state of the Dev Center Project Environment Type"
+ value = try(azapi_resource.dev_center_project_environment_type.output.properties.provisioningState, null)
+}
+
+output "user_role_assignments" {
+ description = "The user role assignments for the project environment type"
+ value = try(azapi_resource.dev_center_project_environment_type.output.properties.userRoleAssignments, null)
+}
\ No newline at end of file
diff --git a/modules/dev_center_project_environment_type/variables.tf b/modules/dev_center_project_environment_type/variables.tf
new file mode 100644
index 0000000..74c3edf
--- /dev/null
+++ b/modules/dev_center_project_environment_type/variables.tf
@@ -0,0 +1,46 @@
+variable "global_settings" {
+ description = "Global settings object"
+ type = object({
+ prefixes = optional(list(string))
+ random_length = optional(number)
+ passthrough = optional(bool)
+ use_slug = optional(bool)
+ tags = optional(map(string))
+ })
+}
+
+variable "dev_center_project_id" {
+ description = "The ID of the Dev Center Project that will contain the environment type"
+ type = string
+
+ validation {
+ condition = can(regex("^/subscriptions/[0-9a-f-]+/resourceGroups/[^/]+/providers/Microsoft\\.DevCenter/projects/[^/]+$", var.dev_center_project_id))
+ error_message = "Dev Center Project ID must be a valid Azure resource ID for a Dev Center Project."
+ }
+}
+
+variable "environment_type_name" {
+ description = "The name of the environment type (must match an existing environment type in the Dev Center)"
+ type = string
+
+ validation {
+ condition = can(regex("^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]$", var.environment_type_name)) && length(var.environment_type_name) >= 3 && length(var.environment_type_name) <= 128
+ error_message = "Environment type name must be between 3 and 128 characters long and can contain alphanumeric characters, periods, hyphens, and underscores. It must start and end with an alphanumeric character."
+ }
+}
+
+variable "project_environment_type" {
+ description = "Configuration object for the Dev Center Project Environment Type"
+ type = object({
+ status = optional(string, "Enabled")
+ user_role_assignments = optional(map(object({
+ roles = map(object({}))
+ })))
+ tags = optional(map(string))
+ })
+
+ validation {
+ condition = var.project_environment_type.status == null || contains(["Enabled", "Disabled"], var.project_environment_type.status)
+ error_message = "Status must be either 'Enabled' or 'Disabled'."
+ }
+}
\ No newline at end of file
diff --git a/tests/integration/dev_center_integration_test.tftest.hcl b/tests/integration/dev_center_integration_test.tftest.hcl
index fa7a9c7..6dd391b 100644
--- a/tests/integration/dev_center_integration_test.tftest.hcl
+++ b/tests/integration/dev_center_integration_test.tftest.hcl
@@ -83,11 +83,29 @@ variables {
}
// Empty variables required by the root module
- dev_center_galleries = {}
- dev_center_dev_box_definitions = {}
- dev_center_project_environment_types = {}
- dev_center_network_connections = {}
- shared_image_galleries = {}
+ dev_center_galleries = {}
+ dev_center_dev_box_definitions = {}
+ dev_center_project_environment_types = {
+ proj_env_test = {
+ name = "test-environment-type"
+ project = {
+ key = "project1"
+ }
+ environment_type = {
+ key = "envtype1"
+ }
+ status = "Enabled"
+ tags = {
+ environment = "test"
+ module = "dev_center_project_environment_type"
+ test_type = "integration"
+ }
+ }
+ }
+ dev_center_network_connections = {}
+ dev_center_project_pools = {}
+ dev_center_project_pool_schedules = {}
+ shared_image_galleries = {}
}
mock_provider "azapi" {
@@ -135,6 +153,11 @@ run "full_infrastructure_creation" {
error_message = "Dev center catalog should exist"
}
+ assert {
+ condition = module.dev_center_project_environment_types["proj_env_test"] != null
+ error_message = "Project environment type should exist"
+ }
+
// Test input variable values
assert {
condition = var.resource_groups.rg1.name == "test-resource-group"
diff --git a/tests/unit/dev_center_project_environment_type/project_environment_type_test.tftest.hcl b/tests/unit/dev_center_project_environment_type/project_environment_type_test.tftest.hcl
new file mode 100644
index 0000000..947d8e7
--- /dev/null
+++ b/tests/unit/dev_center_project_environment_type/project_environment_type_test.tftest.hcl
@@ -0,0 +1,162 @@
+variables {
+ global_settings = {
+ prefixes = ["test"]
+ random_length = 3
+ passthrough = false
+ use_slug = true
+ }
+
+ resource_groups = {
+ rg1 = {
+ name = "test-resource-group"
+ region = "eastus"
+ tags = {
+ environment = "test"
+ }
+ }
+ }
+
+ dev_centers = {
+ devcenter1 = {
+ name = "test-dev-center"
+ resource_group = {
+ key = "rg1"
+ }
+ tags = {
+ environment = "test"
+ module = "dev_center"
+ }
+ }
+ }
+
+ dev_center_environment_types = {
+ development = {
+ name = "development"
+ display_name = "Development Environment Type"
+ dev_center = {
+ key = "devcenter1"
+ }
+ tags = {
+ environment = "test"
+ module = "dev_center_environment_type"
+ purpose = "development"
+ }
+ }
+ staging = {
+ name = "staging"
+ dev_center = {
+ key = "devcenter1"
+ }
+ tags = {
+ environment = "test"
+ module = "dev_center_environment_type"
+ purpose = "staging"
+ }
+ }
+ }
+
+ dev_center_projects = {
+ project1 = {
+ name = "test-project"
+ dev_center = {
+ key = "devcenter1"
+ }
+ resource_group = {
+ key = "rg1"
+ }
+ tags = {
+ environment = "test"
+ module = "dev_center_project"
+ }
+ }
+ }
+
+ dev_center_project_environment_types = {
+ // Basic project environment type
+ proj_env_dev = {
+ name = "development"
+ project = {
+ key = "project1"
+ }
+ environment_type = {
+ key = "development"
+ }
+ status = "Enabled"
+ tags = {
+ environment = "test"
+ module = "dev_center_project_environment_type"
+ purpose = "development"
+ }
+ }
+ // Project environment type with user role assignments
+ proj_env_staging = {
+ name = "staging"
+ project = {
+ key = "project1"
+ }
+ environment_type = {
+ key = "staging"
+ }
+ status = "Enabled"
+ user_role_assignments = {
+ "e45e3m7c-176e-416a-b466-0c5ec8298f8a" = {
+ roles = {
+ "4cbf0b6c-e750-441c-98a7-10da8387e4d6" = {}
+ }
+ }
+ }
+ tags = {
+ environment = "test"
+ module = "dev_center_project_environment_type"
+ purpose = "staging"
+ }
+ }
+ }
+
+ // Empty variables required by the root module
+ dev_center_galleries = {}
+ dev_center_dev_box_definitions = {}
+ dev_center_network_connections = {}
+ dev_center_catalogs = {}
+ dev_center_project_pools = {}
+ dev_center_project_pool_schedules = {}
+ shared_image_galleries = {}
+}
+
+mock_provider "azapi" {
+ mock_data "azapi_client_config" {
+ defaults = {
+ subscription_id = "12345678-1234-1234-1234-123456789012"
+ tenant_id = "12345678-1234-1234-1234-123456789012"
+ client_id = "12345678-1234-1234-1234-123456789012"
+ }
+ }
+}
+
+mock_provider "azurecaf" {}
+
+run "test_project_environment_type_creation" {
+ command = plan
+
+ providers = {
+ azapi = azapi
+ azurecaf = azurecaf
+ }
+
+ module { source = "../../../" }
+
+ assert {
+ condition = module.dev_center_project_environment_types["proj_env_dev"] != null
+ error_message = "Development project environment type module should exist"
+ }
+
+ assert {
+ condition = module.dev_center_project_environment_types["proj_env_staging"] != null
+ error_message = "Staging project environment type module should exist"
+ }
+
+ assert {
+ condition = length(keys(module.dev_center_project_environment_types)) == 2
+ error_message = "Should have two project environment types (proj_env_dev and proj_env_staging)"
+ }
+}
\ No newline at end of file
diff --git a/variables.tf b/variables.tf
index 3e0e788..cdf4ece 100644
--- a/variables.tf
+++ b/variables.tf
@@ -360,3 +360,52 @@ variable "dev_center_project_pool_schedules" {
}))
default = {}
}
+
+variable "dev_center_project_environment_types" {
+ description = "Dev Center Project Environment Types configuration objects"
+ type = map(object({
+ name = string
+ project = object({
+ key = string
+ })
+ environment_type = object({
+ key = string
+ })
+ dev_center_project_id = optional(string)
+ deployment_target_id = optional(string)
+ status = optional(string, "Enabled")
+ user_role_assignments = optional(map(object({
+ roles = map(object({}))
+ })))
+ tags = optional(map(string), {})
+ }))
+ default = {}
+
+ validation {
+ condition = alltrue([
+ for k, v in var.dev_center_project_environment_types : (
+ (v.dev_center_project_id != null && v.project == null) ||
+ (v.dev_center_project_id == null && v.project != null)
+ )
+ ])
+ error_message = "Either 'dev_center_project_id' or 'project.key' must be specified, but not both."
+ }
+
+ validation {
+ condition = alltrue([
+ for k, v in var.dev_center_project_environment_types : (
+ (v.deployment_target_id != null && v.environment_type == null) ||
+ (v.deployment_target_id == null && v.environment_type != null)
+ )
+ ])
+ error_message = "Either 'deployment_target_id' or 'environment_type.key' must be specified, but not both."
+ }
+
+ validation {
+ condition = alltrue([
+ for k, v in var.dev_center_project_environment_types :
+ contains(["Enabled", "Disabled"], v.status)
+ ])
+ error_message = "Status must be either 'Enabled' or 'Disabled'."
+ }
+}
From 9d8e94c4d21e3e1767fa17f97ca6dd99136c4dce Mon Sep 17 00:00:00 2001
From: Rafferty Uy