-
Notifications
You must be signed in to change notification settings - Fork 8
Infrastructure as Code via Terraform #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
MartinPankraz
merged 9 commits into
Azure-Samples:main
from
lechnerc77:feat/iac-via-terraform
Feb 10, 2023
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
5e505c1
initial terraform setup
lechnerc77 bc030bc
update terraform
lechnerc77 edbfa4d
bug fixes Terraform, workspaces
lechnerc77 dc7f46a
finalization Terraform setup
lechnerc77 d1bb056
finalized documentation for Terraform
lechnerc77 c59ed5b
review comments + terraform specifics
lechnerc77 73d0301
Fix for devcontainer consistency
lechnerc77 08f039b
restructuring for health endpoint
lechnerc77 0f72897
Disclaimer for Codespaces
lechnerc77 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| .azure | ||
| node_modules | ||
| dist | ||
| dist | ||
| infra-terraform/.terraform.lock.hcl |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| # Infrastructure Deployment via Azure Developer CLI for Terraform | ||
|
|
||
| > **Note** - there are known issues when using the Terraform provider in combination with GitHub Codespaces i.e., the login flow via the Azure CLI gets stuck in the callback. You find the details in this [issue](https://github.com/Azure/azure-dev/pull/1497) of the `azd` repository as well as in this [issue](https://github.com/Azure/azure-dev/pull/1496) in the Azure CLI repository. The described workaround in the second issue did not do the trick for us. Hence, we currently cannot support GitHub Codespaces in this type of infrastructure setup. | ||
|
|
||
| The basic steps of using `azd` to setup remain unchanged as described [here](./DEPLOYMENT-AZD.md). There are three spots that differ from a technical perspective, namely: | ||
|
|
||
| * The login flow via Azure CLI | ||
| * The specification of Terraform as IaC in the `azure.yaml` manifest. | ||
| * The definition of the infrastructure via Terraform files and modules | ||
|
|
||
| In the following sections describe the points that need to be taken in to account if you want to use Terraform. | ||
|
|
||
| ## The login flow | ||
|
|
||
| To deploy the infrastructure via Terraform `azd` uses the [Azure Terraform Provider](https://github.com/hashicorp/terraform-provider-azurerm) under the hood. This provider (not `azd`) is using the Azure CLI login flow. As a consequence when using Terraform you must enforce `azd` to make use of this flow: | ||
|
|
||
| * Configure `azd` to make use of the Azure CLI via the command `azd config set auth.useAzCliAuth true`. | ||
| * If you are using the `devcontainer` or GitHub Codespaces, make sure to add the Azure CLI feature in the `devcontainer.json` file. We provide the corresponding template as [/templates/devcontainer.json.azcli](../templates/devcontainer.json.azcli) in this repository. | ||
|
|
||
| ## Adjustment of manifest | ||
|
|
||
| The main file configuring the behavior of `azd` is the `azure.yml` file in the root repository. In order to make use of Terraform we must change it to use it for the infrastructure deployment as the default is bicep. To achieve this add the following section to the file to specify the infrastructure *provider* as well as the *path* to the Terraform files: | ||
|
|
||
| ```yaml | ||
| infra: | ||
| provider: terraform | ||
| path: ./infra-terraform | ||
| ``` | ||
|
|
||
| > **Note** - The default path is `infra`. As we already have our `.bicep` resources defined there, we use a different path that we have to explicitly specify in the manifest. | ||
|
|
||
| ## Infrastructure setup | ||
|
|
||
| The Terraform files are located in the `infra-terraform` folder. As you can see the structure contains the modules provided by the `azd` team containing the Terraform modules that represent the core building blocks of your application. These modules are use in the `main.tf` file to define the setup of our app. | ||
|
|
||
| In the following section we present the single files of the Terraform-based setup: | ||
|
|
||
| ### The "provider.tf" file | ||
|
|
||
| This files defines what Terraform provider should be usd in order to do the setup. In addition the provider features for how to handle the purging of an Azure Key Vault as well as how deal with non-empty Resource Groups are defined in this file. | ||
|
|
||
| ### The "variables.tf" file | ||
|
|
||
| This files defines the input variables for the `main.tf` file. They can be seen like function arguments and are used in the setup of the infrastructure to e.g., provide the value for a specific resource configuration. | ||
|
|
||
| Here we also placed our quickstarter specific variables that define the access to SAP system's OData services. | ||
|
|
||
| ### The "output.tf" file | ||
|
|
||
| This file defines the output of the provisioning e.g., the URL endpoint of the application. The values correspond to function return values. They will be displayed in the CLI output when the provisioning was successfully executed. | ||
|
|
||
| ### The "main.tfvars.json" file | ||
|
|
||
| This files is the so called **variable definitions** file. This is one way to set the input variables defined in the `variables.tf`. | ||
|
|
||
| ### The "main.tf" file | ||
|
|
||
| This file is the core of the Terraform infrastructure configuration. Here we define the infrastructure setup of our application by combining the different building blocks provided via the Terraform modules. One example is the configuration of the module to provision the Azure App Service: | ||
|
|
||
| ```tf | ||
| module "api" { | ||
| source = "./modules/appservicenode" | ||
| location = var.location | ||
| rg_name = azurerm_resource_group.rg.name | ||
| resource_token = local.resource_token | ||
|
|
||
| tags = merge(local.tags, { "azd-service-name" : "sap-cloud-sdk-api" }) | ||
| service_name = "sap-cloud-sdk-api" | ||
| appservice_plan_id = module.appserviceplan.APPSERVICE_PLAN_ID | ||
| app_settings = { | ||
| "SCM_DO_BUILD_DURING_DEPLOYMENT" = "true" | ||
| "AZURE_KEY_VAULT_ENDPOINT" = module.keyvault.AZURE_KEY_VAULT_ENDPOINT | ||
| "APPLICATIONINSIGHTS_CONNECTION_STRING" = module.applicationinsights.APPLICATIONINSIGHTS_CONNECTION_STRING | ||
| "ODATA_URL" = var.oDataUrl | ||
| "ODATA_USERNAME" = var.oDataUsername | ||
| "ODATA_USERPWD" = "@Microsoft.KeyVault(SecretUri=${module.keyvault.AZURE_KEY_VAULT_ENDPOINT}secrets/${local.abbrKeyVaultVaults}secret-odata-password)" | ||
| "APIKEY" = "@Microsoft.KeyVault(SecretUri=${module.keyvault.AZURE_KEY_VAULT_ENDPOINT}secrets/${local.abbrKeyVaultVaults}secret-apikey)" | ||
| "APIKEY_HEADERNAME" = var.ApiKeyHeaderName | ||
| } | ||
|
|
||
| app_command_line = "" | ||
|
|
||
| always_on = false | ||
| identity = [{ | ||
| type = "SystemAssigned" | ||
| }] | ||
| } | ||
| ``` | ||
|
|
||
| As you can see we use the variables defined in the `variables.tf` file to configure e.g., the app settings. The components are basically the same as in the Bicep flow, so we do not repeat them in detail here. | ||
|
|
||
| ## Mind the differences | ||
|
|
||
| The Terraform-based setup in the `azd` was introduced a later than the one using Bicep. Due to that and due to the differences between them, some things work differently. Here are two things that we think are worth to mention. | ||
|
|
||
| ### Purging of Azure Key Vault | ||
|
|
||
| When using Bicep to provide your infrastructure you get asked by the CLI if you want to purge you Azure Key Vault when deprovisioning your infrastructure. This is not the case when using Terraform. If you want to make sure that the Key Vault is purged when deprovisioning your infrastructure you must make an adjustment in the `provider.tf` file namely you must set the `purge_soft_delete_on_destroy` to `false`: | ||
|
|
||
| ```tf | ||
| key_vault { | ||
| purge_soft_delete_on_destroy = false | ||
| } | ||
| ``` | ||
|
|
||
| The purging is automatically done once you delete your infrastructure. | ||
|
|
||
| > **Note** - We do not recommend the automatic purging of the Azure Key Vault in a productive environment | ||
|
|
||
| ### Changes in the appservicenode module | ||
|
|
||
| In order to enable you to deploy the Azure App Service in the SKU `F1` i.e., the free plan, we had to adjust some modules delivered by the `azd` team. One parameter available in the Bicep variant is missing in the Terraform setup, namely the `site configuration` `always_on`. This is set to `true` which prevents the deployment to a free plan. To enable this we made the following adjustements: | ||
|
|
||
| 1. Introduction of the parameter as a variable in the `infra-terraform\modules\appservicenode\appservicenode_variables.tf` | ||
|
|
||
| ```tf | ||
| variable "always_on" { | ||
| description = "The always on setting for the app service." | ||
| type = bool | ||
| default = true | ||
| } | ||
| ``` | ||
|
|
||
| 2. Exchanging the hard-coded value for the parameter with the newly defined variable in the `infra-terraform/modules/appservicenode/appservicenode.tf`file: | ||
|
|
||
| ```tf | ||
| resource "azurerm_linux_web_app" "web" { | ||
| name = azurecaf_name.web_name.result | ||
| location = var.location | ||
| resource_group_name = var.rg_name | ||
| service_plan_id = var.appservice_plan_id | ||
| https_only = true | ||
| tags = var.tags | ||
|
|
||
| site_config { | ||
| always_on = var.always_on | ||
| ftps_state = "FtpsOnly" | ||
| app_command_line = var.app_command_line | ||
| application_stack { | ||
| node_version = var.node_version | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| With these changes we were able to set the variable in the definition of the Azure App Service in the `main.tf` file. | ||
|
|
||
| ## More information | ||
|
|
||
| You find more information about `azd` and Terraform [here](https://learn.microsoft.com/azure/developer/azure-developer-cli/use-terraform-for-azd). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| locals { | ||
| tags = { azd-env-name : var.environment_name } | ||
| sha = base64encode(sha256("${var.environment_name}${var.location}${data.azurerm_client_config.current.subscription_id}")) | ||
| resource_token = substr(replace(lower(local.sha), "[^A-Za-z0-9_]", ""), 0, 13) | ||
| abbr_key_vault_vaults = "kv-" | ||
| always_on = var.sku_name == "F1" || var.sku_name == "FREE" || var.sku_name == "SHARED" ? false : true | ||
| use_32_bit_worker = var.sku_name == "F1" || var.sku_name == "FREE" || var.sku_name == "SHARED" ? true : false | ||
| } | ||
| # ------------------------------------------------------------------------------------------------------ | ||
| # Deploy resource Group | ||
| # ------------------------------------------------------------------------------------------------------ | ||
| resource "azurecaf_name" "rg_name" { | ||
| name = var.environment_name | ||
| resource_type = "azurerm_resource_group" | ||
| random_length = 0 | ||
| clean_input = true | ||
| } | ||
|
|
||
| resource "azurerm_resource_group" "rg" { | ||
| name = azurecaf_name.rg_name.result | ||
| location = var.location | ||
| tags = local.tags | ||
| } | ||
|
|
||
| # ------------------------------------------------------------------------------------------------------ | ||
| # Deploy application insights | ||
| # ------------------------------------------------------------------------------------------------------ | ||
| module "applicationinsights" { | ||
| source = "./modules/applicationinsights" | ||
| location = var.location | ||
| rg_name = azurerm_resource_group.rg.name | ||
| environment_name = var.environment_name | ||
| workspace_id = module.loganalytics.LOGANALYTICS_WORKSPACE_ID | ||
| tags = azurerm_resource_group.rg.tags | ||
| resource_token = local.resource_token | ||
| } | ||
|
|
||
| # ------------------------------------------------------------------------------------------------------ | ||
| # Deploy log analytics | ||
| # ------------------------------------------------------------------------------------------------------ | ||
| module "loganalytics" { | ||
| source = "./modules/loganalytics" | ||
| location = var.location | ||
| rg_name = azurerm_resource_group.rg.name | ||
| tags = azurerm_resource_group.rg.tags | ||
| resource_token = local.resource_token | ||
| } | ||
|
|
||
| # ------------------------------------------------------------------------------------------------------ | ||
| # Deploy key vault | ||
| # ------------------------------------------------------------------------------------------------------ | ||
| module "keyvault" { | ||
| source = "./modules/keyvault" | ||
| location = var.location | ||
| principal_id = var.principal_id | ||
| rg_name = azurerm_resource_group.rg.name | ||
| tags = azurerm_resource_group.rg.tags | ||
| resource_token = local.resource_token | ||
| access_policy_object_ids = [module.api.IDENTITY_PRINCIPAL_ID] | ||
| secrets = [ | ||
| { | ||
| name = "${local.abbr_key_vault_vaults}secret-odata-password" | ||
| value = var.oDataUserpwd | ||
| }, | ||
| { | ||
| name = "${local.abbr_key_vault_vaults}secret-apikey" | ||
| value = var._APIKey | ||
| } | ||
| ] | ||
| } | ||
|
|
||
| # ------------------------------------------------------------------------------------------------------ | ||
| # Deploy app service plan | ||
| # ------------------------------------------------------------------------------------------------------ | ||
| module "appserviceplan" { | ||
| source = "./modules/appserviceplan" | ||
| location = var.location | ||
| rg_name = azurerm_resource_group.rg.name | ||
| tags = azurerm_resource_group.rg.tags | ||
| resource_token = local.resource_token | ||
| sku_name = var.sku_name | ||
| } | ||
|
|
||
| # ------------------------------------------------------------------------------------------------------ | ||
| # Deploy app service api | ||
| # ------------------------------------------------------------------------------------------------------ | ||
| module "api" { | ||
| source = "./modules_local/appservicenode" | ||
| location = var.location | ||
| rg_name = azurerm_resource_group.rg.name | ||
| resource_token = local.resource_token | ||
|
|
||
| tags = merge(local.tags, { "azd-service-name" : "sap-cloud-sdk-api" }) | ||
| service_name = "sap-cloud-sdk-api" | ||
| appservice_plan_id = module.appserviceplan.APPSERVICE_PLAN_ID | ||
| app_settings = { | ||
| "SCM_DO_BUILD_DURING_DEPLOYMENT" = "true" | ||
| "AZURE_KEY_VAULT_ENDPOINT" = module.keyvault.AZURE_KEY_VAULT_ENDPOINT | ||
| "APPLICATIONINSIGHTS_CONNECTION_STRING" = module.applicationinsights.APPLICATIONINSIGHTS_CONNECTION_STRING | ||
| "ODATA_URL" = var.oDataUrl | ||
| "ODATA_USERNAME" = var.oDataUsername | ||
| "ODATA_USERPWD" = "@Microsoft.KeyVault(SecretUri=${module.keyvault.AZURE_KEY_VAULT_ENDPOINT}secrets/${local.abbr_key_vault_vaults}secret-odata-password)" | ||
| "APIKEY" = "@Microsoft.KeyVault(SecretUri=${module.keyvault.AZURE_KEY_VAULT_ENDPOINT}secrets/${local.abbr_key_vault_vaults}secret-apikey)" | ||
| "APIKEY_HEADERNAME" = var.ApiKeyHeaderName | ||
| } | ||
|
|
||
| app_command_line = "" | ||
| health_check_path = var.health_check_path | ||
| always_on = local.always_on | ||
| use_32_bit_worker = local.use_32_bit_worker | ||
| identity = [{ | ||
| type = "SystemAssigned" | ||
| }] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "location": "${AZURE_LOCATION}", | ||
| "environment_name": "${AZURE_ENV_NAME}", | ||
| "principal_id": "${AZURE_PRINCIPAL_ID}" | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make the bicep workspace flavor default somehow? Would make things easier on first run with gh codespaces and vsc.
Seems like no easy way present at the moment: microsoft/vscode#161414
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked the documentation and did not find a way to define one workspace as default, neither via the workspace files nor via a vscode configuration we could attach.