diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index f87ba737d..5bc49cf92 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -6,7 +6,7 @@ version: '3.7' services: rover: - image: aztfmod/rover-preview:1.2.1-2205.300342 + image: aztfmod/rover-preview:1.3.9-2303.090804 user: vscode labels: diff --git a/.github/workflows/landingzones-tf100.yml b/.github/workflows/landingzones-tf100.yml index e2c5597fe..1e317e6b2 100644 --- a/.github/workflows/landingzones-tf100.yml +++ b/.github/workflows/landingzones-tf100.yml @@ -39,11 +39,11 @@ jobs: random_length: ['5'] container: - image: aztfmod/rover-preview:1.2.1-2205.300342 + image: aztfmod/rover-preview:1.3.9-2303.090804 options: --user 0 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Login azure run: | @@ -52,6 +52,10 @@ jobs: echo "local user: $(whoami)" + - name: Github Actions permissions workaround + run: | + git config --global --add safe.directory ${GITHUB_WORKSPACE} + - name: launchpad run: | /tf/rover/rover.sh -lz ${GITHUB_WORKSPACE}/caf_launchpad -a apply \ @@ -92,17 +96,21 @@ jobs: ] container: - image: aztfmod/rover-preview:1.2.1-2205.300342 + image: aztfmod/rover-preview:1.3.9-2303.090804 options: --user 0 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Login azure run: | az login --service-principal -u '${{ env.ARM_CLIENT_ID }}' -p '${{ env.ARM_CLIENT_SECRET }}' --tenant '${{ env.ARM_TENANT_ID }}' az account set -s ${{ env.ARM_SUBSCRIPTION_ID }} + - name: Github Actions permissions workaround + run: | + git config --global --add safe.directory ${GITHUB_WORKSPACE} + - name: deploy example run: | /tf/rover/rover.sh -lz ${GITHUB_WORKSPACE}/caf_solution/ -a apply \ @@ -135,11 +143,11 @@ jobs: random_length: ['5'] container: - image: aztfmod/rover-preview:1.2.1-2205.300342 + image: aztfmod/rover-preview:1.3.9-2303.090804 options: --user 0 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Login azure run: | @@ -148,6 +156,10 @@ jobs: echo "local user: $(whoami)" + - name: Github Actions permissions workaround + run: | + git config --global --add safe.directory ${GITHUB_WORKSPACE} + - name: launchpad-200-upgrade run: | /tf/rover/rover.sh -lz ${GITHUB_WORKSPACE}/caf_launchpad -a apply \ @@ -186,17 +198,21 @@ jobs: ] container: - image: aztfmod/rover-preview:1.2.1-2205.300342 + image: aztfmod/rover-preview:1.3.9-2303.090804 options: --user 0 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Login azure run: | az login --service-principal -u '${{ env.ARM_CLIENT_ID }}' -p '${{ env.ARM_CLIENT_SECRET }}' --tenant '${{ env.ARM_TENANT_ID }}' az account set -s ${{ env.ARM_SUBSCRIPTION_ID }} + - name: Github Actions permissions workaround + run: | + git config --global --add safe.directory ${GITHUB_WORKSPACE} + - name: deploy example run: | /tf/rover/rover.sh -lz ${GITHUB_WORKSPACE}/caf_solution/ -a apply \ @@ -228,11 +244,11 @@ jobs: random_length: ['5'] container: - image: aztfmod/rover-preview:1.2.1-2205.300342 + image: aztfmod/rover-preview:1.3.9-2303.090804 options: --user 0 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Login azure run: | @@ -241,6 +257,10 @@ jobs: echo "local user: $(whoami)" + - name: Github Actions permissions workaround + run: | + git config --global --add safe.directory ${GITHUB_WORKSPACE} + - name: foundations run: | /tf/rover/rover.sh -lz ${GITHUB_WORKSPACE}/caf_solution -a destroy \ diff --git a/.github/workflows/landingzones-tf15.yml b/.github/workflows/landingzones-tf15.yml deleted file mode 100644 index edf417c6b..000000000 --- a/.github/workflows/landingzones-tf15.yml +++ /dev/null @@ -1,271 +0,0 @@ -# -# Copyright (c) Microsoft Corporation -# Licensed under the MIT License. -# - -name: landingzones-tf15 - -on: - workflow_dispatch: - schedule: - - cron: '0 1 * * *' - -env: - TF_CLI_ARGS: '-no-color' - TF_CLI_ARGS_destroy: '-refresh=false' - ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} - ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} - ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} - ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} - TF_REGISTRY_DISCOVERY_RETRY: 5 - TF_REGISTRY_CLIENT_TIMEOUT: 15 - ROVER_RUNNER: true - -jobs: - foundations100: - name: foundations-100 - runs-on: ubuntu-latest - - strategy: - fail-fast: true - max-parallel: 1 - matrix: - random_length: ['5'] - - container: - image: aztfmod/rover-preview:0.15.5-2205.300342 - options: --user 0 - - steps: - - uses: actions/checkout@v2 - - - name: Login azure - run: | - az login --service-principal -u '${{ env.ARM_CLIENT_ID }}' -p '${{ env.ARM_CLIENT_SECRET }}' --tenant '${{ env.ARM_TENANT_ID }}' - az account set -s ${{ env.ARM_SUBSCRIPTION_ID }} - - echo "local user: $(whoami)" - - - name: launchpad - run: | - /tf/rover/rover.sh -lz ${GITHUB_WORKSPACE}/caf_launchpad -a apply \ - -var-folder ${GITHUB_WORKSPACE}/caf_launchpad/scenario/100 \ - -level level0 \ - -launchpad \ - -parallelism=30 \ - --environment ${{ github.run_id }} \ - '-var random_length=${{ matrix.random_length }}' \ - '-var prefix=g${{ github.run_id }}' \ - '-var tags={testing_job_id="${{ github.run_id }}"}' - - - name: foundations - run: | - sleep 180 - /tf/rover/rover.sh -lz ${GITHUB_WORKSPACE}/caf_solution -a apply \ - -var-folder ${GITHUB_WORKSPACE}/caf_solution/scenario/foundations/100-passthrough \ - -tfstate caf_foundations.tfstate \ - -level level1 \ - -parallelism=30 \ - --environment ${{ github.run_id }} \ - '-var tags={testing_job_id="${{ github.run_id }}"}' - - networking100: - name: networking-100 - runs-on: ubuntu-latest - - needs: foundations100 - - strategy: - fail-fast: false - matrix: - config_files: [ - "caf_solution/scenario/networking/100-single-region-hub", - "caf_solution/scenario/networking/101-multi-region-hub", - "caf_solution/scenario/networking/105-hub-and-spoke", - "caf_solution/scenario/networking/106-hub-virtual-wan-firewall" - ] - - container: - image: aztfmod/rover-preview:0.15.5-2205.300342 - options: --user 0 - - steps: - - uses: actions/checkout@v2 - - - name: Login azure - run: | - az login --service-principal -u '${{ env.ARM_CLIENT_ID }}' -p '${{ env.ARM_CLIENT_SECRET }}' --tenant '${{ env.ARM_TENANT_ID }}' - az account set -s ${{ env.ARM_SUBSCRIPTION_ID }} - - - name: deploy example - run: | - /tf/rover/rover.sh -lz ${GITHUB_WORKSPACE}/caf_solution/ -a apply \ - -tfstate $(basename ${{ matrix.config_files }}).tfstate \ - -level level2 \ - -parallelism=30 \ - -var-folder ${GITHUB_WORKSPACE}/${{ matrix.config_files }} \ - --environment ${{ github.run_id }} - - - name: destroy example - run: | - /tf/rover/rover.sh -lz ${GITHUB_WORKSPACE}/caf_solution/ -a destroy \ - -tfstate $(basename ${{ matrix.config_files }}).tfstate \ - -level level2 \ - -parallelism=30 \ - -var-folder ${GITHUB_WORKSPACE}/${{ matrix.config_files }} \ - --environment ${{ github.run_id }} \ - -refresh=false - - foundations200: - name: foundations-200 - runs-on: ubuntu-latest - needs: networking100 - if: always() - - strategy: - fail-fast: true - max-parallel: 1 - matrix: - random_length: ['5'] - - container: - image: aztfmod/rover-preview:0.15.5-2205.300342 - options: --user 0 - - steps: - - uses: actions/checkout@v2 - - - name: Login azure - run: | - az login --service-principal -u '${{ env.ARM_CLIENT_ID }}' -p '${{ env.ARM_CLIENT_SECRET }}' --tenant '${{ env.ARM_TENANT_ID }}' - az account set -s ${{ env.ARM_SUBSCRIPTION_ID }} - - echo "local user: $(whoami)" - - - name: launchpad-200-upgrade - run: | - /tf/rover/rover.sh -lz ${GITHUB_WORKSPACE}/caf_launchpad -a apply \ - -var-folder ${GITHUB_WORKSPACE}/caf_launchpad/scenario/200 \ - -level level0 \ - -launchpad \ - -parallelism=30 \ - --environment ${{ github.run_id }} \ - '-var random_length=${{ matrix.random_length }}' \ - '-var prefix=g${{ github.run_id }}' \ - '-var tags={testing_job_id="${{ github.run_id }}"}' - - - name: foundations-200-upgrade - run: | - /tf/rover/rover.sh -lz ${GITHUB_WORKSPACE}/caf_solution -a apply \ - -var-folder ${GITHUB_WORKSPACE}/caf_solution/scenario/foundations/gitops \ - -tfstate caf_foundations.tfstate \ - -level level1 \ - -parallelism=30 \ - --environment ${{ github.run_id }} \ - '-var tags={testing_job_id="${{ github.run_id }}"}' - - networking200: - name: networking-200 - runs-on: ubuntu-latest - - needs: foundations200 - - strategy: - fail-fast: false - matrix: - config_files: [ - "caf_solution/scenario/networking/200-single-region-hub", - "caf_solution/scenario/networking/201-multi-region-hub", - "caf_solution/scenario/networking/210-aks-private" - ] - - container: - image: aztfmod/rover-preview:0.15.5-2205.300342 - options: --user 0 - - steps: - - uses: actions/checkout@v2 - - - name: Login azure - run: | - az login --service-principal -u '${{ env.ARM_CLIENT_ID }}' -p '${{ env.ARM_CLIENT_SECRET }}' --tenant '${{ env.ARM_TENANT_ID }}' - az account set -s ${{ env.ARM_SUBSCRIPTION_ID }} - - - name: deploy example - run: | - /tf/rover/rover.sh -lz ${GITHUB_WORKSPACE}/caf_solution/ -a apply \ - -tfstate $(basename ${{ matrix.config_files }}).tfstate \ - -level level2 \ - -parallelism=30 \ - -var-folder ${GITHUB_WORKSPACE}/${{ matrix.config_files }} \ - --environment ${{ github.run_id }} - - - name: destroy example - run: | - /tf/rover/rover.sh -lz ${GITHUB_WORKSPACE}/caf_solution/ -a destroy \ - -tfstate $(basename ${{ matrix.config_files }}).tfstate \ - -level level2 \ - -parallelism=30 \ - -var-folder ${GITHUB_WORKSPACE}/${{ matrix.config_files }} \ - --environment ${{ github.run_id }} \ - -refresh=false - - foundations_destroy: - name: foundations_destroy - runs-on: ubuntu-latest - if: always() - needs: networking200 - - strategy: - fail-fast: false - matrix: - random_length: ['5'] - - container: - image: aztfmod/rover-preview:0.15.5-2205.300342 - options: --user 0 - - steps: - - uses: actions/checkout@v2 - - - name: Login azure - run: | - az login --service-principal -u '${{ env.ARM_CLIENT_ID }}' -p '${{ env.ARM_CLIENT_SECRET }}' --tenant '${{ env.ARM_TENANT_ID }}' - az account set -s ${{ env.ARM_SUBSCRIPTION_ID }} - - echo "local user: $(whoami)" - - - name: foundations - run: | - /tf/rover/rover.sh -lz ${GITHUB_WORKSPACE}/caf_solution -a destroy \ - -var-folder ${GITHUB_WORKSPACE}/caf_solution/scenario/foundations/gitops \ - -tfstate caf_foundations.tfstate \ - -level level1 \ - -parallelism=30 \ - --environment ${{ github.run_id }} \ - '-var tags={testing_job_id="${{ github.run_id }}"}' - - - name: Remove launchpad - run: | - /tf/rover/rover.sh -lz ${GITHUB_WORKSPACE}/caf_launchpad -a destroy \ - -var-folder ${GITHUB_WORKSPACE}/caf_launchpad/scenario/200 \ - -level level0 \ - -launchpad \ - -parallelism=30 \ - --environment ${{ github.run_id }} \ - '-var random_length=${{ matrix.random_length }}' \ - '-var prefix=g${{ github.run_id }}' \ - '-var tags={testing_job_id="${{ github.run_id }}"}' - - - - name: Complete purge - if: ${{ always() }} - run: | - for i in `az monitor diagnostic-settings subscription list -o tsv --query "value[?contains(name, '${{ github.run_id }}' )].name"`; do echo "purging subscription diagnostic-settings: $i" && $(az monitor diagnostic-settings subscription delete --name $i --yes); done - for i in `az monitor log-profiles list -o tsv --query '[].name'`; do az monitor log-profiles delete --name $i; done - for i in `az ad group list --query "[?contains(displayName, '${{ github.run_id }}')].objectId" -o tsv`; do echo "purging Azure AD group: $i" && $(az ad group delete --verbose --group $i || true); done - for i in `az ad app list --query "[?contains(displayName, '${{ github.run_id }}')].appId" -o tsv`; do echo "purging Azure AD app: $i" && $(az ad app delete --verbose --id $i || true); done - for i in `az keyvault list-deleted --query "[?tags.environment=='${{ github.run_id }}'].name" -o tsv`; do az keyvault purge --name $i; done - for i in `az group list --query "[?tags.environment=='${{ github.run_id }}'].name" -o tsv`; do echo "purging resource group: $i" && $(az group delete -n $i -y --no-wait || true); done - for i in `az role assignment list --query "[?contains(roleDefinitionName, '${{ github.run_id }}')].roleDefinitionName" -o tsv`; do echo "purging role assignment: $i" && $(az role assignment delete --role $i || true); done - for i in `az role definition list --query "[?contains(roleName, '${{ github.run_id }}')].roleName" -o tsv`; do echo "purging custom role definition: $i" && $(az role definition delete --name $i || true); done diff --git a/.gitignore b/.gitignore index 9496a3682..dc01ba489 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,4 @@ **/*.log **/backend.azurerm.tf public -aztfmod *output.json \ No newline at end of file diff --git a/_pictures/caf_elements.png b/_pictures/caf_elements.png index 965fe02c3..69fb0782f 100644 Binary files a/_pictures/caf_elements.png and b/_pictures/caf_elements.png differ diff --git a/caf_launchpad/dynamic_secrets.tf b/caf_launchpad/dynamic_secrets.tf index be1086f68..9c721a515 100644 --- a/caf_launchpad/dynamic_secrets.tf +++ b/caf_launchpad/dynamic_secrets.tf @@ -1,9 +1,7 @@ module "dynamic_keyvault_secrets" { - # source = "aztfmod/caf/azurerm//modules/security/dynamic_keyvault_secrets" - # version = "5.5.5" - - source = "git::https://github.com/aztfmod/terraform-azurerm-caf.git//modules/security/dynamic_keyvault_secrets?ref=main" + source = "aztfmod/caf/azurerm//modules/security/dynamic_keyvault_secrets" + version = "5.6.6" for_each = try(var.dynamic_keyvault_secrets, {}) diff --git a/caf_launchpad/landingzone.tf b/caf_launchpad/landingzone.tf index 95930d6c9..237b82199 100644 --- a/caf_launchpad/landingzone.tf +++ b/caf_launchpad/landingzone.tf @@ -1,13 +1,9 @@ module "launchpad" { - # source = "aztfmod/caf/azurerm" - # version = "5.5.5" - - # during dev cycles for the module, you can pick dev branches from GitHub, or from a local fork - source = "git::https://github.com/aztfmod/terraform-azurerm-caf.git?ref=main" - # source = "../../aztfmod" + source = "aztfmod/caf/azurerm" + version = "5.6.6" providers = { - azurerm.vhub = azurerm + azurerm.vhub = azurerm.vhub } current_landingzone_key = var.landingzone.key @@ -64,6 +60,7 @@ module "launchpad" { network_security_group_definition = try(var.networking.network_security_group_definition, var.network_security_group_definition) public_ip_addresses = try(var.networking.public_ip_addresses, var.public_ip_addresses) route_tables = try(var.networking.route_tables, var.route_tables) + virtual_hub_connections = try(var.networking.virtual_hub_connections, var.virtual_hub_connections) vnets = try(var.networking.vnets, var.vnets) } diff --git a/caf_launchpad/local.custom_variables.tf b/caf_launchpad/local.custom_variables.tf new file mode 100644 index 000000000..09af79806 --- /dev/null +++ b/caf_launchpad/local.custom_variables.tf @@ -0,0 +1,4 @@ +locals { + connectivity_subscription_id = can(var.custom_variables.virtual_hub_subscription_id) ? var.custom_variables.virtual_hub_subscription_id : data.azurerm_client_config.current.subscription_id + connectivity_tenant_id = can(var.custom_variables.virtual_hub_tenant_id) ? var.custom_variables.virtual_hub_tenant_id : data.azurerm_client_config.current.tenant_id +} diff --git a/caf_launchpad/locals.remote_tfstates.tf b/caf_launchpad/locals.remote_tfstates.tf index c0c19d3a6..69ccffc8f 100644 --- a/caf_launchpad/locals.remote_tfstates.tf +++ b/caf_launchpad/locals.remote_tfstates.tf @@ -11,8 +11,8 @@ locals { data "terraform_remote_state" "remote" { for_each = try(var.landingzone.tfstates, {}) - backend = var.landingzone.backend_type - config = local.remote_state[try(each.value.backend_type, var.landingzone.backend_type, "azurerm")][each.key] + backend = try(var.landingzone.backend_type, var.backend_type) + config = local.remote_state[try(each.value.backend_type, var.landingzone.backend_type, var.backend_type)][each.key] } locals { diff --git a/caf_launchpad/main.tf b/caf_launchpad/main.tf index ee73d87f2..f95d2829e 100644 --- a/caf_launchpad/main.tf +++ b/caf_launchpad/main.tf @@ -23,7 +23,7 @@ terraform { version = "~> 1.2.0" } } - required_version = ">= 0.15" + required_version = ">= 1.3.0" } @@ -73,6 +73,14 @@ provider "azurerm" { } } +provider "azurerm" { + alias = "vhub" + skip_provider_registration = true + features {} + subscription_id = local.connectivity_subscription_id + tenant_id = local.connectivity_tenant_id +} + provider "azuread" { partner_id = "ca4078f8-9bc4-471b-ab5b-3af6b86a42c8" } @@ -83,7 +91,7 @@ resource "random_string" "prefix" { length = 4 special = false upper = false - number = false + numeric = false } locals { diff --git a/caf_launchpad/variables.tf b/caf_launchpad/variables.tf index b744caab1..0be38ee28 100644 --- a/caf_launchpad/variables.tf +++ b/caf_launchpad/variables.tf @@ -254,3 +254,10 @@ variable "network_profiles" { default = {} } +variable "virtual_hub_connections" { + default = {} +} + +variable "custom_variables" { + default = {} +} \ No newline at end of file diff --git a/caf_launchpad/variables.tfc.tf b/caf_launchpad/variables.tfc.tf new file mode 100644 index 000000000..bcda99c05 --- /dev/null +++ b/caf_launchpad/variables.tfc.tf @@ -0,0 +1,8 @@ +variable "backend_type" { + default = "azurerm" + description = "Set the terraform remote backend provider." + validation { + condition = contains(["azurerm", "remote"], var.backend_type) + error_message = "Only azurerm or remote are supported at that time." + } +} diff --git a/caf_solution/add-ons/caf_eslz/locals.remote_tfstates.tf b/caf_solution/add-ons/caf_eslz/locals.remote_tfstates.tf index 2e88cee3d..6a7febee0 100644 --- a/caf_solution/add-ons/caf_eslz/locals.remote_tfstates.tf +++ b/caf_solution/add-ons/caf_eslz/locals.remote_tfstates.tf @@ -16,8 +16,8 @@ locals { data "terraform_remote_state" "remote" { for_each = try(var.landingzone.tfstates, {}) - backend = var.landingzone.backend_type - config = local.remote_state[try(each.value.backend_type, var.landingzone.backend_type, "azurerm")][each.key] + backend = try(each.value.backend_type, "azurerm") + config = local.remote_state[try(each.value.backend_type, "azurerm")][each.key] } locals { @@ -32,6 +32,15 @@ locals { tenant_id = try(value.tenant_id, data.azurerm_client_config.current.tenant_id) } } + remote = { + for key, value in try(var.landingzone.tfstates, {}) : key => { + hostname = try(value.hostname, var.tf_cloud_hostname) + organization = try(value.organization, var.tf_cloud_organization) + workspaces = { + name = value.workspace + } + } if try(value.backend_type, "azurerm") == "remote" + } } landingzone_tag = { diff --git a/caf_solution/add-ons/caf_eslz/main.tf b/caf_solution/add-ons/caf_eslz/main.tf index 1ee5a91e2..5fb34bb92 100644 --- a/caf_solution/add-ons/caf_eslz/main.tf +++ b/caf_solution/add-ons/caf_eslz/main.tf @@ -6,7 +6,7 @@ terraform { version = "~> 2.93.1" } } - required_version = ">= 0.14" + required_version = ">= 1.1.0" experiments = [module_variable_optional_attrs] } diff --git a/caf_solution/add-ons/caf_eslz/variables.tf b/caf_solution/add-ons/caf_eslz/variables.tf index 701bced5a..bce716cc5 100644 --- a/caf_solution/add-ons/caf_eslz/variables.tf +++ b/caf_solution/add-ons/caf_eslz/variables.tf @@ -240,3 +240,13 @@ variable "reconcile_vending_subscriptions" { default = false description = "Will reconcile the subrisciptions created outside of enterprise scale to prevent them to be revoved by the execution of this module." } + +variable "tf_cloud_organization" { + default = null + description = "When user backend_type with remote, set the TFC/TFE organization name." +} + +variable "tf_cloud_hostname" { + default = "app.terraform.io" + description = "When user backend_type with remote, set the TFC/TFE hostname." +} \ No newline at end of file diff --git a/caf_solution/add-ons/terraform_cloud/terraform_cloud.tf b/caf_solution/add-ons/terraform_cloud/terraform_cloud.tf index db9218fc0..8ccc501bb 100644 --- a/caf_solution/add-ons/terraform_cloud/terraform_cloud.tf +++ b/caf_solution/add-ons/terraform_cloud/terraform_cloud.tf @@ -81,3 +81,20 @@ resource "null_resource" "backend_file_destroy" { on_failure = fail } } + + +resource "tfe_agent_pool" "tfe_agent_pools" { + depends_on = [tfe_organization.tfe_org] + for_each = try(var.tfe_agent_pools, {}) + + name = each.value.name + organization = try(each.value.organization_name, tfe_organization.tfe_org[each.value.organization_key].name) +} + +resource "tfe_agent_token" "tfe_agent_pool_tokens" { + depends_on = [tfe_agent_pool.tfe_agent_pools] + for_each = try(var.tfe_agent_pool_tokens, {}) + + agent_pool_id = try(each.value.agent_pool_id, tfe_agent_pool.tfe_agent_pools[each.value.agent_pool_key].id) + description = each.value.description +} \ No newline at end of file diff --git a/caf_solution/add-ons/terraform_cloud/variables.tf b/caf_solution/add-ons/terraform_cloud/variables.tf index 499b0aba5..e0cd7b82a 100644 --- a/caf_solution/add-ons/terraform_cloud/variables.tf +++ b/caf_solution/add-ons/terraform_cloud/variables.tf @@ -80,6 +80,10 @@ variable "tfe_servers" { default = {} } -variable "tfe_agents" { +variable "tfe_agent_pools" { default = {} -} \ No newline at end of file +} + +variable "tfe_agent_pool_tokens" { + default = {} +} diff --git a/caf_solution/dynamic_secrets.tf b/caf_solution/dynamic_secrets.tf index 56424db5a..8fa308806 100644 --- a/caf_solution/dynamic_secrets.tf +++ b/caf_solution/dynamic_secrets.tf @@ -1,8 +1,6 @@ module "dynamic_keyvault_secrets" { - # source = "aztfmod/caf/azurerm//modules/security/dynamic_keyvault_secrets" - # version = "5.5.5" - - source = "git::https://github.com/aztfmod/terraform-azurerm-caf.git//modules/security/dynamic_keyvault_secrets?ref=main" + source = "aztfmod/caf/azurerm//modules/security/dynamic_keyvault_secrets" + version = "5.6.6" for_each = { for keyvault_key, secrets in try(var.dynamic_keyvault_secrets, {}) : keyvault_key => { diff --git a/caf_solution/landingzone.tf b/caf_solution/landingzone.tf index f85c83fbf..f7d1f221e 100644 --- a/caf_solution/landingzone.tf +++ b/caf_solution/landingzone.tf @@ -1,10 +1,6 @@ module "solution" { - # source = "aztfmod/caf/azurerm" - # version = "5.5.5" - - # during dev cycles for the module, you can pick dev branches from GitHub, or from a local fork - source = "git::https://github.com/aztfmod/terraform-azurerm-caf.git?ref=main" - # source = "../../aztfmod" + source = "aztfmod/caf/azurerm" + version = "5.6.6" providers = { azurerm.vhub = azurerm.vhub @@ -15,10 +11,11 @@ module "solution" { compute = local.compute apim = local.apim cognitive_services = local.cognitive_services - current_landingzone_key = var.landingzone.key + current_landingzone_key = try(var.landingzone.key, var.landingzone[var.backend_type].key) custom_role_definitions = var.custom_role_definitions data_factory = local.data_factory database = local.database + data_protection = local.data_protection diagnostic_storage_accounts = var.diagnostic_storage_accounts diagnostics_definition = var.diagnostics_definition diagnostics_destinations = var.diagnostics_destinations diff --git a/caf_solution/local.compute.tf b/caf_solution/local.compute.tf index 404d4f714..b4da70938 100644 --- a/caf_solution/local.compute.tf +++ b/caf_solution/local.compute.tf @@ -3,6 +3,7 @@ locals { var.compute, { aks_clusters = var.aks_clusters + aro_clusters = var.aro_clusters availability_sets = var.availability_sets azure_container_registries = var.azure_container_registries batch_accounts = var.batch_accounts diff --git a/caf_solution/local.custom_variables.tf b/caf_solution/local.custom_variables.tf index b7f2932d8..bb6c960f6 100644 --- a/caf_solution/local.custom_variables.tf +++ b/caf_solution/local.custom_variables.tf @@ -1,6 +1,6 @@ locals { - connectivity_subscription_id = can(local.tfstates[local.custom_variables.virtual_hub_lz_key].subscription_id) ? local.tfstates[local.custom_variables.virtual_hub_lz_key].subscription_id : data.azurerm_client_config.current.subscription_id - connectivity_tenant_id = can(local.tfstates[local.custom_variables.virtual_hub_lz_key].tenant_id) ? local.tfstates[local.custom_variables.virtual_hub_lz_key].tenant_id : data.azurerm_client_config.current.tenant_id + connectivity_subscription_id = can(local.tfstates[local.custom_variables.virtual_hub_lz_key].subscription_id) || can(var.custom_variables.virtual_hub_subscription_id) ? try(var.custom_variables.virtual_hub_subscription_id, local.tfstates[local.custom_variables.virtual_hub_lz_key].subscription_id) : data.azurerm_client_config.current.subscription_id + connectivity_tenant_id = can(local.tfstates[local.custom_variables.virtual_hub_lz_key].tenant_id) || can(var.custom_variables.virtual_hub_tenant_id) ? try(var.custom_variables.virtual_hub_tenant_id, local.tfstates[local.custom_variables.virtual_hub_lz_key].tenant_id) : data.azurerm_client_config.current.tenant_id remote_custom_variables = { for key, value in try(var.landingzone.tfstates, {}) : "deep_merged_l1" => merge(try(data.terraform_remote_state.remote[key].outputs.custom_variables, {}))... diff --git a/caf_solution/local.data_protection.tf b/caf_solution/local.data_protection.tf new file mode 100644 index 000000000..b0702bbb7 --- /dev/null +++ b/caf_solution/local.data_protection.tf @@ -0,0 +1,10 @@ +locals { + data_protection = merge( + var.data_protection, + { + backup_vaults = var.backup_vaults + backup_vault_policies = var.backup_vault_policies + backup_vault_instances = var.backup_vault_instances + } + ) +} diff --git a/caf_solution/local.database.tf b/caf_solution/local.database.tf index 0dbc1dd59..ac909d00f 100644 --- a/caf_solution/local.database.tf +++ b/caf_solution/local.database.tf @@ -3,6 +3,7 @@ locals { var.database, { app_config = var.app_config + app_config_entries = var.app_config_entries azurerm_redis_caches = var.azurerm_redis_caches cosmos_dbs = var.cosmos_dbs cosmosdb_sql_databases = var.cosmosdb_sql_databases diff --git a/caf_solution/local.remote.tf b/caf_solution/local.remote.tf index f5a6f9753..7c77828a2 100644 --- a/caf_solution/local.remote.tf +++ b/caf_solution/local.remote.tf @@ -29,6 +29,9 @@ locals { app_service_environments = { for key, value in try(var.landingzone.tfstates, {}) : key => merge(try(data.terraform_remote_state.remote[key].outputs.objects[key].app_service_environments, {})) } + app_service_environments_v3 = { + for key, value in try(var.landingzone.tfstates, {}) : key => merge(try(data.terraform_remote_state.remote[key].outputs.objects[key].app_service_environments_v3, {})) + } app_service_plans = { for key, value in try(var.landingzone.tfstates, {}) : key => merge(try(data.terraform_remote_state.remote[key].outputs.objects[key].app_service_plans, {})) } @@ -262,6 +265,9 @@ locals { wvd_workspaces = { for key, value in try(var.landingzone.tfstates, {}) : key => merge(try(data.terraform_remote_state.remote[key].outputs.objects[key].wvd_workspaces, {})) } + image_definitions = { + for key, value in try(var.landingzone.tfstates, {}) : key => merge(try(data.terraform_remote_state.remote[key].outputs.objects[key].image_definitions, {})) + } } } diff --git a/caf_solution/local.shared_services.tf b/caf_solution/local.shared_services.tf index c1adaa172..b70b9fac6 100644 --- a/caf_solution/local.shared_services.tf +++ b/caf_solution/local.shared_services.tf @@ -3,6 +3,7 @@ locals { var.shared_services, { automations = var.automations + automation_log_analytics_links = var.automation_log_analytics_links consumption_budgets = var.consumption_budgets image_definitions = var.image_definitions log_analytics_storage_insights = var.log_analytics_storage_insights @@ -11,6 +12,7 @@ locals { monitoring = var.monitoring packer_managed_identity = var.packer_managed_identity packer_service_principal = var.packer_service_principal + packer_build = var.packer_build recovery_vaults = var.recovery_vaults shared_image_galleries = var.shared_image_galleries } diff --git a/caf_solution/local.webapp.tf b/caf_solution/local.webapp.tf index 64844afb0..fc994ea8f 100644 --- a/caf_solution/local.webapp.tf +++ b/caf_solution/local.webapp.tf @@ -3,6 +3,7 @@ locals { var.webapp, { app_service_environments = var.app_service_environments + app_service_environments_v3 = var.app_service_environments_v3 app_service_plans = var.app_service_plans app_services = var.app_services azurerm_application_insights = var.azurerm_application_insights diff --git a/caf_solution/locals.remote_tfstates.tf b/caf_solution/locals.remote_tfstates.tf index 874de6961..d6c0e7646 100644 --- a/caf_solution/locals.remote_tfstates.tf +++ b/caf_solution/locals.remote_tfstates.tf @@ -16,8 +16,8 @@ locals { data "terraform_remote_state" "remote" { for_each = try(var.landingzone.tfstates, {}) - backend = try(each.value.backend_type, var.landingzone.backend_type, "azurerm") - config = local.remote_state[try(each.value.backend_type, var.landingzone.backend_type, "azurerm")][each.key] + backend = try(each.value.backend_type, "azurerm") + config = local.remote_state[try(each.value.backend_type, "azurerm")][each.key] } locals { @@ -37,8 +37,8 @@ locals { } remote = { for key, value in try(var.landingzone.tfstates, {}) : key => { - hostname = try(value.hostname, null) - organization = value.organization + hostname = try(value.hostname, var.tf_cloud_hostname) + organization = try(value.organization, var.tf_cloud_organization) workspaces = { name = value.workspace } @@ -46,14 +46,15 @@ locals { } } - tags = merge(try(local.global_settings.tags, {}), { "level" = var.landingzone.level }, try({ "environment" = local.global_settings.environment }, {}), { "rover_version" = var.rover_version }, var.tags) + tags = merge(try(local.global_settings.tags, {}), { "level" = try(var.landingzone.level, var.landingzone[var.backend_type].level) }, try({ "environment" = local.global_settings.environment }, {}), { "rover_version" = var.rover_version }, var.tags) global_settings = merge( var.global_settings, try(data.terraform_remote_state.remote[var.landingzone.global_settings_key].outputs.objects[var.landingzone.global_settings_key].global_settings, null), try(data.terraform_remote_state.remote[var.landingzone.global_settings_key].outputs.global_settings, null), try(data.terraform_remote_state.remote[keys(var.landingzone.tfstates)[0]].outputs.global_settings, null), - local.custom_variables + local.custom_variables, + var.global_settings_override ) diff --git a/caf_solution/main.tf b/caf_solution/main.tf index 31ed6f558..e5c4afccc 100644 --- a/caf_solution/main.tf +++ b/caf_solution/main.tf @@ -23,7 +23,7 @@ terraform { version = "~> 1.2.0" } } - required_version = ">= 0.15" + required_version = ">= 1.3.0" } provider "azuread" { @@ -93,11 +93,11 @@ locals { tfstates = merge( tomap( { - (var.landingzone.key) = local.backend[var.landingzone.backend_type] + (try(var.landingzone.key, var.landingzone[var.backend_type].key)) = local.backend[try(var.landingzone.backend_type, var.backend_type)] } ) , - data.terraform_remote_state.remote[var.landingzone.global_settings_key].outputs.tfstates + try(data.terraform_remote_state.remote[var.landingzone.global_settings_key].outputs.tfstates, {}) ) @@ -107,13 +107,13 @@ locals { container_name = var.tfstate_container_name resource_group_name = var.tfstate_resource_group_name key = var.tfstate_key - level = var.landingzone.level + level = try(var.landingzone.level, var.landingzone[var.backend_type].level) tenant_id = var.tenant_id subscription_id = data.azurerm_client_config.current.subscription_id } remote = { - hostname = try(var.tfstate_hostname, "app.terraform.io") - organization = var.tfstate_organization + hostname = var.tf_cloud_hostname + organization = var.tf_cloud_organization workspaces = { name = var.workspace } diff --git a/caf_solution/output.tf b/caf_solution/output.tf index 941300c13..b345d4419 100644 --- a/caf_solution/output.tf +++ b/caf_solution/output.tf @@ -1,7 +1,7 @@ output "objects" { value = tomap( { - (var.landingzone.key) = { + (try(var.landingzone.key, var.landingzone[var.backend_type].key)) = { for key, value in module.solution : key => value if try(value, {}) != {} } diff --git a/caf_solution/variables.compute.tf b/caf_solution/variables.compute.tf index d624b3b85..727055852 100644 --- a/caf_solution/variables.compute.tf +++ b/caf_solution/variables.compute.tf @@ -77,4 +77,7 @@ variable "wvd_workspaces" { } variable "runbooks" { default = {} +} +variable "aro_clusters" { + default = {} } \ No newline at end of file diff --git a/caf_solution/variables.data_protection.tf b/caf_solution/variables.data_protection.tf new file mode 100644 index 000000000..ab4eda95a --- /dev/null +++ b/caf_solution/variables.data_protection.tf @@ -0,0 +1,12 @@ +variable "data_protection" { + default = {} +} +variable "backup_vaults" { + default = {} +} +variable "backup_vault_policies" { + default = {} +} +variable "backup_vault_instances" { + default = {} +} \ No newline at end of file diff --git a/caf_solution/variables.database.tf b/caf_solution/variables.database.tf index cabeaf701..89574d73c 100644 --- a/caf_solution/variables.database.tf +++ b/caf_solution/variables.database.tf @@ -1,6 +1,10 @@ variable "app_config" { default = {} } +variable "app_config_entries" { + description = "Map of objects describing kv entries to an app config." + default = {} +} variable "azurerm_redis_caches" { default = {} } @@ -79,4 +83,4 @@ variable "postgresql_servers" { } variable "synapse_workspaces" { default = {} -} \ No newline at end of file +} diff --git a/caf_solution/variables.shared_services.tf b/caf_solution/variables.shared_services.tf index f05a440c9..d5d8ad40d 100644 --- a/caf_solution/variables.shared_services.tf +++ b/caf_solution/variables.shared_services.tf @@ -12,6 +12,10 @@ variable "automations" { default = {} } +variable "automation_log_analytics_links" { + default = {} +} + variable "consumption_budgets" { default = {} } @@ -36,6 +40,10 @@ variable "packer_service_principal" { default = {} } +variable "packer_build" { + default = {} +} + variable "packer_managed_identity" { default = {} } diff --git a/caf_solution/variables.tf b/caf_solution/variables.tf index 1a8780e1a..0c77a4dfc 100644 --- a/caf_solution/variables.tf +++ b/caf_solution/variables.tf @@ -44,14 +44,27 @@ variable "sas_token" { variable "landingzone" { default = { - backend_type = "azurerm" - global_settings_key = "launchpad" - level = "level1" - key = "caf_examples" - tfstates = { - launchpad = { - level = "lower" - tfstate = "caf_launchpad.tfstate" + azurerm = { + backend_type = "azurerm" + global_settings_key = "launchpad" + level = "level1" + key = "caf_examples" + tfstates = { + launchpad = { + level = "lower" + tfstate = "caf_launchpad.tfstate" + } + } + } + remote = { + backend_type = "remote" + global_settings_key = "launchpad" + level = "level1" + key = "caf_examples" + tfstates = { + launchpad = { + tfstate = "caf_launchpad.tfstate" + } } } } @@ -61,6 +74,10 @@ variable "global_settings" { default = {} } +variable "global_settings_override" { + default = {} +} + variable "rover_version" { default = "caf_standalone" } diff --git a/caf_solution/variables.tfc.tf b/caf_solution/variables.tfc.tf new file mode 100644 index 000000000..c3f8612a2 --- /dev/null +++ b/caf_solution/variables.tfc.tf @@ -0,0 +1,18 @@ +variable "backend_type" { + default = "azurerm" + description = "Set the terraform remote backend provider." + validation { + condition = contains(["azurerm", "remote"], var.backend_type) + error_message = "Only azurerm or remote are supported at that time." + } +} + +variable "tf_cloud_organization" { + default = null + description = "When user backend_type with remote, set the TFC/TFE organization name." +} + +variable "tf_cloud_hostname" { + default = "app.terraform.io" + description = "When user backend_type with remote, set the TFC/TFE hostname." +} \ No newline at end of file diff --git a/caf_solution/variables.webapp.tf b/caf_solution/variables.webapp.tf index 3445b594b..befbe90df 100644 --- a/caf_solution/variables.webapp.tf +++ b/caf_solution/variables.webapp.tf @@ -12,6 +12,9 @@ variable "webapp" { variable "app_service_environments" { default = {} } +variable "app_service_environments_v3" { + default = {} +} variable "app_service_plans" { default = {} } diff --git a/caf_solution/vm_extensions.tf b/caf_solution/vm_extensions.tf index bf2648a8a..41cdc59ad 100644 --- a/caf_solution/vm_extensions.tf +++ b/caf_solution/vm_extensions.tf @@ -1,114 +1,22 @@ -# -# microsoft_enterprise_cloud_monitoring - Install the monitoring agent in the virtual machine -# - -module "vm_extension_monitoring_agent" { - # source = "aztfmod/caf/azurerm//modules/compute/virtual_machine_extensions" - # version = "5.5.5" - - source = "git::https://github.com/aztfmod/terraform-azurerm-caf.git//modules/compute/virtual_machine_extensions?ref=main" - - depends_on = [module.solution] - - for_each = { - for key, value in try(var.virtual_machines, {}) : key => value - if try(value.virtual_machine_extensions.microsoft_enterprise_cloud_monitoring, null) != null - } - - client_config = module.solution.client_config - virtual_machine_id = module.solution.virtual_machines[each.key].id - extension = each.value.virtual_machine_extensions.microsoft_enterprise_cloud_monitoring - extension_name = "microsoft_enterprise_cloud_monitoring" - settings = { - diagnostics = merge(module.solution.diagnostics, local.diagnostics) - } +## Extensions have moved! We now have them inside the module directly to improve graph processing +## The moved instructions are supported Terraform 1.1 and are to be removed after a couple of release. +moved { + from = module.vm_extension_monitoring_agent + to = module.solution.module.vm_extension_monitoring_agent } - -module "vm_extension_diagnostics" { - # source = "aztfmod/caf/azurerm//modules/compute/virtual_machine_extensions" - # version = "5.5.5" - - source = "git::https://github.com/aztfmod/terraform-azurerm-caf.git//modules/compute/virtual_machine_extensions?ref=main" - - depends_on = [module.solution] - - for_each = { - for key, value in try(var.virtual_machines, {}) : key => value - if try(value.virtual_machine_extensions.microsoft_azure_diagnostics, null) != null - } - - client_config = module.solution.client_config - virtual_machine_id = module.solution.virtual_machines[each.key].id - extension = each.value.virtual_machine_extensions.microsoft_azure_diagnostics - extension_name = "microsoft_azure_diagnostics" - settings = { - var_folder_path = var.var_folder_path - diagnostics = merge(module.solution.diagnostics, local.diagnostics) - xml_diagnostics_file = try(each.value.virtual_machine_extensions.microsoft_azure_diagnostics.xml_diagnostics_file, null) - diagnostics_storage_account_keys = each.value.virtual_machine_extensions.microsoft_azure_diagnostics.diagnostics_storage_account_keys - } +moved { + from = module.vm_extension_diagnostics + to = module.solution.module.vm_extension_diagnostics } - -module "vm_extension_microsoft_azure_domainjoin" { - # source = "aztfmod/caf/azurerm//modules/compute/virtual_machine_extensions" - # version = "5.5.5" - - source = "git::https://github.com/aztfmod/terraform-azurerm-caf.git//modules/compute/virtual_machine_extensions?ref=main" - - depends_on = [module.solution] - - for_each = { - for key, value in try(var.virtual_machines, {}) : key => value - if try(value.virtual_machine_extensions.microsoft_azure_domainjoin, null) != null - } - - client_config = module.solution.client_config - virtual_machine_id = module.solution.virtual_machines[each.key].id - extension = each.value.virtual_machine_extensions.microsoft_azure_domainjoin - extension_name = "microsoft_azure_domainJoin" - keyvaults = merge(tomap({ (var.landingzone.key) = module.solution.keyvaults }), try(local.remote.keyvaults, {})) +moved { + from = module.vm_extension_microsoft_azure_domainjoin + to = module.solution.module.vm_extension_microsoft_azure_domainjoin } - -module "vm_extension_session_host_dscextension" { - # source = "aztfmod/caf/azurerm//modules/compute/virtual_machine_extensions" - # version = "5.5.5" - - source = "git::https://github.com/aztfmod/terraform-azurerm-caf.git//modules/compute/virtual_machine_extensions?ref=main" - - depends_on = [module.vm_extension_microsoft_azure_domainjoin] - - for_each = { - for key, value in try(var.virtual_machines, {}) : key => value - if try(value.virtual_machine_extensions.session_host_dscextension, null) != null - } - - client_config = module.solution.client_config - virtual_machine_id = module.solution.virtual_machines[each.key].id - extension = each.value.virtual_machine_extensions.session_host_dscextension - extension_name = "session_host_dscextension" - keyvaults = merge(tomap({ (var.landingzone.key) = module.solution.keyvaults }), try(local.remote.keyvaults, {})) - wvd_host_pools = merge(tomap({ (var.landingzone.key) = module.solution.wvd_host_pools }), try(local.remote.wvd_host_pools, {})) -} - - -module "vm_extension_custom_scriptextension" { - # source = "aztfmod/caf/azurerm//modules/compute/virtual_machine_extensions" - # version = "5.5.5" - - source = "git::https://github.com/aztfmod/terraform-azurerm-caf.git//modules/compute/virtual_machine_extensions?ref=main" - - depends_on = [module.solution, module.vm_extension_microsoft_azure_domainjoin] - - for_each = { - for key, value in try(var.virtual_machines, {}) : key => value - if try(value.virtual_machine_extensions.custom_script, null) != null - } - - client_config = module.solution.client_config - virtual_machine_id = module.solution.virtual_machines[each.key].id - virtual_machine_os_type = module.solution.virtual_machines[each.key].os_type - extension = each.value.virtual_machine_extensions.custom_script - extension_name = "custom_script" - managed_identities = merge(tomap({ (var.landingzone.key) = module.solution.managed_identities }), try(local.remote.managed_identities, {})) - storage_accounts = merge(tomap({ (var.landingzone.key) = module.solution.storage_accounts }), try(local.remote.storage_accounts, {})) +moved { + from = module.vm_extension_session_host_dscextension + to = module.solution.module.vm_extension_session_host_dscextension } +moved { + from = module.vm_extension_custom_scriptextension + to = module.solution.module.vm_extension_custom_scriptextension +} \ No newline at end of file diff --git a/caf_solution/vmss_extensions.tf b/caf_solution/vmss_extensions.tf index c6245900d..48743addb 100644 --- a/caf_solution/vmss_extensions.tf +++ b/caf_solution/vmss_extensions.tf @@ -1,41 +1,12 @@ -module "vmss_extension_microsoft_azure_domainjoin" { - # source = "aztfmod/caf/azurerm//modules/compute/virtual_machine_scale_set_extensions" - # version = "5.5.5" +## Extensions have moved! We now have them inside the module directly to improve graph processing +## The moved instructions are supported Terraform 1.1 and are to be removed after a couple of release. - source = "git::https://github.com/aztfmod/terraform-azurerm-caf.git//modules/compute/virtual_machine_scale_set_extensions?ref=main" - - depends_on = [module.solution] - - for_each = { - for key, value in try(var.virtual_machine_scale_sets, {}) : key => value - if try(value.virtual_machine_scale_set_extensions.microsoft_azure_domainjoin, null) != null - } - - client_config = module.solution.client_config - virtual_machine_scale_set_id = module.solution.virtual_machine_scale_sets[each.key].id - extension = each.value.virtual_machine_scale_set_extensions.microsoft_azure_domainjoin - extension_name = "microsoft_azure_domainJoin" - keyvaults = merge(tomap({ (var.landingzone.key) = module.solution.keyvaults }), try(local.remote.keyvaults, {})) +moved { + from = module.vmss_extension_custom_scriptextension + to = module.solution.module.vmss_extension_custom_scriptextension } - -module "vmss_extension_custom_scriptextension" { - # source = "aztfmod/caf/azurerm//modules/compute/virtual_machine_scale_set_extensions" - # version = "5.5.5" - - source = "git::https://github.com/aztfmod/terraform-azurerm-caf.git//modules/compute/virtual_machine_scale_set_extensions?ref=main" - - depends_on = [module.solution] - - for_each = { - for key, value in try(var.virtual_machine_scale_sets, {}) : key => value - if try(value.virtual_machine_scale_set_extensions.custom_script, null) != null - } - - client_config = module.solution.client_config - virtual_machine_scale_set_id = module.solution.virtual_machine_scale_sets[each.key].id - extension = each.value.virtual_machine_scale_set_extensions.custom_script - extension_name = "custom_script" - managed_identities = merge(tomap({ (var.landingzone.key) = module.solution.managed_identities }), try(local.remote.managed_identities, {})) - storage_accounts = merge(tomap({ (var.landingzone.key) = module.solution.storage_accounts }), try(local.remote.storage_accounts, {})) -} +moved { + from = module.vmss_extension_microsoft_azure_domainjoin + to = module.solution.module.vmss_extension_microsoft_azure_domainjoin +} \ No newline at end of file diff --git a/rover_on_ssh_host.yml b/rover_on_ssh_host.yml index 8dc2b93c9..68fc2f06c 100644 --- a/rover_on_ssh_host.yml +++ b/rover_on_ssh_host.yml @@ -11,7 +11,7 @@ version: '3.7' services: rover: - image: aztfmod/rover-preview:1.2.1-2205.300342 + image: aztfmod/rover:1.2.1-2206.1703 user: vscode diff --git a/templates/ansible/action_plugins/__pycache__/merge_vars.cpython-39.pyc b/templates/ansible/action_plugins/__pycache__/merge_vars.cpython-39.pyc index 63e23883c..6f060503a 100644 Binary files a/templates/ansible/action_plugins/__pycache__/merge_vars.cpython-39.pyc and b/templates/ansible/action_plugins/__pycache__/merge_vars.cpython-39.pyc differ diff --git a/templates/ansible/ansible.yaml b/templates/ansible/ansible.yaml index 7308bfd42..2de7ffb9d 100644 --- a/templates/ansible/ansible.yaml +++ b/templates/ansible/ansible.yaml @@ -8,7 +8,11 @@ job_cache_base_path: "/home/vscode/.terraform.cache" destination_base_path: '{{ platform_configuration_folder }}' resource_template_folder: "{{ public_templates_folder }}/resources" - platform_service_folder: "{{ public_templates_folder }}/platform/services" + platform_service_folder: "{{ private_templates_folder if private_templates_folder is defined else public_templates_folder + '/platform/services' }}" + multiple_lines_list: "{{ multiple_lines_list | default(false) }}" + + - debug: + msg: "platform_service_folder is: {{platform_service_folder}}" - name: "load {{ template_folder | default(platform_definition_folder)}}/ignite.yaml" include_vars: @@ -54,40 +58,31 @@ - find: paths: "{{definition_folder | default(platform_definition_folder)}}" recurse: yes - patterns: "*.yaml" + patterns: ["*.yaml","*.j2"] file_type: file excludes: "*.caf.platform.yaml" register: yaml_files_to_process - when: deployment_mode == 'platform' - - - name: load variables - set_fact: - "{{item.path | basename | regex_replace('.yaml$', '')}}_resource__to_merge": "{{ lookup('template', item.path )}}" - with_items: "{{yaml_files_to_process.files}}" - when: deployment_mode == 'platform' + - name: "Creates cache directory" + file: + path: "{{ job_cache_base_path }}/launchpad" + state: directory + + - name: Load platform variables from tfstates + include_tasks: "load_variables.yaml" - - name: load variables - set_fact: - merged_resources: "{{ merged_resources | default({}) | combine( lookup('template', item.path ) | from_yaml )}}" - with_items: "{{yaml_files_to_process.files}}" + - name: Load firewall rules + include_tasks: "load_firewall_rules.yaml" when: deployment_mode == 'platform' - - set_fact: - resources: "{{ merged_resources }}" - - name: "Creates destination directory - {{destination_base_path}}" file: path: "{{destination_base_path}}" state: directory - - name: "Creates cache directory" - file: - path: "{{ job_cache_base_path }}/launchpad" - state: directory - - debug: msg: + - "launchpad_tfstate_exists: {{launchpad_tfstate_exists}}" - "bootstrap: {{bootstrap}}" - "resources: {{resources}}" @@ -110,10 +105,12 @@ loop_control: loop_var: region when: - - bootstrap.deployments[deployment_mode].alz is defined and launchpad_tfstate_exists.rc == 0 + - launchpad_tfstate_exists.rc == 0 + - bootstrap.deployments[deployment_mode].alz is defined vars: lz_type: "{{deployment_mode}}" stage: alz + when: deployment_mode == 'platform' # # Process the deployments folders @@ -137,12 +134,46 @@ vars: lz_type: "{{deployment_mode}}" stage: scale_out_domains - + + +# Copy github workflows + - find: + paths: "{{public_templates_folder}}/pipelines/.github/workflows" + recurse: yes + file_type: file + register: github_workflows_to_process + + - name: copy github workflows + ansible.builtin.template: + src: "{{ item.path }}" + dest: "{{base_folder}}/{{ item.path | regex_replace(public_templates_folder + '/pipelines', '') }}" + loop: "{{github_workflows_to_process.files}}" + +# Copy default firewall rules + + - name: "Creates destination directory - {{firewall_rules_path}}" + file: + path: "{{firewall_rules_path}}/{{item}}" + state: directory + with_items: + - application_rule_collections + - network_rule_collections + - nat_rule_collections + + - find: + paths: "{{public_templates_folder}}/firewall_rules" + recurse: yes + file_type: file + register: firewall_files_to_process + + - name: copy firewall files + ansible.builtin.copy: + src: "{{ item.path }}" + dest: "{{firewall_rules_path}}/{{ item.path | regex_replace(public_templates_folder + '/firewall_rules', '') }}" + force: no + loop: "{{firewall_files_to_process.files}}" -# -# Formatting & Linters -# - - name: Terraform Formatting - shell: | - terraform fmt -recursive {{ destination_base_path }} + - name: Format and validate file + shell: "terraform fmt -recursive {{ platform_configuration_folder }}" + register: fmt \ No newline at end of file diff --git a/templates/ansible/get_tfstate_content.yaml b/templates/ansible/get_tfstate_content.yaml new file mode 100644 index 000000000..2e74b818b --- /dev/null +++ b/templates/ansible/get_tfstate_content.yaml @@ -0,0 +1,117 @@ +- debug: + msg: + - "env: '{{env}}'" + - "exported_variable_name: '{{exported_variable_name}}'" + - "resource_key: '{{resource_key | default('')}}'" + +- set_fact: + tfstate_obj: "{{resources.tfstates.platform[tfstate_key] if env == '' else resources.tfstates.platform[tfstate_key][env]}}" + tfstate_key_name: "{{resources.tfstates.platform[tfstate_key].lz_key_name if env == '' else resources.tfstates.platform[tfstate_key][env].lz_key_name}}" + env_var: "{{ env if env != '' else '' }}" + +# Get tfstate +- name: "[Get {{ tfstate_key_name }} tfstate account name" + register: storage_account + shell: | + az storage account list \ + --subscription {{ resources.caf_launchpad.subscription_id }} \ + --query "[?tags.caf_tfstate=='{{ caf_level | default('level0') }}' && tags.caf_environment=='{{ resources.caf_environment }}'].{name:name}[0]" -o json | jq -r .name + +- set_fact: + "{{tfstate_key_name}}_storage_account": "{{ lookup('vars', 'storage_account') }}" + +- debug: + msg: "{{lookup('vars', tfstate_key_name + '_storage_account')}}" + +- name: "[Get subscription details for {{ tfstate_obj.tfstate }} }}" + register: tfstate_exists + ignore_errors: true + shell: | + az storage blob download \ + --name "{{ tfstate_obj.tfstate }}" \ + --account-name "{{ lookup('vars', tfstate_key_name + '_storage_account').stdout }}" \ + --container-name "{{ tfstate_obj.workspace | default('tfstate') }}" \ + --auth-mode "login" \ + --file "{{ job_cache_base_path }}/launchpad/{{ tfstate_obj.tfstate }}" + +- set_fact: + "{{tfstate_key_name}}_tfstate_exists": "{{ lookup('vars', 'tfstate_exists') }}" + +- name: "Get {{tfstate_key_name}} details from tfstate" + when: + - lookup('vars', tfstate_key_name + '_tfstate_exists').rc == 0 + shell: "cat {{ job_cache_base_path }}/launchpad/{{ tfstate_obj.tfstate }}" + register: "tfstate_file" + +- set_fact: + "{{tfstate_key_name}}_tfstate": "{{ lookup('vars', 'tfstate_file') }}" + when: + - lookup('vars', tfstate_key_name + '_tfstate_exists').rc == 0 + +# - debug: +# msg: "{{lookup('vars', tfstate_key_name + '_tfstate')}}" + +- name: "Get {{tfstate_key_name}} json data" + when: + - lookup('vars', tfstate_key_name + '_tfstate_exists').rc == 0 + - lookup('vars', tfstate_key_name + '_storage_account').rc == 0 + set_fact: + jsondata: "{{ lookup('vars', tfstate_key_name + '_tfstate').stdout | from_json }}" + +- name: "Set {{exported_variable_name}}" + when: + - lookup('vars', tfstate_key_name + '_tfstate_exists').rc == 0 + - lookup('vars', tfstate_key_name + '_tfstate') + - resource_key is undefined + - env_var == '' + set_fact: + "{{exported_variable_name}}": "{{ jsondata | default({}) | json_query(path)}}" + vars: + path: 'outputs.objects.value.{{tfstate_key_name}}.{{resource_type}}' + +- name: "Set {{exported_variable_name}} with env:{{env}} - no resource_key" + when: + - lookup('vars', tfstate_key_name + '_tfstate_exists').rc == 0 + - lookup('vars', tfstate_key_name + '_tfstate') + - resource_key is undefined + - env_var != '' + set_fact: + "{{exported_variable_name}}": "{{ lookup('vars', exported_variable_name, default={}) | combine({item.0: item.1}, recursive=True ) }}" + with_together: + - ["{{env}}"] + - ["{{jsondata | default({}) | json_query(path)}}"] + vars: + path: 'outputs.objects.value.{{tfstate_key_name}}.{{resource_type}}' + +- name: "Set {{exported_variable_name}} - (with resource_key: {{resource_key}})" + when: + - lookup('vars', tfstate_key_name + '_tfstate_exists').rc == 0 + - lookup('vars', tfstate_key_name + '_tfstate') + - resource_key is defined + - env == '' + set_fact: + "{{exported_variable_name}}": "{{ jsondata | default({}) | json_query(path)}}" + vars: + path: 'outputs.objects.value.{{tfstate_key_name}}.{{resource_type}}.{{resource_key}}' + + +- name: "Set {{exported_variable_name}} with env:'{{env}}' - (with resource_key: {{resource_key}})" + when: + - resource_key is defined + - env != '' + - lookup('vars', tfstate_key_name + '_tfstate_exists').rc == 0 + - lookup('vars', tfstate_key_name + '_tfstate') + set_fact: + "{{exported_variable_name}}": "{{ lookup('vars', exported_variable_name, default={}) | combine({item.0: item.1}, recursive=True )) }}" + with_together: + - ["{{env}}"] + - ["{{jsondata | default({}) | json_query(path)}}"] + vars: + path: 'outputs.objects.value.{{tfstate_key_name}}.{{resource_type}}.{{resource_key}}' + +- name: "[{{resources[tfstate].relative_destination_folder}}] cleanup" + when: + - lookup('vars', tfstate_key_name + '_tfstate_exists') == 0 + file: + path: "{{ job_cache_base_path }}/launchpad/{{ tfstate_obj.tfstate }}" + state: absent \ No newline at end of file diff --git a/templates/ansible/load_deployments.yaml b/templates/ansible/load_deployments.yaml index c4a87afaf..7007e8839 100644 --- a/templates/ansible/load_deployments.yaml +++ b/templates/ansible/load_deployments.yaml @@ -7,29 +7,29 @@ - name: "Process 1 deployment file {{stage}}/{{region}}" set_fact: - "{{stage}}_{{region}}_{{item}}_deployment__to_merge": "{{ lookup('template', '{{ platform_service_folder + \"/\" + topology.deployments[deployment_mode][stage][region][item]}}') | from_yaml }}" + "{{stage}}_{{region}}_{{item}}_deployment__to_merge": "{{ lookup('template', '{{ platform_service_folder + \"/\" + bootstrap.deployments[deployment_mode][stage][region][item]}}') | from_yaml }}" loop: "{{topology.deployments[deployment_mode][stage][region].keys()}}" when: - stage == 'root' or stage == 'alz' - - topologies is not defined + - resources is not defined - name: "Copy file {{stage}} from {{platform_service_folder}}" ansible.builtin.template: src: "{{platform_service_folder}}/{{topology.deployments[deployment_mode][stage][region][item]}}" - dest: "{{destination_path}}/{{topologies[item].tfstate.config_file}}" + dest: "{{destination_path}}/{{resources[item].tfstate.config_file}}" loop: "{{topology.deployments[deployment_mode][stage][region].keys()}}" when: - stage == 'root' - - topologies is defined + - resources is defined - name: "Copy file {{stage}} from {{platform_service_folder}}" ansible.builtin.template: src: "{{platform_service_folder}}/{{topology.deployments[deployment_mode][stage][region][item]}}" - dest: "{{destination_path}}/{{topologies[stage + '_' + item].tfstate.config_file}}" + dest: "{{destination_path}}/{{resources[stage + '_' + item].tfstate.config_file}}" loop: "{{topology.deployments[deployment_mode][stage][region].keys()}}" when: - stage == 'alz' - - topologies is defined + - resources is defined - name: "Process 2 deployment file {{stage}}" @@ -39,7 +39,7 @@ loop_var: service when: - stage == 'alz' - - topologies is defined + - resources is defined - name: "Process 2 deployment file {{stage}}" include_tasks: "load_deployments_env.yaml" diff --git a/templates/ansible/load_deployments_alz.yaml b/templates/ansible/load_deployments_alz.yaml index da10a28ae..344225a66 100644 --- a/templates/ansible/load_deployments_alz.yaml +++ b/templates/ansible/load_deployments_alz.yaml @@ -7,7 +7,7 @@ - name: "{{destination_alz_path}} - Set tfstate_object" set_fact: - tfstate_object: "{{topologies['alz_' + service].tfstate}}" + tfstate_object: "{{resources['alz_' + service].tfstate}}" - name: "{{destination_path}}/{{stage}} - Set landingzone file_path" set_fact: @@ -65,5 +65,5 @@ - name: "{{deployment_mode}}/{{stage}}/{{region}}/{{service}} to {{destination_path}}/{{'alz_' + service}}.yaml" ansible.builtin.template: src: "{{platform_service_folder}}/{{topology.deployments[deployment_mode][stage][region][item]}}" - dest: "{{destination_path}}/{{topologies[stage + '_' + item].tfstate.config_file}}" + dest: "{{destination_path}}/{{resources[stage + '_' + item].tfstate.config_file}}" loop: "{{topology.deployments[deployment_mode][stage][region].keys()}}" \ No newline at end of file diff --git a/templates/ansible/load_deployments_env.yaml b/templates/ansible/load_deployments_env.yaml index 0c197a12d..5f5bfd55d 100644 --- a/templates/ansible/load_deployments_env.yaml +++ b/templates/ansible/load_deployments_env.yaml @@ -12,7 +12,7 @@ loop_control: loop_var: env when: - - topologies is not defined + - resources is not defined - name: "Creates directory" @@ -23,16 +23,16 @@ loop_control: loop_var: env when: - - topologies is defined + - resources is defined - name: "Copy file {{stage}}/{{service}}" ansible.builtin.template: src: "{{platform_service_folder}}/{{topology.deployments[deployment_mode][stage][region][service][env]}}" - dest: "{{destination_path}}/{{stage}}/{{env}}/{{topologies[service + '_' + env].tfstate.config_file}}" + dest: "{{destination_path}}/{{stage}}/{{env}}/{{resources[service + '_' + env].tfstate.config_file}}" loop: "{{topology.deployments[deployment_mode][stage][region][service].keys()}}" loop_control: loop_var: env when: - - topologies is defined + - resources is defined diff --git a/templates/ansible/load_firewall_rules.yaml b/templates/ansible/load_firewall_rules.yaml new file mode 100644 index 000000000..b15234528 --- /dev/null +++ b/templates/ansible/load_firewall_rules.yaml @@ -0,0 +1,55 @@ +# Load firewall rules + +# application_rule_collections + +- find: + paths: "{{firewall_rules_path}}/application_rule_collections" + recurse: yes + patterns: "*.yaml" + file_type: file + register: fw_rules_application_rule_collections + +- name: load firewall application_rule_collections + set_fact: + merged_fw_rules_application_rule_collections: "{{ merged_fw_rules_application_rule_collections | default({}) | combine( lookup('template', item.path ) | from_yaml )}}" + with_items: "{{fw_rules_application_rule_collections.files}}" + +- set_fact: + fw_rules_application_rule_collections: "{{ merged_fw_rules_application_rule_collections }}" + when: fw_rules_application_rule_collections is defined + +# fw_rules_nat_rule_collections + +- find: + paths: "{{firewall_rules_path}}/nat_rule_collections" + recurse: yes + patterns: "*.yaml" + file_type: file + register: fw_rules_nat_rule_collections + +- name: load firewall nat_rule_collections + set_fact: + merged_fw_rules_nat_rule_collections: "{{ merged_fw_rules_nat_rule_collections | default({}) | combine( lookup('template', item.path ) | from_yaml )}}" + with_items: "{{fw_rules_nat_rule_collections.files}}" + +- set_fact: + fw_rules_nat_rule_collections: "{{ merged_fw_rules_nat_rule_collections }}" + when: merged_fw_rules_nat_rule_collections is defined + +# network_rule_collections + +- find: + paths: "{{firewall_rules_path}}/network_rule_collections" + recurse: yes + patterns: "*.yaml" + file_type: file + register: fw_rules_network_rule_collections + +- name: load firewall network_rule_collections + set_fact: + merged_fw_rules_network_rule_collections: "{{ merged_fw_rules_network_rule_collections | default({}) | combine( lookup('template', item.path ) | from_yaml )}}" + with_items: "{{fw_rules_network_rule_collections.files}}" + +- set_fact: + fw_rules_network_rule_collections: "{{ merged_fw_rules_network_rule_collections }}" + when: merged_fw_rules_network_rule_collections is defined diff --git a/templates/ansible/load_regions.yaml b/templates/ansible/load_regions.yaml index 72fa0a100..c49c1784c 100644 --- a/templates/ansible/load_regions.yaml +++ b/templates/ansible/load_regions.yaml @@ -5,12 +5,12 @@ when: stage != 'alz' - include_tasks: "load_deployments.yaml" - loop: "{{topology.deployments[deployment_mode][stage].keys()}}" + loop: "{{bootstrap.deployments[deployment_mode][stage].keys()}}" loop_control: loop_var: region when: - stage == 'alz' - - topologies is not defined + - resources is not defined - include_tasks: "load_alz.yaml" loop: "{{topology.deployments[deployment_mode][stage].keys()}}" @@ -18,5 +18,5 @@ loop_var: region when: - stage == 'alz' - - topologies is defined + - resources is defined diff --git a/templates/ansible/load_variables.yaml b/templates/ansible/load_variables.yaml new file mode 100644 index 000000000..b779b2f03 --- /dev/null +++ b/templates/ansible/load_variables.yaml @@ -0,0 +1,193 @@ +# Load variables from tfstates + + +# Initial load + +- name: load variables + set_fact: + merged_resource__to_merge: "{{ merged_resource__to_merge | default({}) | combine( lookup('template', item.path ) | from_yaml )}}" + with_items: "{{yaml_files_to_process.files}}" + when: deployment_mode == 'platform' + +- name: Merge resources variables + merge_vars: + suffix_to_merge: _resource__to_merge + merged_var_name: merged_resources + expected_type: 'dict' + recursive_dict_merge: True + +- set_fact: + resources: "{{ merged_resources }}" + +# Azure AD Groups +- name: tfstate - azuread_groups from launchpad to variable + include_tasks: "get_tfstate_content.yaml" + register: launchpad_tfstate_exists + vars: + env: '' + tfstate_key: launchpad + resource_type: azuread_groups + caf_level: level0 + exported_variable_name: launchpad_azuread_groups + when: deployment_mode == 'platform' + +# Keyvaults having credentials +- name: tfstate - keyvaults from launchpad_credentials to variable + include_tasks: "get_tfstate_content.yaml" + register: credentials_tfstate_exists + vars: + env: '' + tfstate_key: launchpad_credentials + resource_type: keyvaults + caf_level: level0 + exported_variable_name: keyvaults + when: deployment_mode == 'platform' + +# Keyvault_scl credentials +- name: tfstate - keyvaults from launchpad_credentials to variable + include_tasks: "get_tfstate_content.yaml" + register: credentials_tfstate_exists + vars: + env: '' + tfstate_key: launchpad_credentials + resource_type: keyvaults + caf_level: cred_subscription_creation_landingzones + exported_variable_name: keyvault_scl + when: deployment_mode == 'asvm' + +# Platform subscriptions +- name: tfstate - platform_subscriptions from subscriptions to variable + include_tasks: "get_tfstate_content.yaml" + when: + - resources.subscription_deployment_mode == 'multiple_subscriptions' + vars: + env: '' + tfstate_key: platform_subscriptions + resource_type: subscriptions + caf_level: level1 + exported_variable_name: platform_subscriptions + when: deployment_mode == 'platform' + +# Private DNS zones +- name: tfstate - private dns zones to variable + include_tasks: "get_tfstate_content.yaml" + vars: + resource_type: private_dns + tfstate_key: private_dns + caf_level: level2 + exported_variable_name: private_dns + loop_control: + loop_var: env + loop: "{{ deployments.scale_out_domain_keys }}" + +# Virtual hub prod +- name: tfstate - virtual hub prod to variable + include_tasks: "get_tfstate_content.yaml" + vars: + resource_type: virtual_hubs + tfstate_key: virtual_hubs + caf_level: level2 + exported_variable_name: virtual_hubs_resources + loop_control: + loop_var: env + loop: "{{ deployments.scale_out_domain_keys }}" + +# Secure firewalls prod +- name: tfstate - secure firewall prod to variable + include_tasks: "get_tfstate_content.yaml" + vars: + resource_type: azurerm_firewalls + tfstate_key: secure_firewalls + caf_level: level2 + exported_variable_name: secure_firewalls_resources + loop_control: + loop_var: env + loop: "{{ deployments.scale_out_domain_keys }}" + + +# azurerm_firewall for dns resolution +- name: tfstate - azurerm_firewalls from private_dns_firewalls to variable + include_tasks: "get_tfstate_content.yaml" + vars: + resource_type: azurerm_firewalls + tfstate_key: private_dns_firewalls + caf_level: level2 + exported_variable_name: private_dns_firewalls + loop_control: + loop_var: env + loop: "{{ deployments.scale_out_domain_keys }}" + +# azurerm_firewall for dns resolution (vnets) +- name: tfstate - azurerm_firewalls from private_dns_firewalls..vnets to variable + include_tasks: "get_tfstate_content.yaml" + vars: + resource_type: vnets + tfstate_key: private_dns_firewalls + caf_level: level2 + exported_variable_name: private_dns_firewalls_vnets + loop_control: + loop_var: env + loop: "{{ deployments.scale_out_domain_keys }}" + when: deployment_mode == 'platform' + + +# launchpad vnet +- name: tfstate - launchpad vnets to variable + include_tasks: "get_tfstate_content.yaml" + vars: + env: '' + resource_type: vnets + tfstate_key: launchpad + caf_level: level0 + exported_variable_name: vnet_launchpad + when: deployment_mode == 'platform' + +# asvn vnet +- name: tfstate - asvm vnets to variable + include_tasks: "get_tfstate_content.yaml" + vars: + env: '' + resource_type: vnets + tfstate_key: asvm + caf_level: level2 + exported_variable_name: vnet_asvm + when: deployment_mode == 'platform' + +# Platform asvm storage accounts +- name: tfstate - level2_storage_account from launchpad to variable + include_tasks: "get_tfstate_content.yaml" + vars: + env: '' + caf_level: level0 + tfstate_key: launchpad + resource_type: storage_accounts + resource_key: level2 + exported_variable_name: level2_storage_account + when: deployment_mode == 'platform' + +- name: tfstate - storage_account_level3 from launchpad to variable + include_tasks: "get_tfstate_content.yaml" + vars: + env: '' + caf_level: level0 + tfstate_key: launchpad + resource_type: storage_accounts + resource_key: level3 + exported_variable_name: storage_account_level3 + when: deployment_mode == 'asvm' + +# Reloading with the variables from tfstates +- name: load variables + set_fact: + "{{item.path | basename | regex_replace('.yaml$', '')}}_resource__to_merge": "{{ lookup('template', item.path )}}" + with_items: "{{yaml_files_to_process.files}}" + when: deployment_mode == 'platform' + +- name: Combine variables into resources + set_fact: + merged_resources: "{{ merged_resources | default({}) | combine( lookup('template', item.path ) | from_yaml )}}" + with_items: "{{yaml_files_to_process.files}}" + when: deployment_mode == 'platform' + +- set_fact: + resources: "{{ merged_resources }}" \ No newline at end of file diff --git a/templates/ansible/process_resources.yaml b/templates/ansible/process_resources.yaml index beca98235..ce422e072 100644 --- a/templates/ansible/process_resources.yaml +++ b/templates/ansible/process_resources.yaml @@ -20,11 +20,16 @@ # # resources # +# - name: "resources - {{resource_type}}" +# ansible.builtin.template: +# src: "{{ item }}" +# dest: "{{ destination_path }}/{{ item | basename | regex_replace('.j2$', '') }}" +# force: yes +# with_fileglob: +# - "{{resource_type_override if override_file.stat.exists else resource_type_template}}" + - name: "resources - {{resource_type}}" - ansible.builtin.template: - src: "{{ item }}" - dest: "{{ destination_path }}/{{ item | basename | regex_replace('.j2$', '') }}" - force: yes + include_tasks: "render_template.yaml" with_fileglob: - "{{resource_type_override if override_file.stat.exists else resource_type_template}}" diff --git a/templates/ansible/process_subscription_resources.yaml b/templates/ansible/process_subscription_resources.yaml index 38278ea07..51d7eac09 100644 --- a/templates/ansible/process_subscription_resources.yaml +++ b/templates/ansible/process_subscription_resources.yaml @@ -5,6 +5,7 @@ - "{{deployment_mode}}" - "{{tfstate}}" - "{{env}}" + - "{{tfstate_object}}" - name: "{{deployment_mode}} - Set ansible_to_process" set_fact: diff --git a/templates/ansible/process_tfstate.yaml b/templates/ansible/process_tfstate.yaml index 36908567e..a1db0ede3 100644 --- a/templates/ansible/process_tfstate.yaml +++ b/templates/ansible/process_tfstate.yaml @@ -20,7 +20,6 @@ msg: - 'sub_template_folder - {{tfstate_object.sub_template_folder | default()}}' - 'tfstate_object - {{tfstate_object}}' - # - "{{resources}}" - name: "{{deployment_mode}}/{{stage}}/{{tfstate}} - process subscription resources" include_tasks: "process_subscription_resources.yaml" diff --git a/templates/ansible/render_template.yaml b/templates/ansible/render_template.yaml new file mode 100644 index 000000000..1c991378e --- /dev/null +++ b/templates/ansible/render_template.yaml @@ -0,0 +1,24 @@ +- debug: + msg: + - "{{item}}" + +- set_fact: + template_dest_file: "{{ destination_path }}/{{ item | basename | regex_replace('.j2$', '') }}" + +- name: "resources - {{resource_type}}" + ansible.builtin.template: + src: "{{ item }}" + dest: "{{ template_dest_file }}" + force: yes + +- name: Cleanup file from empty lines + shell: "cat '{{template_dest_file}}' | awk 'NF' " + register: grep_output + +- copy: + content: "{{grep_output.stdout }}" + dest: "{{ template_dest_file }}" + +- name: Format and validate file + shell: "terraform fmt {{ template_dest_file }}" + register: fmt \ No newline at end of file diff --git a/templates/ansible/walk-through-single.yaml b/templates/ansible/walk-through-bootstrap.yaml similarity index 96% rename from templates/ansible/walk-through-single.yaml rename to templates/ansible/walk-through-bootstrap.yaml index 1a5a01a80..40084b7f9 100644 --- a/templates/ansible/walk-through-single.yaml +++ b/templates/ansible/walk-through-bootstrap.yaml @@ -18,10 +18,10 @@ private: no default: contoso - - name: caf_environment + - name: input_caf_environment prompt: Set the CAF Environment value private: no - default: contoso + default: "{{TF_VAR_environment}}" - name: prefix prompt: Set the prefix to add to all resource. diff --git a/templates/ansible/walk-through-ci.yaml b/templates/ansible/walk-through-ci.yaml new file mode 100644 index 000000000..fbd08fd5a --- /dev/null +++ b/templates/ansible/walk-through-ci.yaml @@ -0,0 +1,18 @@ +# +# Initial script to select a topology and create the base templates for the definitions folder +# +# ansible-playbook /tf/caf/landingzones/templates/platform/walk-through-single.yaml \ +# -e topology_file=/tf/caf/landingzones/templates/platform/alz_single_subscription.yaml \ +# -e config_folder_platform_templates=/tf/caf/landingzones/templates/platform \ +# -e landingzones_folder=/tf/caf/landingzones \ +# -e destination_base_path=/tf/caf \ +# -e definitions_relative_path=definitions/v1 \ +# -e configuration_relative_path=configuration/demo +# + +- name: Setup platform template repository + hosts: localhost + + tasks: + + - include_tasks: "walk-through.yaml" diff --git a/templates/ansible/walk-through.yaml b/templates/ansible/walk-through.yaml index ebe24391e..12c0cf2d1 100644 --- a/templates/ansible/walk-through.yaml +++ b/templates/ansible/walk-through.yaml @@ -3,9 +3,9 @@ # - name: Get deployment user object_id (make sure you are logged-in to the launchpad Azure subscription first.) - shell: az ad signed-in-user show --query objectId -o tsv + shell: az ad signed-in-user show --query id -o tsv register: bash_object_id - when: lookup('env', 'AZURE_OBJECT_ID') == '' + when: AZURE_OBJECT_ID is undefined - name: Get deployment user UPN shell: az ad signed-in-user show --query userPrincipalName -o tsv @@ -28,15 +28,19 @@ - name: Get default subscription name shell: az account show --query name -o tsv register: subscription_name + +- name: Get public ip address for bootstrap whitelisting + shell: dig @resolver1.opendns.com A myip.opendns.com +short -4 + register: public_ip - set_fact: regions: "{{ azure_regions }}" deployment_mode: "platform" - topology: "{{bootstrap | default()}}" - object_id: "{{ bash_object_id.stdout | default(lookup('env', 'AZURE_OBJECT_ID')) }}" + object_id: "{{ bash_object_id.stdout | default(AZURE_OBJECT_ID) }}" upn: "{{ upn_owner | default(bash_upn.stdout) }}" tenant_name: "{{ tenant_name | default(bash_tenant_name.stdout) }}" - base_folder: "{{ lookup('env', 'AZURE_OBJECT_ID') if lookup('env', 'AZURE_OBJECT_ID') != '' else '/tf/caf' }}" + base_folder: "{{ base_folder | default('/tf/caf') }}" + PUBLIC_IP_WHITE_LIST: "{{ [public_ip.stdout] }}" - debug: msg: @@ -45,9 +49,24 @@ - "{{ object_id }}" - "{{ upn }}" - "{{ base_folder }}" + - "{{ input_caf_environment if input_caf_environment is defined else TF_VAR_environment }}" + + +- name: "load {{ template_folder | default(platform_definition_folder)}}/ignite.yaml" + include_vars: + name: bootstrap + dir: "{{ template_folder | default(platform_definition_folder)}}" + depth: 1 + ignore_unknown_extensions: true + files_matching: "ignite.yaml" +- set_fact: + caf_environment: "{{ TF_VAR_environment }}" + when: TF_VAR_environment is defined + - set_fact: topology: "{{ lookup('template', '{{ topology_file }}') | from_yaml }}" + topology_deployment__to_merge: "{{ lookup('template', '{{ topology_file }}') | from_yaml }}" destination_path: "{{definition_folder | default(platform_definition_folder)}}" resource_template_folder: "{{ public_templates_folder }}/resources" platform_service_folder: "{{ public_templates_folder }}/platform/services" @@ -73,7 +92,7 @@ - debug: msg: - "variables: {{variables}}" - - "{{topology}}" + - "topology: {{topology}}" - include_tasks: "load_regions.yaml" loop: "{{topology.deployments[deployment_mode].keys()}}" @@ -89,13 +108,17 @@ - name: "Topologies merged" set_fact: - topologies: "{{ merged_topologies }}" + resources: "{{ merged_topologies }}" + +# - debug: +# msg: "{{resources}}" # Need topologies to render the following templates - name: "load tfstates" set_fact: "tfstates_deployment__to_merge": "{{ lookup('template', '{{platform_service_folder}}/tfstates.yaml') | from_yaml }}" + - name: Merge deployment files into topologies variable merge_vars: suffix_to_merge: _deployment__to_merge @@ -104,10 +127,10 @@ recursive_dict_merge: True - set_fact: - topologies: "{{ merged_topologies }}" + resources: "{{ merged_topologies }}" -- debug: - msg: "topologies: {{topologies}}" +# - debug: +# msg: "resources1: {{resources}}" # # Generate target folder structure and files @@ -132,6 +155,62 @@ dest: "{{destination_path}}/{{ item.path | basename }}" loop: "{{variable_files_to_process.files}}" +# Copy github workflows +- find: + paths: "{{public_templates_folder}}/pipelines/.github/workflows" + recurse: yes + file_type: file + register: github_workflows_to_process + +- name: copy github workflows + ansible.builtin.template: + src: "{{ item.path }}" + dest: "{{base_folder}}/{{ item.path | regex_replace(public_templates_folder + '/pipelines', '') }}" + loop: "{{github_workflows_to_process.files}}" + +# Copy default firewall rules + +- name: "Creates destination directory - {{firewall_rules_path}}" + file: + path: "{{firewall_rules_path}}/{{item}}" + state: directory + with_items: + - application_rule_collections + - network_rule_collections + - nat_rule_collections + +- find: + paths: "{{public_templates_folder}}/firewall_rules" + recurse: yes + file_type: file + register: firewall_files_to_process + +- name: copy firewall files + ansible.builtin.copy: + src: "{{ item.path }}" + dest: "{{firewall_rules_path}}/{{ item.path | regex_replace(public_templates_folder + '/firewall_rules', '') }}" + loop: "{{firewall_files_to_process.files}}" + +# Copy platform templates +- find: + paths: "{{platform_service_folder}}" + recurse: no + patterns: "*.yaml" + file_type: file + register: platform_service_template_files_to_process + +- name: "Creates destination directory - {{topology.private_templates_folder}}" + file: + path: "{{topology.private_templates_folder}}" + state: directory + +- name: copy public platform templates + ansible.builtin.copy: + src: "{{ item.path }}" + dest: "{{topology.private_templates_folder}}/{{ item.path | regex_replace(platform_service_folder, '') }}" + loop: "{{platform_service_template_files_to_process.files}}" + + - name: tfstates.yaml ansible.builtin.template: src: "{{platform_service_folder}}/tfstates.yaml" @@ -139,18 +218,11 @@ - name: ignite.yaml ansible.builtin.template: - src: "{{public_templates_folder}}/platform/single_subscription.yaml" + src: "{{public_templates_folder}}/platform/caf_platform_prod_nonprod.yaml" dest: "{{destination_path}}/ignite.yaml" -- name: readme.md +- name: "{{platform_service_folder}}/README.md" ansible.builtin.template: src: "{{platform_service_folder}}/README.md" dest: "{{destination_path}}/GETTING-STARTED.md" - - -- debug: - msg: - - "You have now initialized the definition of the platform" - - "You can review and adjust the yaml files." - - "configuration folder: - {{destination_path}}" - - "readme: {{destination_path}}/GETTING-STARTED.md" \ No newline at end of file + \ No newline at end of file diff --git a/templates/asvm/orion/deploy_template.sh b/templates/asvm/orion/deploy_template.sh index ba8ae633c..aeae4078a 100755 --- a/templates/asvm/orion/deploy_template.sh +++ b/templates/asvm/orion/deploy_template.sh @@ -9,7 +9,7 @@ ansible-playbook /tf/caf/landingzones/templates/asvm/orion/walk-through.yaml \ -e topology_folder=/tf/caf/landingzones/templates/asvm/orion \ -e public_templates_folder=/tf/caf/landingzones/templates \ -e landingzones_folder=/tf/caf/landingzones \ - -e template_folder=/tf/caf/asvm/${landingzone_definition} \ + -e template_folder=/tf/caf/asvm/${landingzone_definition}/template \ -e definition_folder=/tf/caf/asvm/${landingzone_definition}/definition \ -e platform_configuration_folder=/tf/caf/configuration \ -e platform_definition_folder=/tf/caf/platform/definition \ diff --git a/templates/asvm/orion/ignite.yaml b/templates/asvm/orion/ignite.yaml index 05c3a2745..83d29bd03 100644 --- a/templates/asvm/orion/ignite.yaml +++ b/templates/asvm/orion/ignite.yaml @@ -1,6 +1,8 @@ landingzone_definition: {{landingzone_definition}} +caf_landingzone_branch: int.5.6.0 + subscriptions: {% for env in scale_out_domains %} {{landingzone_definition}}_{{env}}: @@ -15,7 +17,15 @@ subscriptions: # deployments: + platform: + scale_out_domain_keys: + - prod + - non_prod asvm: + mappings: +{%for key, value in platform_domain_mapping.items() %} + {{key}}: {{value}} +{% endfor %} root: region1: asvm_subscriptions: subscriptions.asvm.yaml @@ -23,11 +33,6 @@ deployments: {{landingzone_definition}}_{{env}}: subscriptions.asvm.yaml {% endfor %} -platform_mappings: -{%for key, value in platform_domain_mapping.items() %} - {{key}}: {{value}} -{% endfor %} - # # If platform folder and config not accessible to the asvm repo you need to add the following variables # diff --git a/templates/asvm/orion/readme.md b/templates/asvm/orion/readme.md index af29014eb..28afdfae1 100644 --- a/templates/asvm/orion/readme.md +++ b/templates/asvm/orion/readme.md @@ -2,16 +2,21 @@ ## Generate the definition files +Note: This script will generate the definition files from your templates + ```bash +# Execute this script from the /tf/caf folder. ansible-playbook {{public_templates_folder}}/ansible/asvm_definition.yaml \ - --extra-vars "@{{template_folder}}/ignite.yaml" + --extra-vars "@{{template_folder}}/ignite.yaml" \ + -e base_folder=$(pwd) ``` ### Regenerate the template -Note: This playbook will override the customization you have performed in your {{platform_configuration_folder}} folder. +Note: This playbook will override the customization you have performed in your {{platform_configuration_folder}} folder and redeploy the original version from the public library. + ```bash ansible-playbook {{public_templates_folder}}/asvm/orion/walk-through.yaml \ diff --git a/templates/asvm/orion/readme_definition.md b/templates/asvm/orion/readme_definition.md index e7946f6eb..5e573a766 100644 --- a/templates/asvm/orion/readme_definition.md +++ b/templates/asvm/orion/readme_definition.md @@ -4,7 +4,9 @@ ```bash ansible-playbook {{public_templates_folder}}/ansible/ansible.yaml \ - --extra-vars "@{{template_folder}}/ignite.yaml" + --extra-vars "@{{template_folder}}/ignite.yaml" \ + -e base_folder=$(pwd) + ``` @@ -12,6 +14,7 @@ ansible-playbook {{public_templates_folder}}/ansible/ansible.yaml \ ```bash ansible-playbook {{public_templates_folder}}/ansible/asvm_definition.yaml \ - --extra-vars "@{{template_folder}}/ignite.yaml" + --extra-vars "@{{template_folder}}/ignite.yaml" \ + -e base_folder=$(pwd) ``` \ No newline at end of file diff --git a/templates/asvm/orion/resources.asvm.yaml b/templates/asvm/orion/resources.asvm.yaml index b8a2e9189..7c27f273e 100644 --- a/templates/asvm/orion/resources.asvm.yaml +++ b/templates/asvm/orion/resources.asvm.yaml @@ -1,6 +1,6 @@ {{landingzone_definition}}_{{env}}: gitops: - caf_landingzone_branch: 2203.0 + caf_landingzone_branch: {{caf_landingzone_branch}} relative_destination_folder: level3/{{landingzone_definition}}/{{env}} @@ -8,15 +8,16 @@ landingzone: global_settings_key: platform: - virtual_hubs: {{platform_mappings[env]}} + virtual_hubs: {{deployments.asvm.mappings[env]}} remote_tfstates: asvm: asvm_subscriptions: platform: - virtual_hubs: {{platform_mappings[env]}} - virtual_hubs_route_tables: {{platform_mappings[env]}} - private_dns_firewalls: {{platform_mappings[env]}} - identity_level2: {{platform_mappings[env]}} + virtual_hubs: {{deployments.asvm.mappings[env]}} + virtual_hubs_route_tables: {{deployments.asvm.mappings[env]}} + private_dns_firewalls: {{deployments.asvm.mappings[env]}} + private_dns: {{deployments.asvm.mappings[env]}} + identity_level2: {{deployments.asvm.mappings[env]}} asvm: resources: @@ -43,10 +44,10 @@ resource_group_key: networking region_key: region1 dns_servers_keys: - fw_secure_{{platform_mappings[env]}}: + fw_secure_{{deployments.asvm.mappings[env]}}: resource_type: azurerm_firewall - lz_key: connectivity_private_dns_firewalls_{{platform_mappings[env]}} - key: fw_prod_dns_{{platform_mappings[env]}} + lz_key: connectivity_private_dns_firewalls_{{deployments.asvm.mappings[env]}} + key: fw_prod_dns_{{deployments.asvm.mappings[env]}} address_space: - 10.101.8.0/23 subnets: @@ -368,16 +369,21 @@ destination_port_range: "*" virtual_hub_connections: - vnet_to_{{platform_mappings[env]}}: - name: vnet-{{landingzone_definition}}-{{env}}-TO-{{platform_mappings[env]}} + vnet_to_{{deployments.asvm.mappings[env]}}: + name: vnet-{{landingzone_definition}}-{{env}}-TO-{{deployments.asvm.mappings[env]}} virtual_hub: - lz_key: connectivity_virtual_hubs_{{platform_mappings[env]}} - key: {{platform_mappings[env]}} + lz_key: connectivity_virtual_hubs_{{deployments.asvm.mappings[env]}} + key: {{deployments.asvm.mappings[env]}} vnet: vnet_key: vnet routing: - egress: - lz_key: virtual_hubs_route_tables_{{platform_mappings[env]}} + firewall_manager: + virtual_hub_route_table_key: "defaultRouteTable" + propagated_route_table: + labels: + - none + virtual_hub_route_table_keys: + - noneRouteTable recovery_vaults: asr: @@ -393,11 +399,58 @@ time: "23:00" retention_daily: count: 7 +{% if private_endpoints is defined %} + private_endpoints: + asr: + name: vault-{{landingzone_definition}}-{{env}} + resource_group_key: backup + vnet_key: vnet + subnet_key: private_endpoints + private_service_connection: + name: vault-{{landingzone_definition}}-{{env}} + is_manual_connection: false + subresource_names: + - vault +{% if private_endpoints is defined and private_dns is defined %} + private_dns: + zone_group_name: default + lz_key: connectivity_private_dns_{{deployments.asvm.mappings[env]}} + keys: + - privatelink.siterecovery.windowsazure.com +{% endif %} +{% endif %} keyvaults: kv_delegated_sp: name: {{landingzone_definition}}{{env}}001 resource_group_key: rg + network: + bypass: AzureServices + default_action: Deny +{% if PUBLIC_IP_WHITE_LIST is defined %} + ip_rules: + {{ PUBLIC_IP_WHITE_LIST | default('None') }} +{% endif %} +{% if private_endpoints is defined %} + private_endpoints: + level2: + name: {{landingzone_definition}}{{env}}001 + resource_group_key: rg + vnet_key: vnet_region1 + subnet_key: private_endpoints + private_service_connection: + name: {{landingzone_definition}}{{env}}001 + is_manual_connection: false + subresource_names: + - vault +{% if private_endpoints is defined and private_dns is defined %} + private_dns: + zone_group_name: default + lz_key: connectivity_private_dns_{{deployments.asvm.mappings[env]}} + keys: + - privatelink.vaultcore.azure.net +{% endif %} +{% endif %} creation_policies: logged_in_user: secret_permissions: @@ -407,9 +460,9 @@ - Delete - Purge - Recover - landingzone_maintainers_{{platform_mappings[env]}}: + landingzone_maintainers_{{deployments.asvm.mappings[env]}}: lz_key: asvm - azuread_group_key: caf_ac_landingzone_maintainers_{{platform_mappings[env]}} + azuread_group_key: caf_ac_landingzone_maintainers_{{deployments.asvm.mappings[env]}} secret_permissions: - Set - Get @@ -457,13 +510,13 @@ days: 58 azuread_groups_membership: - caf_{{platform_mappings[env]}}_landingzones_dns_contributors: + caf_{{deployments.asvm.mappings[env]}}_landingzones_dns_contributors: azuread_service_principals: sp_LZContributors: - group_lz_key: identity_level2_{{platform_mappings[env]}} + group_lz_key: identity_level2_{{deployments.asvm.mappings[env]}} keys: - sp_LZContributors - caf_ac_landingzone_maintainers_{{platform_mappings[env]}}: + caf_ac_landingzone_maintainers_{{deployments.asvm.mappings[env]}}: azuread_service_principals: sp_LZContributors: group_lz_key: asvm diff --git a/templates/asvm/orion/subscriptions.asvm.yaml b/templates/asvm/orion/subscriptions.asvm.yaml index c0e2cb134..c3be3e51d 100644 --- a/templates/asvm/orion/subscriptions.asvm.yaml +++ b/templates/asvm/orion/subscriptions.asvm.yaml @@ -1,6 +1,6 @@ asvm_subscriptions: gitops: - caf_landingzone_branch: 2203.0 + caf_landingzone_branch: {{caf_landingzone_branch}} relative_destination_folder: level3/{{landingzone_definition}}/subscriptions diff --git a/templates/firewall_rules/application_rule_collections/azure_firewall_management.yaml b/templates/firewall_rules/application_rule_collections/azure_firewall_management.yaml new file mode 100644 index 000000000..01e90e676 --- /dev/null +++ b/templates/firewall_rules/application_rule_collections/azure_firewall_management.yaml @@ -0,0 +1,42 @@ +azure_firewall_management: + name: Azure Firewall management + action: Allow + rules: +{% if private_dns_firewalls is defined %} + # https://docs.microsoft.com/en-us/azure/api-management/virtual-network-reference?tabs=stv2#metrics-and-health-monitoring-1 + metrics_and_health: + name: metric and health + source_addresses: + {{ private_dns_firewalls_vnets[env | default('prod')].vnet.address_space | to_nice_yaml }} + destination_fqdns: + - gcs.prod.monitoring.core.windows.net + - global.prod.microsoftmetrics.com + - shoebox2.prod.microsoftmetrics.com + - shoebox2-red.prod.microsoftmetrics.com + - shoebox2-black.prod.microsoftmetrics.com + - prod3.prod.microsoftmetrics.com + - prod3-black.prod.microsoftmetrics.com + - prod3-red.prod.microsoftmetrics.com + - gcs.prod.warm.ingestion.monitoring.azure.com + - nfvprodmainsy.blob.core.windows.net + - cnfwsecurityaue.blob.core.windows.net + - cnfwaue.blob.core.windows.net + protocols: + https: + port: 443 + type: Https + threat_protection: + name: threat protection updates + source_addresses: + {{ private_dns_firewalls_vnets[env | default('prod')].vnet.address_space | to_nice_yaml }} + destination_fqdns: + - webres1.msazure.ctmail.com + - webres2.msazure.ctmail.com + - webres3.msazure.ctmail.com + - webres4.msazure.ctmail.com + - webres5.msazure.ctmail.com + protocols: + https: + port: 80 + type: Http +{% endif %} \ No newline at end of file diff --git a/templates/firewall_rules/application_rule_collections/azure_management.yaml b/templates/firewall_rules/application_rule_collections/azure_management.yaml new file mode 100644 index 000000000..314429e7e --- /dev/null +++ b/templates/firewall_rules/application_rule_collections/azure_management.yaml @@ -0,0 +1,39 @@ +azure_management: + name: Azure management services + action: Allow + rules: + # '443': + # name: "443" + # source_addresses: + # - "*" + # destination_fqdns: + # - "*" + # protocols: + # https: + # port: 443 + # type: Https + monitoring: + name: azure-monitor + source_addresses: + - "*" + destination_fqdns: + - dc.services.visualstudio.com + - "*.ods.opinsights.azure.com" + - "*.oms.opinsights.azure.com" + - "*.monitoring.azure.com" + protocols: + https: + port: 443 + type: Https + policy: + name: azure-policy + source_addresses: + - "*" + destination_fqdns: + - data.policy.core.windows.net + - store.policy.core.windows.net + - dc.services.visualstudio.com + protocols: + https: + port: 443 + type: Https \ No newline at end of file diff --git a/templates/firewall_rules/application_rule_collections/gitops.yaml b/templates/firewall_rules/application_rule_collections/gitops.yaml new file mode 100644 index 000000000..daeac7071 --- /dev/null +++ b/templates/firewall_rules/application_rule_collections/gitops.yaml @@ -0,0 +1,42 @@ +gitops: + name: gitops + action: Allow + rules: +{% if vnet_launchpad is defined and vnet_asvm is defined %} + azure_cli: + name: azure-cli + source_addresses: + {{ vnet_launchpad.vnet_region1.address_space | to_nice_yaml }} + destination_fqdns: + - aka.ms + - app.aladdin.microsoft.com + - azcliextensionsync.blob.core.windows.net + - azcliprod.blob.core.windows.net + - azure.microsoft.com + - azurecliextensionsync.blob.core.windows.net + - graph.microsoft.com + - login.microsoftonline.com + - management.azure.com + protocols: + https: + port: 443 + type: Https + github: + name: github.com + description: Services for self-hosted runners + source_addresses: + {{ vnet_launchpad.vnet_region1.address_space | to_nice_yaml }} + protocols: + https: + port: 443 + type: Https + destination_fqdns: + - api.github.com + - codeload.github.com + - docs.github.com + - github.com + - objects.githubusercontent.com + - pipelines.actions.githubusercontent.com + - raw.githubusercontent.com + - vstoken.actions.githubusercontent.com +{% endif %} \ No newline at end of file diff --git a/templates/firewall_rules/application_rule_collections/hashicorp.yaml b/templates/firewall_rules/application_rule_collections/hashicorp.yaml new file mode 100644 index 000000000..b96455334 --- /dev/null +++ b/templates/firewall_rules/application_rule_collections/hashicorp.yaml @@ -0,0 +1,28 @@ +hashicorp: + name: Hashicorp + action: Allow + rules: +{% if vnet_launchpad is defined and vnet_asvm is defined %} + terraform: + name: terraform + source_addresses: + {{ vnet_launchpad.vnet_region1.address_space | to_nice_yaml }} + destination_fqdns: + - checkpoint-api.hashicorp.com + - registry.terraform.io + - releases.hashicorp.com + protocols: + https: + port: 443 + type: Https + azuread: + name: azuread provider + source_addresses: + {{ vnet_launchpad.vnet_region1.address_space | to_nice_yaml }} + destination_fqdns: + - graph.windows.net + protocols: + https: + port: 443 + type: Https +{% endif %} \ No newline at end of file diff --git a/templates/firewall_rules/application_rule_collections/packages_linux.yaml b/templates/firewall_rules/application_rule_collections/packages_linux.yaml new file mode 100644 index 000000000..2139ebaf0 --- /dev/null +++ b/templates/firewall_rules/application_rule_collections/packages_linux.yaml @@ -0,0 +1,70 @@ +packages_linux: + name: security packages + action: Allow + rules: + ubuntu: + name: ubuntu + description: Upstream addresses to get Ubuntu package updates + source_addresses: ["*"] + protocols: + https: + port: 443 + type: Https + http: + port: 80 + type: Http + destination_fqdns: + - archive.ubuntu.com + - azure.archive.ubuntu.com + - changelogs.ubuntu.com + - security.ubuntu.com + github: + name: github + description: github package updates + source_addresses: ["*"] + protocols: + https: + port: 443 + type: Https + destination_fqdns: + - cli.github.com + docker: + name: docker + description: docker package updates + source_addresses: ["*"] + protocols: + https: + port: 443 + type: Https + destination_fqdns: + - download.docker.com + microsoft: + name: microsoft + description: Microsoft package updates + source_addresses: ["*"] + protocols: + https: + port: 443 + type: Https + destination_fqdns: + - packages.microsoft.com + kubernetes: + name: kubernetes + description: kubernetes package updates + source_addresses: ["*"] + protocols: + https: + port: 443 + type: Https + destination_fqdns: + - apt.kubernetes.io + google: + name: google + description: Google package updates + source_addresses: ["*"] + protocols: + https: + port: 443 + type: Https + destination_fqdns: + - packages.cloud.google.com diff --git a/templates/firewall_rules/network_rule_collections/caf_terraform_lz.yaml b/templates/firewall_rules/network_rule_collections/caf_terraform_lz.yaml new file mode 100644 index 000000000..fc70bf739 --- /dev/null +++ b/templates/firewall_rules/network_rule_collections/caf_terraform_lz.yaml @@ -0,0 +1,16 @@ +caf_terraform_lz: + name: CAF Terraform landing zones + action: Allow + rules: +{% if vnet_launchpad is defined and vnet_asvm is defined %} + gitops_bootstrap_to_asvm: + name: gitops bootstrap to asvm + source_addresses: + {{ vnet_launchpad.vnet_region1.address_space | to_nice_yaml }} + destination_ports: + - 443 + destination_addresses: + {{ vnet_asvm.vnet_region1.address_space | to_nice_yaml }} + protocols: + - TCP +{% endif %} \ No newline at end of file diff --git a/templates/firewall_rules/network_rule_collections/services.yaml b/templates/firewall_rules/network_rule_collections/services.yaml new file mode 100644 index 000000000..da9e2fe14 --- /dev/null +++ b/templates/firewall_rules/network_rule_collections/services.yaml @@ -0,0 +1,39 @@ +services: + name: services + action: Allow + rules: + ntp: + name: ntp + source_addresses: ["*"] + destination_ports: + - 123 + destination_addresses: ["*"] + protocols: + - UDP + dns: + name: dns + source_addresses: ["*"] + destination_ports: + - 53 + destination_addresses: +{% if secure_firewalls_resources is defined %} + - {{ secure_firewalls_resources[env | default('prod')].fw_secure_prod.virtual_hub[0].private_ip_address }} +{% else %} + - "168.63.129.16" +{% endif %} + protocols: + - UDP + - TCP +{% if secure_firewalls_resources is defined %} + service_bus: + name: service bus + source_addresses: + {{ private_dns_firewalls_vnets[env | default('prod')].vnet.address_space | to_nice_yaml }} + destination_ports: + - 5671 + destination_addresses: + # Australia + - 13.70.72.7 + protocols: + - TCP +{% endif %} \ No newline at end of file diff --git a/templates/pipelines/.github/workflows/bootstrap.yaml b/templates/pipelines/.github/workflows/bootstrap.yaml new file mode 100644 index 000000000..f4cd1119d --- /dev/null +++ b/templates/pipelines/.github/workflows/bootstrap.yaml @@ -0,0 +1,114 @@ +# +# Copyright (c) Microsoft Corporation +# Licensed under the MIT License. +# + +name: bootstrap + +on: + push: + branches: + - bootstrap + +permissions: + id-token: write + contents: read +{% raw %} +concurrency: + group: ${{ github.ref }} +{% endraw %} +env: + LZ_REPO: '{{resources.terraform_code_repository}}' + LZ_BRANCH: '{{resources.caf_landingzone_branch}}' + CAF_ENVIRONMENT: {{resources.caf_environment}} + +jobs: +{% raw %} + environment: + name: Setup dynamic environment variables + runs-on: ubuntu-latest + outputs: + + terraform_code_repository: ${{ steps.set_env.outputs.terraform_code_repository }} + terraform_code_ref: ${{ steps.set_env.outputs.terraform_code_ref }} + environment: ${{ steps.set_env.outputs.environment }} + steps: + - name: Set environment variables for re-usable workflows + id: set_env + run: | + echo "::set-output name=terraform_code_repository::$LZ_REPO" + echo "::set-output name=terraform_code_ref::$LZ_BRANCH" + echo "::set-output name=environment::$CAF_ENVIRONMENT" + + + launchpad: + uses: ./.github/workflows/rover.yaml + needs: [environment] + concurrency: + group: launchpad + secrets: + + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + caf_identity_aad_key: cred_level0 + landingzone_code_path: ./landingzones/caf_launchpad + landingzone_configuration_path: ./platform/configuration/level0/launchpad + plan_path: ${GITHUB_WORKSPACE}/caf_launchpad.tfstate.tfplan + launchpad: true + tfstate: caf_launchpad.tfstate + level: level0 + terraform_action: plan_apply + + credentials: + uses: ./.github/workflows/rover.yaml + needs: [environment, launchpad] + concurrency: + group: credentials + secrets: + + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + landingzone_code_path: ./landingzones/caf_solution + landingzone_configuration_path: ./platform/configuration/level0/credentials + plan_path: ${GITHUB_WORKSPACE}/launchpad_credentials.tfstate.tfplan + tfstate: launchpad_credentials.tfstate + level: level0 + terraform_action: plan_apply + + generate_definition_files: + uses: ./.github/workflows/generate-definition-files.yaml + needs: [environment, credentials] + concurrency: + group: credentials + secrets: + + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_MANAGEMENT_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_CONNECTIVITY_SUBSCRIPTION_ID: ${{ secrets.AZURE_CONNECTIVITY_SUBSCRIPTION_ID }} + AZURE_IDENTITY_SUBSCRIPTION_ID: ${{ secrets.AZURE_IDENTITY_SUBSCRIPTION_ID }} + AZURE_SECURITY_SUBSCRIPTION_ID: ${{ secrets.AZURE_SECURITY_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_TARGET_SUBSCRIPTION_ID }} + RUNNER_REGISTRATION_TOKEN: ${{ secrets.RUNNER_REGISTRATION_TOKEN }} + RUNNER_NUMBERS: ${{ secrets.RUNNER_NUMBERS }} + AZURE_OBJECT_ID: ${{ secrets.AZURE_OBJECT_ID }} + ROVER_AGENT_DOCKER_IMAGE: ${{ secrets.ROVER_AGENT_DOCKER_IMAGE }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + +{% endraw %} \ No newline at end of file diff --git a/templates/pipelines/.github/workflows/end-to-end-deploy.yaml b/templates/pipelines/.github/workflows/end-to-end-deploy.yaml new file mode 100644 index 000000000..2c78d56cd --- /dev/null +++ b/templates/pipelines/.github/workflows/end-to-end-deploy.yaml @@ -0,0 +1,445 @@ +# +# Copyright (c) Microsoft Corporation +# Licensed under the MIT License. +# + +name: end-to-end + +on: + pull_request: + branches: + - end2end + +permissions: + id-token: write + contents: read + +{% raw %} +concurrency: + group: ${{ github.ref }} +{% endraw %} + +env: + LZ_REPO: '{{resources.terraform_code_repository}}' + LZ_BRANCH: '{{resources.caf_landingzone_branch}}' + CAF_ENVIRONMENT: {{resources.caf_environment}} + +jobs: +{% raw %} + environment: + name: Setup dynamic environment variables + runs-on: [self-hosted, platform] + outputs: + terraform_code_repository: ${{ steps.set_env.outputs.terraform_code_repository }} + terraform_code_ref: ${{ steps.set_env.outputs.terraform_code_ref }} + environment: ${{ steps.set_env.outputs.environment }} + steps: + - name: Set environment variables for re-usable workflows + id: set_env + run: | + echo "::set-output name=terraform_code_repository::$LZ_REPO" + echo "::set-output name=terraform_code_ref::$LZ_BRANCH" + echo "::set-output name=environment::$CAF_ENVIRONMENT" + + configuration_updates: + name: Generate configuration tfvars files + runs-on: [self-hosted, platform] + + permissions: + id-token: write + contents: write + + needs: [environment] + uses: ./.github/workflows/generate-configuration-files.yaml + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + RUNNER_REGISTRATION_TOKEN: ${{ secrets.RUNNER_REGISTRATION_TOKEN }} + RUNNER_NUMBERS: ${{ secrets.RUNNER_NUMBERS }} + AZURE_OBJECT_ID: ${{ secrets.AZURE_OBJECT_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + + + launchpad: + uses: ./.github/workflows/rover.yaml + needs: [environment, configuration_updates] + concurrency: + group: level0-launchpad + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + caf_identity_aad_key: cred_level0 + landingzone_code_path: ./landingzones/caf_launchpad + landingzone_configuration_path: ./platform/configuration/level0/launchpad + plan_path: ${GITHUB_WORKSPACE}/caf_launchpad.tfstate.tfplan + tfstate: caf_launchpad.tfstate + level: level0 + terraform_action: plan_apply + + credentials: + uses: ./.github/workflows/rover.yaml + needs: [environment, launchpad] + concurrency: + group: level0-credentials + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + caf_identity_aad_key: cred_identity + landingzone_code_path: ./landingzones/caf_solution + landingzone_configuration_path: ./platform/configuration/level0/credentials + plan_path: ${GITHUB_WORKSPACE}/launchpad_credentials.tfstate.tfplan + tfstate: launchpad_credentials.tfstate + level: level0 + terraform_action: plan_apply + + platform_subscriptions: + uses: ./.github/workflows/rover.yaml + needs: [environment, launchpad] + concurrency: + group: level1-platform_subscriptions + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + caf_identity_aad_key: cred_subscription_creation_platform + landingzone_code_path: ./landingzones/caf_solution + landingzone_configuration_path: ./platform/configuration/level1/subscriptions + plan_path: ${GITHUB_WORKSPACE}/platform_subscriptions.tfstate.tfplan + tfstate: platform_subscriptions.tfstate + level: level1 + terraform_action: plan_apply + + + management: + uses: ./.github/workflows/rover.yaml + needs: [environment, launchpad] + concurrency: + group: level1-management + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + caf_identity_aad_key: cred_management + landingzone_code_path: ./landingzones/caf_solution + landingzone_configuration_path: ./platform/configuration/level1/management + plan_path: ${GITHUB_WORKSPACE}/management.tfstate.tfplan + tfstate: management.tfstate + level: level1 + terraform_action: plan_apply + + security: + uses: ./.github/workflows/rover.yaml + needs: [environment, launchpad] + concurrency: + group: level1-security + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_SECURITY_SUBSCRIPTION_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + caf_identity_aad_key: cred_management + landingzone_code_path: ./landingzones/caf_solution + landingzone_configuration_path: ./platform/configuration/level1/security + plan_path: ${GITHUB_WORKSPACE}/security.tfstate.tfplan + tfstate: security.tfstate + level: level1 + terraform_action: plan_apply + + identity_level1: + uses: ./.github/workflows/rover.yaml + needs: [environment, launchpad] + concurrency: + group: level1-identity + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_IDENTITY_SUBSCRIPTION_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + caf_identity_aad_key: cred_identity + landingzone_code_path: ./landingzones/caf_solution + landingzone_configuration_path: ./platform/configuration/level1/identity + plan_path: ${GITHUB_WORKSPACE}/identity.tfstate.tfplan + tfstate: identity.tfstate + level: level1 + terraform_action: plan_apply + + alz: + uses: ./.github/workflows/rover.yaml + needs: [environment, launchpad, identity_level1, management, platform_subscriptions] + concurrency: + group: level1-alz + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + with: + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + terraform_code_ref: 'int-5.7.0' + caf_identity_aad_key: cred_alz + landingzone_code_path: ./landingzones/caf_solution/add-ons/caf_eslz + landingzone_configuration_path: ./platform/configuration/level1/alz/{{alz_mg_prefix}} + plan_path: ${GITHUB_WORKSPACE}/alz_{{alz_mg_prefix}}.tfstate.tfplan + tfstate: alz_{{alz_mg_prefix}}.tfstate + level: level1 + terraform_action_apply: -parallelism=50 + terraform_action: plan_apply + + identity_level2_prod: + uses: ./.github/workflows/rover.yaml + needs: [environment, identity_level1] + concurrency: + group: level2-identity_level2_prod + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_IDENTITY_SUBSCRIPTION_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + caf_identity_aad_key: cred_identity + landingzone_code_path: ./landingzones/caf_solution + landingzone_configuration_path: ./platform/configuration/level2/identity/prod + plan_path: ${GITHUB_WORKSPACE}/identity_level2_prod.tfstate.tfplan + tfstate: identity_level2_prod.tfstate + level: level2 + terraform_action: plan_apply + + identity_level2_non_prod: + uses: ./.github/workflows/rover.yaml + needs: [environment, identity_level1] + concurrency: + group: level2-identity_level2_non_prod + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_IDENTITY_SUBSCRIPTION_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + caf_identity_aad_key: cred_identity + landingzone_code_path: ./landingzones/caf_solution + landingzone_configuration_path: ./platform/configuration/level2/identity/non_prod + plan_path: ${GITHUB_WORKSPACE}/identity_level2_non_prod.tfstate.tfplan + tfstate: identity_level2_non_prod.tfstate + level: level2 + terraform_action: plan_apply + + asvm: + uses: ./.github/workflows/rover.yaml + needs: [environment, management, connectivity_virtual_hubs_prod] + concurrency: + group: level2-asvm_subscription_vending_machine + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + caf_identity_aad_key: cred_level0 + landingzone_code_path: ./landingzones/caf_solution + landingzone_configuration_path: ./platform/configuration/level2/asvm + plan_path: ${GITHUB_WORKSPACE}/asvm_subscription_vending_machine.tfstate.tfplan + tfstate: asvm_subscription_vending_machine.tfstate + level: level2 + terraform_action: plan_apply + + connectivity_virtual_wans_prod: + uses: ./.github/workflows/rover.yaml + needs: [environment, management] + concurrency: + group: level2-connectivity_virtual_wans_prod + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_CONNECTIVITY_SUBSCRIPTION_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + caf_identity_aad_key: cred_connectivity + landingzone_code_path: ./landingzones/caf_solution + landingzone_configuration_path: ./platform/configuration/level2/connectivity/virtual_wans + plan_path: ${GITHUB_WORKSPACE}/connectivity_virtual_wans_prod.tfstate.tfplan + tfstate: connectivity_virtual_wans_prod.tfstate + level: level2 + terraform_action: plan_apply + + connectivity_virtual_hubs_prod: + uses: ./.github/workflows/rover.yaml + needs: [environment, connectivity_virtual_wans_prod] + concurrency: + group: level2-connectivity_virtual_hubs_prod + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_CONNECTIVITY_SUBSCRIPTION_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + caf_identity_aad_key: cred_connectivity + landingzone_code_path: ./landingzones/caf_solution + landingzone_configuration_path: ./platform/configuration/level2/connectivity/virtual_hubs/prod + plan_path: ${GITHUB_WORKSPACE}/connectivity_virtual_hubs_prod.tfstate.tfplan + tfstate: connectivity_virtual_hubs_prod.tfstate + level: level2 + terraform_action: plan_apply + + connectivity_firewall_policies_prod: + uses: ./.github/workflows/rover.yaml + needs: [environment, connectivity_virtual_wans_prod] + concurrency: + group: level2-connectivity_firewall_policies_prod + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_CONNECTIVITY_SUBSCRIPTION_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + caf_identity_aad_key: cred_connectivity + landingzone_code_path: ./landingzones/caf_solution + landingzone_configuration_path: ./platform/configuration/level2/connectivity/azurerm_firewall_policies/prod + plan_path: ${GITHUB_WORKSPACE}/connectivity_firewall_policies_prod.tfstate.tfplan + tfstate: connectivity_firewall_policies_prod.tfstate + level: level2 + terraform_action: plan_apply + + connectivity_private_dns_firewalls_prod: + uses: ./.github/workflows/rover.yaml + needs: [environment, connectivity_virtual_hubs_prod, connectivity_firewall_policies_prod] + concurrency: + group: level2-connectivity_private_dns_firewalls_prod + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_CONNECTIVITY_SUBSCRIPTION_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + caf_identity_aad_key: cred_connectivity + landingzone_code_path: ./landingzones/caf_solution + landingzone_configuration_path: ./platform/configuration/level2/connectivity/private_dns_firewalls/prod + plan_path: ${GITHUB_WORKSPACE}/connectivity_private_dns_firewalls_prod.tfstate.tfplan + tfstate: connectivity_private_dns_firewalls_prod.tfstate + level: level2 + terraform_action: plan_apply + + connectivity_secure_firewalls_prod: + uses: ./.github/workflows/rover.yaml + needs: [environment, connectivity_virtual_hubs_prod, connectivity_firewall_policies_prod] + concurrency: + group: level2-connectivity_secure_firewalls_prod + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_CONNECTIVITY_SUBSCRIPTION_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + caf_identity_aad_key: cred_connectivity + landingzone_code_path: ./landingzones/caf_solution + landingzone_configuration_path: ./platform/configuration/level2/connectivity/secure_firewalls/prod + plan_path: ${GITHUB_WORKSPACE}/connectivity_secure_firewalls_prod.tfstate.tfplan + tfstate: connectivity_secure_firewalls_prod.tfstate + level: level2 + terraform_action: plan_apply + + virtual_hubs_route_tables_prod: + uses: ./.github/workflows/rover.yaml + needs: [environment, connectivity_virtual_hubs_prod, connectivity_secure_firewalls_prod, connectivity_private_dns_firewalls_prod] + concurrency: + group: level2-connectivity_virtual_hubs_route_tables_prod + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_CONNECTIVITY_SUBSCRIPTION_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + caf_identity_aad_key: cred_connectivity + landingzone_code_path: ./landingzones/caf_solution + landingzone_configuration_path: ./platform/configuration/level2/connectivity/virtual_hubs_route_tables/prod + plan_path: ${GITHUB_WORKSPACE}/connectivity_virtual_hubs_route_tables_prod.tfstate.tfplan + tfstate: connectivity_virtual_hubs_route_tables_prod.tfstate + level: level2 + terraform_action: plan_apply + + connectivity_private_dns_prod: + uses: ./.github/workflows/rover.yaml + needs: [environment, connectivity_private_dns_firewalls_prod, identity_level2_prod] + concurrency: + group: level2-connectivity_private_dns_prod + secrets: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_CONNECTIVITY_SUBSCRIPTION_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + caf_identity_aad_key: cred_connectivity + landingzone_code_path: ./landingzones/caf_solution + landingzone_configuration_path: ./platform/configuration/level2/connectivity/private_dns/prod + plan_path: ${GITHUB_WORKSPACE}/connectivity_private_dns_prod.tfstate.tfplan + tfstate: connectivity_private_dns_prod.tfstate + terraform_action_apply: -parallelism=50 + level: level2 + terraform_action: plan_apply + +{% endraw %} \ No newline at end of file diff --git a/templates/pipelines/.github/workflows/generate-configuration-files.yaml b/templates/pipelines/.github/workflows/generate-configuration-files.yaml new file mode 100644 index 000000000..45be8b837 --- /dev/null +++ b/templates/pipelines/.github/workflows/generate-configuration-files.yaml @@ -0,0 +1,118 @@ +# +# Copyright (c) Microsoft Corporation +# Licensed under the MIT License. +# + +name: "Generate configuration files" + +on: + workflow_call: + inputs: + terraform_code_repository: + description: "Git repository of the the terraform entry code." + required: true + type: string + terraform_code_ref: + description: "Tag or branch name." + required: true + type: string + environment: + description: Name of the CAF environment + required: true + type: string + secrets: + AZURE_CLIENT_ID: + required: true + AZURE_TENANT_ID: + required: true + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: + required: true + AZURE_TARGET_SUBSCRIPTION_ID: + required: true + RUNNER_REGISTRATION_TOKEN: + required: true + RUNNER_NUMBERS: + required: true + AZURE_OBJECT_ID: + required: true + + +env: + TF_CLI_ARGS: '-no-color' + TF_REGISTRY_DISCOVERY_RETRY: 5 + TF_REGISTRY_CLIENT_TIMEOUT: 15 + ROVER_RUNNER: true + +{% raw %} +jobs: + rover: + name: Generate configuration files + runs-on: [self-hosted, platform] + + steps: + + - name: Checkout Configuration + uses: actions/checkout@v3 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.ref }} + + - name: 'Azure Login' + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + + - name: Create configuration files + run: | + rm -rf ./landingzones + git clone -b ${{ inputs.terraform_code_ref }} --single-branch ${{ inputs.terraform_code_repository }} ./landingzones + + cred_vault_name=$(az keyvault list --query "[?(tags.caf_identity_aad_key=='cred_level0' && tags.caf_environment=='${{ inputs.environment }}')].{name:name}[0]" -o tsv) + if [ "${cred_vault_name}" != '' ]; then + sp_keyvault_url="https://${cred_vault_name}.vault.azure.net/" + + # Test permissions + az keyvault secret show --id ${sp_keyvault_url}/secrets/sp-client-id --query 'value' -o tsv --only-show-errors | read CLIENT_ID + + if [ ! -z "${tenant}" ]; then + export ARM_TENANT_ID=${tenant} + else + export ARM_TENANT_ID=$(az keyvault secret show --id ${sp_keyvault_url}/secrets/sp-tenant-id --query 'value' -o tsv --only-show-errors) + fi + + export ARM_CLIENT_ID=$(az keyvault secret show --id ${sp_keyvault_url}/secrets/sp-client-id --query 'value' -o tsv --only-show-errors) + export ARM_CLIENT_SECRET=$(az keyvault secret show --id ${sp_keyvault_url}/secrets/sp-client-secret --query 'value' -o tsv --only-show-errors) + + az login --service-principal -u ${ARM_CLIENT_ID} -p ${ARM_CLIENT_SECRET} -t ${ARM_TENANT_ID} --only-show-errors 1> /dev/null + fi + + ansible-playbook $(readlink -f ./landingzones/templates/ansible/ansible.yaml) \ + --extra-vars "@$(readlink -f ./platform/definition/ignite.yaml)" \ + -e AGENT_TOKEN=${{ secrets.RUNNER_REGISTRATION_TOKEN }} \ + -e RUNNER_NUMBERS=${{ secrets.RUNNER_NUMBERS }} \ + -e AZURE_OBJECT_ID=${{ secrets.AZURE_OBJECT_ID }} \ + -e GITHUB_SERVER_URL=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} \ + -e base_folder=$(pwd) + + + - name: Update pull Request + run: | + git config --global --add safe.directory $(pwd) + git remote -v + + git config user.name "GitHub Actions Bot" + git config user.email "github-actions@github.com" + + echo ${GITHUB_HEAD_REF} + + if [ -z "$(git status --porcelain)" ]; + then + echo "Nothing to commit" + else + git add --all . + git commit -m "Update configuration files" + git push + fi +{% endraw %} \ No newline at end of file diff --git a/templates/pipelines/.github/workflows/generate-definition-files.yaml b/templates/pipelines/.github/workflows/generate-definition-files.yaml new file mode 100644 index 000000000..a31ad89de --- /dev/null +++ b/templates/pipelines/.github/workflows/generate-definition-files.yaml @@ -0,0 +1,148 @@ +# +# Copyright (c) Microsoft Corporation +# Licensed under the MIT License. +# + +name: "Generate definition files" + +on: + workflow_call: + inputs: + terraform_code_repository: + description: "Git repository of the the terraform entry code." + required: true + type: string + terraform_code_ref: + description: "Tag or branch name." + required: true + type: string + environment: + description: Name of the CAF environment + required: true + type: string + secrets: + AZURE_CLIENT_ID: + required: true + AZURE_TENANT_ID: + required: true + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: + required: true + AZURE_MANAGEMENT_SUBSCRIPTION_ID: + required: false + AZURE_CONNECTIVITY_SUBSCRIPTION_ID: + required: false + AZURE_IDENTITY_SUBSCRIPTION_ID: + required: false + AZURE_SECURITY_SUBSCRIPTION_ID: + required: false + AZURE_TARGET_SUBSCRIPTION_ID: + required: true + RUNNER_REGISTRATION_TOKEN: + required: true + AZURE_OBJECT_ID: + required: true + ROVER_AGENT_DOCKER_IMAGE: + required: true + RUNNER_NUMBERS: + required: true + + +env: + TF_CLI_ARGS: '-no-color' + TF_REGISTRY_DISCOVERY_RETRY: 5 + TF_REGISTRY_CLIENT_TIMEOUT: 15 + ROVER_RUNNER: true + +{% raw %} +jobs: + rover: + name: Generate configuration files + runs-on: [self-hosted, platform] + + steps: + + - name: Checkout Configuration + uses: actions/checkout@v3 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.ref }} + + - name: 'Azure Login' + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + + - name: Create defnition files + run: | + rm -rf ./landingzones + git clone -b ${{ inputs.terraform_code_ref }} --single-branch ${{ inputs.terraform_code_repository }} ./landingzones + + cred_vault_name=$(az keyvault list --query "[?(tags.caf_identity_aad_key=='cred_level0' && tags.caf_environment=='${{ inputs.environment }}')].{name:name}[0]" -o tsv) + if [ "${cred_vault_name}" != '' ]; then + sp_keyvault_url="https://${cred_vault_name}.vault.azure.net/" + + # Test permissions + az keyvault secret show --id ${sp_keyvault_url}/secrets/sp-client-id --query 'value' -o tsv --only-show-errors | read CLIENT_ID + + if [ ! -z "${tenant}" ]; then + export ARM_TENANT_ID=${tenant} + else + export ARM_TENANT_ID=$(az keyvault secret show --id ${sp_keyvault_url}/secrets/sp-tenant-id --query 'value' -o tsv --only-show-errors) + fi + + export ARM_CLIENT_ID=$(az keyvault secret show --id ${sp_keyvault_url}/secrets/sp-client-id --query 'value' -o tsv --only-show-errors) + export ARM_CLIENT_SECRET=$(az keyvault secret show --id ${sp_keyvault_url}/secrets/sp-client-secret --query 'value' -o tsv --only-show-errors) + + az login --service-principal -u ${ARM_CLIENT_ID} -p ${ARM_CLIENT_SECRET} -t ${ARM_TENANT_ID} --only-show-errors 1> /dev/null + fi + + ansible-playbook $(readlink -f ./landingzones/templates/ansible/walk-through-bootstrap.yaml) \ + --extra-vars "@$(readlink -f ./platform/definition/ignite.yaml)" \ + -e base_folder=$(pwd) \ + -e GITOPS_SERVER_URL={{GITHUB_SERVER_URL}}/{{GITHUB_REPOSITORY}} \ + -e RUNNER_NUMBERS={{ secrets.RUNNER_NUMBERS }} \ + -e AGENT_TOKEN={{ secrets.RUNNER_REGISTRATION_TOKEN }} \ + -e ROVER_AGENT_DOCKER_IMAGE=${{secrets.ROVER_AGENT_DOCKER_IMAGE}} \ + -e sub_management=${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID}} \ + -e sub_connectivity=${{ secrets.AZURE_CONNECTIVITY_SUBSCRIPTION_ID }} \ + -e sub_identity=${{ secrets.AZURE_IDENTITY_SUBSCRIPTION_ID }} \ + -e sub_security=${{secrets.AZURE_SECURITY_SUBSCRIPTION_ID}} \ + -e TF_VAR_environment=${{ inputs.environment }} \ + -e bootstrap_sp_object_id=${{secrets.AZURE_OBJECT_ID}} \ + -e template_folder="$(pwd)/platform/definition" + + + - name: Generate PR for + run: | + git config --global --add safe.directory $(pwd) + git config user.name "GitHub Actions Bot" + git config user.email "github-actions@github.com" + + git checkout -b end2end + git add . + pre-commit + git commit -am "Update definition files." + + git remote -v + + echo ${GITHUB_HEAD_REF} + + if [ -z "$(git status --porcelain)" ]; + then + echo "Nothing to commit" + else + git add --all . + git commit -m "Update configuration files" + git push + fi + + /usr/bin/gh pr create \ + --assignee "@me" \ + --title "Complete the deployment of the platform services." \ + --body "${body}" \ + --base bootstrap \ + -R (echo "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" || echo "git config --get remote.origin.url" | sed -e 's#^https://github.com/##; s#^git@github.com:##; s#.git$##') + +{% endraw %} \ No newline at end of file diff --git a/templates/pipelines/.github/workflows/level0-credentials.yaml b/templates/pipelines/.github/workflows/level0-credentials.yaml new file mode 100644 index 000000000..ccf4944aa --- /dev/null +++ b/templates/pipelines/.github/workflows/level0-credentials.yaml @@ -0,0 +1,71 @@ +# +# Copyright (c) Microsoft Corporation +# Licensed under the MIT License. +# + +name: level0-credentials + +on: + schedule: + # 18:00 UTC / 02:00 SGT every days + - cron: '00 18 * * *' + pull_request: + branches-ignore: + - bootstrap + - end2end + paths: + - 'platform/configuration/level0/credentials/**' + +permissions: + id-token: write + contents: read + +concurrency: + group: level0-credentials + +env: + LZ_REPO: '{{resources.terraform_code_repository}}' + LZ_BRANCH: '{{resources.caf_landingzone_branch}}' + CAF_ENVIRONMENT: {{resources.caf_environment}} + +jobs: + environment: + name: Setup dynamic environment variables + runs-on: [self-hosted, platform] + outputs: +{% raw %} + terraform_code_repository: ${{ steps.set_env.outputs.terraform_code_repository }} + terraform_code_ref: ${{ steps.set_env.outputs.terraform_code_ref }} + environment: ${{ steps.set_env.outputs.environment }} +{% endraw %} + steps: + - name: Set environment variables for reusable workflows + id: set_env + run: | + echo "::set-output name=terraform_code_repository::$LZ_REPO" + echo "::set-output name=terraform_code_ref::$LZ_BRANCH" + echo "::set-output name=environment::$CAF_ENVIRONMENT" + + + + credentials: + uses: ./.github/workflows/rover.yaml + needs: [environment] + secrets: +{% raw %} + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + AZURE_TARGET_SUBSCRIPTION_ID: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + with: + terraform_code_ref: ${{ needs.environment.outputs.terraform_code_ref }} + terraform_code_repository: ${{ needs.environment.outputs.terraform_code_repository }} + environment: ${{ needs.environment.outputs.environment }} + caf_identity_aad_key: cred_identity + landingzone_code_path: ./landingzones/caf_solution + landingzone_configuration_path: ./platform/configuration/level0/credentials + plan_path: ${GITHUB_WORKSPACE}/launchpad_credentials.tfstate.tfplan + tfstate: launchpad_credentials.tfstate + level: level0 + terraform_action: apply +{% endraw %} \ No newline at end of file diff --git a/templates/pipelines/.github/workflows/rover.yaml b/templates/pipelines/.github/workflows/rover.yaml new file mode 100644 index 000000000..e13bd81a7 --- /dev/null +++ b/templates/pipelines/.github/workflows/rover.yaml @@ -0,0 +1,246 @@ +# +# Copyright (c) Microsoft Corporation +# Licensed under the MIT License. +# + +name: "Run terraform with CAF rover" + +on: + workflow_call: + inputs: + bootstrap: + default: false + type: boolean + terraform_code_repository: + description: "Git repository of the the terraform entry code." + required: false + type: string + terraform_code_ref: + description: "Tag or branch name." + required: false + type: string + terraform_action: + description: "Terraform action one of (plan_apply, apply)" + type: string + default: plan + terraform_action_plan: + description: "Terraform action plan and additional parameters" + type: string + required: false + terraform_action_apply: + description: "Terraform action apply and additional parameters" + type: string + required: false + tenant: + description: "Azure Active Directory tenant domain name (e.g: mytenant.onmicrosoft.com or the default custom domain) or the GUID." + required: false + type: string + client_id: + description: Client Id to login Azure profile with OIDC + required: false + type: string + tenant_id: + description: Tenant Id to login Azure profile with OIDC + required: false + type: string + landingzone_code_path: + description: Path of the terraform code + required: false + type: string + landingzone_configuration_path: + description: Path of the configuration files + required: false + type: string + tfstate: + description: name of the tfstate + required: false + type: string + launchpad: + description: Boolean value to specify if the deployment is the launchpad or not + default: false + type: string + environment: + description: Name of the CAF environment + required: false + type: string + level: + description: CAF terraform level of the deployment + required: false + default: level0 + type: string + caf_identity_aad_key: + description: Tag's name of the Keyvault wit the credential to use. + required: false + type: string + plan_path: + description: Full path of the of terraform plan + required: false + type: string + secrets: + AZURE_CLIENT_ID: + required: true + AZURE_TENANT_ID: + required: true + AZURE_LAUNCHPAD_SUBSCRIPTION_ID: + required: true + AZURE_TARGET_SUBSCRIPTION_ID: + required: true + + +env: + TF_CLI_ARGS: '-no-color' + TF_REGISTRY_DISCOVERY_RETRY: 5 + TF_REGISTRY_CLIENT_TIMEOUT: 15 + ROVER_RUNNER: true + +{% raw %} +jobs: + rover: + name: ${{ inputs.level }} + runs-on: [self-hosted, platform] + + steps: + + - name: 'Azure Login' + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID }} + + - name: Checkout Configuration + uses: actions/checkout@v3 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.ref }} + + - name: checkout base terraform landingzone code + shell: bash + run: | + rm -rf ./landingzones + git clone --branch ${{ inputs.terraform_code_ref }} --single-branch ${{ inputs.terraform_code_repository }} ./landingzones + + - id: generate-rover-command + if: inputs.terraform_action == 'plan_apply' + shell: bash + run: | + echo "Retreiving keyvault for credentials" + # Get the keyvault with the credentials + cred_vault_name=$(az keyvault list --query "[?(tags.caf_identity_aad_key=='${{inputs.caf_identity_aad_key}}' && tags.caf_environment=='${{inputs.environment}}')].{name:name}[0]" -o tsv) + if [ -z "${cred_vault_name}" ]; then + echo "Cannot retreive the credentials. Make sure the RBAC are set properly." + echo "Bootstraping the environment." + else + impresonate=("--impersonate-sp-from-keyvault-url https://${cred_vault_name}.vault.azure.net/ ") + fi + + set +e + + rover=("/tf/rover/rover.sh ") + action_plan=("-a plan ${{inputs.terraform_action_plan }} ") + action_apply=("-a apply ${{inputs.terraform_action_apply }} ") + landingzone_code_path=("-lz $(readlink -f ${{inputs.landingzone_code_path}}) ") + landingzone_configuration_path=("-var-folder $(readlink -f ${{inputs.landingzone_configuration_path}}) ") + tfstate_subscription_id=("-tfstate_subscription_id ${{secrets.AZURE_MANAGEMENT_SUBSCRIPTION_ID}} ") + target_subscription=("-target_subscription ${{secrets.AZURE_TARGET_SUBSCRIPTION_ID}} ") + tfstate=("-tfstate ${{inputs.tfstate}} ") + environment=("-env ${{inputs.environment}} ") + level=("-level ${{inputs.level}} ") + output_file=$(readlink -f ./${{inputs.tfstate}}.output) + output=("--output ${output_file} ") + + if [ "${{inputs.launchpad}}" = "true" ]; then + launchpad=("-launchpad ") + fi + if [ ! -z "${{inputs.plan_path}}" ]; then + plan_path=("-p ${{inputs.plan_path}} ") + fi + + command_plan="${rover[@]}${impresonate[@]}${landingzone_code_path[@]}${landingzone_configuration_path[@]}${tfstate_subscription_id[@]}${target_subscription[@]}${tfstate[@]}${environment[@]}${level[@]}${launchpad[@]}${plan_path[@]}${output[@]}${action_plan[@]}${{inputs.terraform_action_plan}}" + command_apply="${rover[@]}${impresonate[@]}${landingzone_code_path[@]}${landingzone_configuration_path[@]}${tfstate_subscription_id[@]}${target_subscription[@]}${tfstate[@]}${environment[@]}${level[@]}${launchpad[@]}" + + echo "::set-output name=rover-command-plan::$(echo ${command_plan})" + echo "::set-output name=rover-command-apply::$(echo ${command_apply})" + echo "::set-output name=output_file::$(echo ${output_file})" + + - id: plan + name: Execute terraform plan + shell: bash + run: | + set +e + + echo "${{ steps.generate-rover-command.outputs.rover-command-plan }}" + eval ${{ steps.generate-rover-command.outputs.rover-command-plan }} 2>&1 | tee outputfile + + plan=$(cat outputfile | grep ' -plan: ') + plan_file=${plan//' -plan: '/} + echo "plan file: '${plan_file}'" + + apply=false + + if [ "${{ inputs.terraform_action }}" = 'plan_apply' ]; then + echo "condition 0f" + verify=false + else + echo "condition 0t" + verify=true + fi + + if [ "$(cat outputfile | grep -o 'No changes')" != 'No changes' ]; then + if [ "$(cat outputfile | grep -o '0 to destroy')" != '0 to destroy' ]; then + echo "condition 1" + apply=true + verify=true + fi + if [ "$(cat outputfile | grep -o '0 to change')" != '0 to change' ]; then + echo "condition 2" + apply=true + fi + if [ "$(cat outputfile | grep -o '0 to add')" != '0 to add' ]; then + echo "condition 3" + apply=true + fi + if [ "$(cat outputfile | grep -o 'so no changes are needed')" = 'so no changes are needed' ]; then + echo "condition 4" + apply=false + verify=false + fi + if [ "$(cat outputfile | grep -o 'Error on or near line')" = 'Error on or near line' ]; then + echo "condition 5" + apply=false + verify=false + set -e + exit 1 + fi + fi + + echo "apply: ${apply}" + echo "verify: ${verify}" + + echo "::set-output name=apply::$(echo ${apply})" + echo "::set-output name=verify::$(echo ${verify})" + echo "::set-output name=plan_file::${plan_file}" + + - id: apply + if: (inputs.terraform_action == 'plan_apply') && (steps.plan.outputs.apply == 'true') + name: Execute terraform apply + shell: bash + run: | + + plan_path=("-p ${{ steps.plan.outputs.plan_file }} ") + action=("-a apply ") + command_apply="${{ steps.generate-rover-command.outputs.rover-command-apply }} ${plan_path[@]}${action[@]}${{inputs.terraform_action_apply}}" + echo ${command_apply} + eval ${command_apply} + + - name: Cleanup + if: always() + run: | + az account clear + echo "Azure session closed." + rm -rf outputfile + rm -rf "$(readlink -f ${{inputs.tfstate}}.output)" + rm -rf ${{inputs.tfstate}} + rm -rf ${{inputs.plan_path}} + +{% endraw %} \ No newline at end of file diff --git a/templates/platform/pipelines/README.md b/templates/pipelines/symphony/README.md similarity index 100% rename from templates/platform/pipelines/README.md rename to templates/pipelines/symphony/README.md diff --git a/templates/platform/pipelines/demo.yaml b/templates/pipelines/symphony/demo.yaml similarity index 100% rename from templates/platform/pipelines/demo.yaml rename to templates/pipelines/symphony/demo.yaml diff --git a/templates/platform/pipelines/symphony_e2e.yaml b/templates/pipelines/symphony/symphony_e2e.yaml similarity index 100% rename from templates/platform/pipelines/symphony_e2e.yaml rename to templates/pipelines/symphony/symphony_e2e.yaml diff --git a/templates/platform/single_subscription.yaml b/templates/platform/caf_platform_prod_nonprod.yaml similarity index 64% rename from templates/platform/single_subscription.yaml rename to templates/platform/caf_platform_prod_nonprod.yaml index 482583e04..56a8ae01b 100644 --- a/templates/platform/single_subscription.yaml +++ b/templates/platform/caf_platform_prod_nonprod.yaml @@ -22,16 +22,28 @@ azure_regions: {{azure_regions }} prefix: {{prefix}} # folder parameters -topology_file: "{{base_folder}}/platform/definition/ignite.yaml" +{% raw %} +bootstrap_file: "{{base_folder}}/platform/definition/ignite.yaml" landingzones_folder: "{{base_folder}}/landingzones" public_templates_folder: "{{base_folder}}/landingzones/templates" +private_templates_folder: "{{base_folder}}/platform/templates" platform_configuration_folder: "{{base_folder}}/platform/configuration" platform_definition_folder: "{{base_folder}}/platform/definition" platform_template_folder: "{{base_folder}}/platform/template" +firewall_rules_path: "{{base_folder}}/platform/firewall_rules" +{% endraw %} deployment_mode: {{deployment_mode}} -caf_landingzone_branch: {{caf_landingzone_branch | default('2204.1.int')}} +terraform_code_repository: https://github.com/Azure/caf-terraform-landingzones.git +caf_landingzone_branch: {{caf_landingzone_branch | default('int.5.6.0')}} +default_rover_image: aztfmod/rover-preview:1.2.3-2206.301920 +# Default image for self-hosted agents +{% if gitops_pipelines == 'github' %} +gitops_default_rover_image: aztfmod/rover-agent:1.1.9-2206.301920-preview-github +{% elif gitops_pipelines == 'tfcloud' %} +gitops_default_rover_image: aztfmod/rover-agent:1.1.9-2206.301920-preview-tfc +{% endif %} caf_regions: {% for region, location in regions.items() %} @@ -51,6 +63,21 @@ resource_groups_allowed_regions: default_region_key: {{default_region_key}} +# Enable the private dns zones deployment and configure vnets to us the secure firewall for private dns resolutions +private_endpoints: true +# Must be set to true for production deployments +keyvault_purge_protection_enabled: false +# Configure the launchpad keyvaults to use azure ad authentication when possible. +keyvault_enable_rbac_authorization: true + +# Render the content of the tfvar list across multiple lines +multiple_lines_list: true + +PUBLIC_IP_WHITE_LIST: +{% for ip in PUBLIC_IP_WHITE_LIST | default([]) %} + - {{ ip }} +{% endfor %} + naming_convention: # When set to false use the CAF provider to generate names aligned to CAF guidance # true: use the name as defined in the configuration files. You may have to iterate multiple times to prevent conflicts with Azure unique names with servides like storage account, keyvault or log analytics workspace. @@ -69,7 +96,7 @@ caf_launchpad: tenant_id: {{tenant_id.stdout}} global_tags_propagated: yes tags: - caf_deployment_mode: demo_single_subscription + caf_deployment_mode: demo azuread_user_ea_account_owner: {{azuread_user_ea_account_owner | default(upn)}} @@ -79,26 +106,9 @@ azuread_identity_mode: service_principal enable_azuread_groups: True enable_azuread_applications: True -enable_azure_subscription_vending_machine: {{topology.enable_azure_subscription_vending_machine | default(bootstrap.enable_azure_subscription_vending_machine)}} - -management_groups: -{% for region, a_value in topology.deployments.platform.alz.items() %} - {{region}}: -{% for key in a_value.keys() %} - {{key}}: - management_group_prefix: "{{ topology.management_groups[region][key].management_group_prefix | default(alz_mg_prefix)}}" - management_group_name: "{{ topology.management_groups[region][key].management_group_name | default(alz_mg_name)}}" - deploy_core_landing_zones: {{topology.management_groups[region][key].deploy_core_landing_zones | default(True)}} - clean_up_destination_folder: {{topology.management_groups[region][key].clean_up_destination_folder | default(True)}} - update_lib_folder: {{topology.management_groups[region][key].update_lib_folder | default(True)}} - version_to_deploy: "{{topology.management_groups[region][key].version_to_deploy | default('v1.1.3')}}" -{% if topology.management_groups[region][key].root_parent_id is defined %} - root_parent_id: "{{topology.management_groups[region][key].root_parent_id}}" -{% endif %} -{% endfor %} -{% endfor %} +enable_azure_subscription_vending_machine: {{bootstrap.enable_azure_subscription_vending_machine | default(bootstrap.enable_azure_subscription_vending_machine)}} -subscription_deployment_mode: single_reuse +subscription_deployment_mode: {{subscription_deployment_mode | default('single_reuse')}} billing_subscription_role_delegations: # true: enable this deployment. The remaining attributes are required. @@ -110,44 +120,52 @@ billing_subscription_role_delegations: enable: false # Azure Active Directory User (UPN) that is Account Owner in the EA portal # if enable=false, set the upn of the user doing the manual deployment - azuread_user_ea_account_owner: {{topology.azuread_user_ea_account_owner | default(upn)}} + azuread_user_ea_account_owner: {{bootstrap.azuread_user_ea_account_owner | default(upn)}} # see comments above to get the object_id # # Also set this GUID to the owner of the launchpad azuread_groups # - azuread_user_ea_account_owner_object_id: {{topology.ea_owner_object_id | default(object_id)}} + azuread_user_ea_account_owner_object_id: {{bootstrap.ea_owner_object_id | default(object_id)}} # Only set the following two attributes when enable=true billing_account_name: enrollment_account_name: -platform_subscriptions: +initial_subscriptions: launchpad: # Do not rename the key - name: {{subscription_name.stdout}} + name: management create_alias: false - subscription_id: {{subscription_id.stdout}} + subscription_id: {{sub_management | default(subscription_id.stdout)}} identity: # Do not rename the key - name: {{subscription_name.stdout}} + name: identity create_alias: false - subscription_id: {{subscription_id.stdout}} + subscription_id: {{sub_identity | default(subscription_id.stdout)}} connectivity: # Do not rename the key - name: {{subscription_name.stdout}} + name: connectivity create_alias: false - subscription_id: {{subscription_id.stdout}} + subscription_id: {{sub_connectivity | default(subscription_id.stdout)}} management: # Do not rename the key - name: {{subscription_name.stdout}} + name: management create_alias: false - subscription_id: {{subscription_id.stdout}} + subscription_id: {{sub_management | default(subscription_id.stdout)}} + security: # Do not rename the key + name: security + create_alias: false + subscription_id: {{sub_security | default(subscription_id.stdout)}} deployments: + scale_out_domain_keys: + - prod + - non_prod platform: root: {{default_region_key}}: - launchpad: launchpad_azuread_sp_single_subscription.yaml - launchpad_credentials: launchpad_credentials_azuread_sp.yaml - subscriptions: subscriptions.yaml + launchpad: launchpad.yaml + launchpad_credentials: launchpad_credentials.yaml + platform_subscriptions: platform_subscriptions.yaml identity: identity.yaml management: management.yaml asvm: asvm.yaml + security: security.yaml gitops_agents: gitops_agents_aci.yaml alz: {{default_region_key}}: @@ -177,10 +195,10 @@ deployments: private_dns: prod: connectivity_private_dns.yaml non_prod: connectivity_private_dns.yaml + security_level2: + prod: security_level2.yaml + non_prod: security_level2.yaml -# -# Advanced settings -# notifications: monitor_action_groups: @@ -228,9 +246,9 @@ azure_landing_zones: # check the AAD property tenant_name: {{ azure_landing_zones.identity.tenant_name | default(tenant_name)}} # only service_principal supported with rover ignite at the moment - azuread_identity_mode: {{topology.azuread_identity_mode}} - enable_azuread_groups: {{topology.enable_azuread_groups}} - enable_azuread_applications: {{topology.enable_azuread_applications}} + azuread_identity_mode: {{bootstrap.azuread_identity_mode}} + enable_azuread_groups: {{bootstrap.enable_azuread_groups}} + enable_azuread_applications: {{bootstrap.enable_azuread_applications}} # UPNs you want to add in the caf_platform_maintainers Azure AD group # Can use user or guest accounts # Those users will have full permissions on platform. @@ -249,8 +267,12 @@ azure_landing_zones: backup_policy: vms: default + security: + backup_policy: + vms: default + connectivity: - networking_topology: + networking_bootstrap: deployment_option: virtual_wan backup_policy: vms: default @@ -263,3 +285,31 @@ configuration_folders: cleanup_destination: true +management_groups: +{% for region, a_value in bootstrap.deployments.platform.alz.items() %} + {{region}}: +{% for key in a_value.keys() %} + {{alz_mg_prefix}}: + management_group_prefix: "{{ bootstrap.management_groups[region].es.management_group_prefix | default(alz_mg_prefix)}}" + management_group_name: "{{ bootstrap.management_groups[region].es.management_group_name | default(alz_mg_name)}}" + deploy_core_landing_zones: {{bootstrap.management_groups[region].es.deploy_core_landing_zones | default(True)}} + clean_up_destination_folder: {{bootstrap.management_groups[region].es.clean_up_destination_folder | default(True)}} + update_lib_folder: {{bootstrap.management_groups[region].es.update_lib_folder | default(True)}} + version_to_deploy: "{{bootstrap.management_groups[region].es.version_to_deploy | default('v1.1.3')}}" +{% if bootstrap.management_groups[region].es.root_parent_id is defined %} + root_parent_id: "{{bootstrap.management_groups[region][key].root_parent_id}}" +{% endif %} + tfstate: + lz_key_name: alz_{{alz_mg_prefix}} + tfstate: alz_{{alz_mg_prefix}}.tfstate + level: level1 + identity_aad_key: cred_alz + config_file: alz_{{alz_mg_prefix}}.yaml + template_lib_folder: platform/level1/alz + sub_template_folder: platform/level1/alz + yaml: platform/level1/alz/ansible.yaml + alz_version: v1.1.3 + # Do not rename the item_key_name + tfstate_key_name: alz_{{alz_mg_prefix}} +{% endfor %} +{% endfor %} \ No newline at end of file diff --git a/templates/platform/deploy_platform.sh b/templates/platform/deploy_platform.sh index 77e3f5f80..51df6b62b 100755 --- a/templates/platform/deploy_platform.sh +++ b/templates/platform/deploy_platform.sh @@ -1,46 +1,106 @@ #! /bin/bash - +set -e +cd /tf/caf export ANSIBLE_DISPLAY_SKIPPED_HOSTS=False params=$(echo ${@} | xargs -n1 | xargs -I@ echo "-e @ " ) -ansible-playbook /tf/caf/landingzones/templates/ansible/walk-through-single.yaml \ - -e topology_file=/tf/caf/landingzones/templates/platform/single_subscription.yaml \ +echo ${params} | xargs +echo "sub_management: ${sub_management}" + +ansible-playbook /tf/caf/landingzones/templates/ansible/walk-through-bootstrap.yaml \ -e public_templates_folder=/tf/caf/landingzones/templates \ -e landingzones_folder=/tf/caf/landingzones \ -e platform_configuration_folder=/tf/caf/configuration \ -e platform_definition_folder=/tf/caf/platform/definition \ -e platform_template_folder=/tf/caf/platform/template \ - -e caf_landingzone_branch='2204.1.int' \ - --extra-vars "@/tf/caf/landingzones/templates/platform/template_topology.yaml" \ + -e template_folder=/tf/caf/landingzones/templates/platform \ + -e firewall_rules_path=/tf/caf/platform/firewall_rules \ + -e keyvault_enable_rbac_authorization=true \ + -e keyvault_purge_protection_enabled=false \ + -e private_endpoints=true \ + -e caf_landingzone_branch="$(cd /tf/caf/landingzones && git rev-parse --abbrev-ref HEAD)" \ + --extra-vars "@/tf/caf/landingzones/templates/platform/ignite.yaml" \ -e $(echo ${params} | xargs) -# Create the initial PR for the bootstrap configuration +# Generate initial configuration +ansible-playbook $(readlink -f ./landingzones/templates/ansible/ansible.yaml) \ + --extra-vars "@$(readlink -f ./platform/definition/ignite.yaml)" \ + -e base_folder=$(pwd) + +/tf/rover/rover.sh \ + -lz /tf/caf/landingzones/caf_launchpad \ + -var-folder /tf/caf/platform/configuration/level0/launchpad \ + -tfstate_subscription_id ${sub_management} \ + -target_subscription ${sub_management} \ + -tfstate caf_launchpad.tfstate \ + -launchpad \ + -env ${TF_VAR_environment} \ + -level level0 \ + -a apply + +# Need to logout and login to get the new group memberships +tenant_id=$(az account show --query tenantId -o tsv) +az account clear + +/tf/rover/rover.sh login -t ${tenant_id} -s ${sub_management} + +/tf/rover/rover.sh \ + -lz /tf/caf/landingzones/caf_solution \ + -var-folder /tf/caf/platform/configuration/level0/gitops_agents \ + -tfstate_subscription_id ${sub_management} \ + -target_subscription ${sub_management} \ + -tfstate gitops_agents.tfstate \ + -env ${TF_VAR_environment} \ + -level level0 \ + -a apply + +/tf/rover/rover.sh \ + -lz /tf/caf/landingzones/caf_solution \ + -var-folder /tf/caf/platform/configuration/level0/credentials \ + -tfstate_subscription_id ${sub_management} \ + -target_subscription ${sub_management} \ + -tfstate launchpad_credentials.tfstate \ + -env ${TF_VAR_environment} \ + -level level0 \ + -a apply + +if [ $? = 0 ]; then + + cd /tf/caf + + if [ "$(gh pr status --json id | jq .currentBranch.id)" = "null" ]; then + git fetch origin main + git checkout -b bootstrap --track origin/main + git add . + pre-commit + git commit -am "Update definition files." + git push origin HEAD + # Create the initial PR for the bootstrap configuration body=<