From 1acc9797fe8f22558e03ed5c2d00cf0be8a653b7 Mon Sep 17 00:00:00 2001 From: Jorge Docampo Date: Mon, 17 Jul 2023 12:37:50 +0200 Subject: [PATCH] Azure Databricks Access Connector module (#243) * adb-access-connector module * update README * adjust precondition * terraform-docs: automated action --------- Co-authored-by: github-actions[bot] --- .../workflows/databricks-access-connector.yml | 98 +++++++++++++++++++ README.md | 3 +- .../databricks/access-connector/README.md | 25 +++++ terraform/databricks/access-connector/main.tf | 23 +++++ .../databricks/access-connector/outputs.tf | 15 +++ .../test/databricks_access_connector.tf | 30 ++++++ .../access-connector/test/locals.tf | 8 ++ .../access-connector/test/outputs.tf | 15 +++ .../access-connector/test/providers.tf | 23 +++++ .../access-connector/test/unit_test.go | 38 +++++++ .../access-connector/test/variables.tf | 10 ++ .../databricks/access-connector/variables.tf | 40 ++++++++ 12 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/databricks-access-connector.yml create mode 100644 terraform/databricks/access-connector/README.md create mode 100644 terraform/databricks/access-connector/main.tf create mode 100644 terraform/databricks/access-connector/outputs.tf create mode 100644 terraform/databricks/access-connector/test/databricks_access_connector.tf create mode 100644 terraform/databricks/access-connector/test/locals.tf create mode 100644 terraform/databricks/access-connector/test/outputs.tf create mode 100644 terraform/databricks/access-connector/test/providers.tf create mode 100644 terraform/databricks/access-connector/test/unit_test.go create mode 100644 terraform/databricks/access-connector/test/variables.tf create mode 100644 terraform/databricks/access-connector/variables.tf diff --git a/.github/workflows/databricks-access-connector.yml b/.github/workflows/databricks-access-connector.yml new file mode 100644 index 00000000..744a1c35 --- /dev/null +++ b/.github/workflows/databricks-access-connector.yml @@ -0,0 +1,98 @@ +name: Module:databricks-access-connector +on: + workflow_dispatch: + pull_request: + branches: + - main + paths: + - '.github/workflows/databricks-access-connector.yml' + - 'terraform/databricks/access-connector/**' +# - '.github/actions/**' + +env: + terraform_workingdir: "terraform/databricks/access-connector" + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} +jobs: + terraform-lint: + name: Run Terraform lint + runs-on: ubuntu-latest + defaults: + run: + working-directory: "${{ env.terraform_workingdir }}" + + steps: + - uses: actions/checkout@v3 + - uses: hashicorp/setup-terraform@v2 + + - name: Terraform fmt + id: fmt + run: terraform fmt -check + continue-on-error: false + + terraform-sec: + name: Run Terraform tfsec + needs: + - terraform-lint + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@main + + - name: Run tfsec with reviewdog output on the PR + uses: ./.github/actions/run-terraform-sec + + terratest: + name: Run Terratest + needs: + - terraform-sec + runs-on: [self-hosted, 1ES.Pool=azure-data-labs-modules] + environment: + name: acctests + + defaults: + run: + working-directory: "${{ env.terraform_workingdir }}/test" + + steps: + - name: Check out code + uses: actions/checkout@v3 + + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.18.2 + + - name: Setup Dependencies + run: | + az login --identity > /dev/null + export ARM_USE_MSI=true + export ARM_SUBSCRIPTION_ID=$(az login --identity | jq -r '.[0] | .id') + export ARM_TENANT_ID=$(az login --identity | jq -r '.[0] | .tenantId') + go mod init test && go mod tidy + env: + GOPATH: "/home/cloudtest/work/azure-labs-modules/azure-labs-modules/${{ env.terraform_workingdir }}" + + - name: Unit-test + run: | + az login --identity > /dev/null + export ARM_USE_MSI=true + export ARM_SUBSCRIPTION_ID=$(az login --identity | jq -r '.[0] | .id') + export ARM_TENANT_ID=$(az login --identity | jq -r '.[0] | .tenantId') + go test -v -timeout 45m + env: + GOPATH: "/home/cloudtest/work/azure-labs-modules/azure-labs-modules/${{ env.terraform_workingdir }}" + + terraform-docs: + name: Run Terraform Docs + needs: + - terratest + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Render terraform docs and push changes back to PR + uses: ./.github/actions/run-terraform-docs \ No newline at end of file diff --git a/README.md b/README.md index 763020d0..d566fe76 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,8 @@ Looking for **built-in templates**? Check out [Azure Data Labs templates](https: | [Data Factory / Self-Hosted Integration Runtime](https://github.com/Azure/azure-data-labs-modules/tree/main/terraform/data-factory/self-hosted-integration-runtime) | [![Module:self-hosted-integration-runtime](https://github.com/Azure/azure-data-labs-modules/actions/workflows/self-hosted-integration-runtime.yml/badge.svg)](https://github.com/Azure/azure-data-labs-modules/actions/workflows/self-hosted-integration-runtime.yml) | | [Data Share / Data Share](https://github.com/Azure/azure-data-labs-modules/tree/main/terraform/data-share/data-share) | [![Module:data-share](https://github.com/Azure/azure-data-labs-modules/actions/workflows/data-share.yml/badge.svg)](https://github.com/Azure/azure-data-labs-modules/actions/workflows/data-share.yml) | | [Data Share / Data Share Account](https://github.com/Azure/azure-data-labs-modules/tree/main/terraform/data-share/data-share-account) | [![Module:data-share-account](https://github.com/Azure/azure-data-labs-modules/actions/workflows/data-share-account.yml/badge.svg)](https://github.com/Azure/azure-data-labs-modules/actions/workflows/data-share-account.yml) | -| [Databricks / Workspace](https://github.com/Azure/azure-data-labs-modules/tree/main/terraform/databricks) | [![Module:databricks](https://github.com/Azure/azure-data-labs-modules/actions/workflows/databricks.yml/badge.svg)](https://github.com/Azure/azure-data-labs-modules/actions/workflows/databricks.yml) | +| [Databricks / Workspace](https://github.com/Azure/azure-data-labs-modules/tree/main/terraform/databricks/databricks-workspace) | [![Module:databricks](https://github.com/Azure/azure-data-labs-modules/actions/workflows/databricks.yml/badge.svg)](https://github.com/Azure/azure-data-labs-modules/actions/workflows/databricks.yml) | +| [Databricks / Access Connector](https://github.com/Azure/azure-data-labs-modules/tree/main/terraform/databricks/access-connector) | [![Module:databricks-access-connector](https://github.com/Azure/azure-data-labs-modules/actions/workflows/databricks-access-connector.yml/badge.svg)](https://github.com/Azure/azure-data-labs-modules/actions/workflows/databricks-access-connector.yml) | | [Event Grid / Domain](https://github.com/Azure/azure-data-labs-modules/tree/main/terraform/event-grid/event-grid-domain) | [![Module:event-grid-domain](https://github.com/Azure/azure-data-labs-modules/actions/workflows/event-grid-domain.yml/badge.svg)](https://github.com/Azure/azure-data-labs-modules/actions/workflows/event-grid-domain.yml) | [Event Grid / Topic](https://github.com/Azure/azure-data-labs-modules/tree/main/terraform/event-grid/event-grid-topic) | [![Module:event-grid-topic](https://github.com/Azure/azure-data-labs-modules/actions/workflows/event-grid-topic.yml/badge.svg)](https://github.com/Azure/azure-data-labs-modules/actions/workflows/event-grid-topic.yml) | [Event Hubs / Event Hubs](https://github.com/Azure/azure-data-labs-modules/tree/main/terraform/event-hubs/event-hubs) | [![Module:event-hubs-namespace](https://github.com/Azure/azure-data-labs-modules/actions/workflows/event-hubs.yml/badge.svg)](https://github.com/Azure/azure-data-labs-modules/actions/workflows/event-hubs.yml) diff --git a/terraform/databricks/access-connector/README.md b/terraform/databricks/access-connector/README.md new file mode 100644 index 00000000..2f9cac04 --- /dev/null +++ b/terraform/databricks/access-connector/README.md @@ -0,0 +1,25 @@ + +## Resources + +| Name | Type | +|------|------| +| [azurerm_databricks_access_connector.adl_adb_access_connector](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/databricks_access_connector) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [basename](#input\_basename) | Basename of the module. | `string` | n/a | yes | +| [resource\_group\_name](#input\_resource\_group\_name) | Resource group name. | `string` | n/a | yes | +| [location](#input\_location) | Location of the resource group. | `string` | n/a | yes | +| [identity\_ids](#input\_identity\_ids) | Specifies the IDs of the User Assigned Managed Identities to be assigned to the Databricks Access Connector. Only one User Assigned Managed Identity ID is supported per Databricks Access Connector resource. | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A mapping of tags which should be assigned to the deployed resource. | `map(string)` | `{}` | no | +| [module\_enabled](#input\_module\_enabled) | Variable to enable or disable the module. | `bool` | `true` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [id](#output\_id) | The ID of the Databricks Access Connector in the Azure management plane. | +| [identity](#output\_identity) | A list of identity blocks containing the system-assigned managed identities. | + \ No newline at end of file diff --git a/terraform/databricks/access-connector/main.tf b/terraform/databricks/access-connector/main.tf new file mode 100644 index 00000000..51118379 --- /dev/null +++ b/terraform/databricks/access-connector/main.tf @@ -0,0 +1,23 @@ +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/databricks_access_connector + +resource "azurerm_databricks_access_connector" "adl_adb_access_connector" { + name = "adb-ac-${var.basename}" + resource_group_name = var.resource_group_name + location = var.location + + identity { + type = length(var.identity_ids) == 0 ? "SystemAssigned" : "UserAssigned" + identity_ids = length(var.identity_ids) == 0 ? [] : var.identity_ids + } + + tags = var.tags + + count = var.module_enabled ? 1 : 0 + + lifecycle { + precondition { + condition = length(var.identity_ids) < 2 + error_message = "Only one User Assigned Managed Identity ID is supported per Databricks Access Connector resource." + } + } +} diff --git a/terraform/databricks/access-connector/outputs.tf b/terraform/databricks/access-connector/outputs.tf new file mode 100644 index 00000000..f8c59d8d --- /dev/null +++ b/terraform/databricks/access-connector/outputs.tf @@ -0,0 +1,15 @@ +output "id" { + value = ( + length(azurerm_databricks_access_connector.adl_adb_access_connector) > 0 ? + azurerm_databricks_access_connector.adl_adb_access_connector[0].id : null + ) + description = "The ID of the Databricks Access Connector in the Azure management plane." +} + +output "identity" { + value = ( + length(azurerm_databricks_access_connector.adl_adb_access_connector) > 0 ? + azurerm_databricks_access_connector.adl_adb_access_connector[0].identity : null + ) + description = "A list of identity blocks containing the system-assigned managed identities." +} \ No newline at end of file diff --git a/terraform/databricks/access-connector/test/databricks_access_connector.tf b/terraform/databricks/access-connector/test/databricks_access_connector.tf new file mode 100644 index 00000000..c655108e --- /dev/null +++ b/terraform/databricks/access-connector/test/databricks_access_connector.tf @@ -0,0 +1,30 @@ +module "access_connector_system_identity" { + source = "../" + basename = random_string.postfix.result + resource_group_name = module.local_rg.name + location = var.location + tags = {} +} + +module "access_connector_user_identity" { + source = "../" + basename = random_string.postfix.result + resource_group_name = module.local_rg.name + location = var.location + identity_ids = [azurerm_user_assigned_identity.example.id] + tags = {} +} + +resource "azurerm_user_assigned_identity" "example" { + location = var.location + name = "uami-${random_string.postfix.result}" + resource_group_name = module.local_rg.name + tags = {} +} + +module "local_rg" { + source = "../../../resource-group" + basename = random_string.postfix.result + location = var.location + tags = local.tags +} diff --git a/terraform/databricks/access-connector/test/locals.tf b/terraform/databricks/access-connector/test/locals.tf new file mode 100644 index 00000000..a9f867a5 --- /dev/null +++ b/terraform/databricks/access-connector/test/locals.tf @@ -0,0 +1,8 @@ +locals { + tags = { + Project = "Azure/azure-data-labs-modules" + Module = "databricks-workspace" + Toolkit = "Terraform" + } + +} \ No newline at end of file diff --git a/terraform/databricks/access-connector/test/outputs.tf b/terraform/databricks/access-connector/test/outputs.tf new file mode 100644 index 00000000..debe6670 --- /dev/null +++ b/terraform/databricks/access-connector/test/outputs.tf @@ -0,0 +1,15 @@ +output "ua_id" { + value = module.access_connector_system_identity.id +} + +output "ua_identity" { + value = module.access_connector_system_identity.identity +} + +output "sa_id" { + value = module.access_connector_user_identity.id +} + +output "sa_identity" { + value = module.access_connector_user_identity.identity +} diff --git a/terraform/databricks/access-connector/test/providers.tf b/terraform/databricks/access-connector/test/providers.tf new file mode 100644 index 00000000..1769be88 --- /dev/null +++ b/terraform/databricks/access-connector/test/providers.tf @@ -0,0 +1,23 @@ +terraform { + backend "azurerm" { + resource_group_name = "rg-adl-terraform-state" + storage_account_name = "stadlterraformstate" + container_name = "default" + key = "databricks.terraform.tfstate" + } + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "= 3.61.0" + } + databricks = { + source = "databricks/databricks" + version = "= 1.10.1" + } + } +} + +provider "azurerm" { + features {} +} \ No newline at end of file diff --git a/terraform/databricks/access-connector/test/unit_test.go b/terraform/databricks/access-connector/test/unit_test.go new file mode 100644 index 00000000..75ff93a9 --- /dev/null +++ b/terraform/databricks/access-connector/test/unit_test.go @@ -0,0 +1,38 @@ +package test + +import ( + "testing" + "github.com/gruntwork-io/terratest/modules/terraform" + "github.com/stretchr/testify/assert" +) + +func TestModule(t *testing.T) { + t.Parallel() + + terraformOptions := &terraform.Options{ + TerraformDir: "./", + Lock: true, + LockTimeout: "1800s", + // VarFiles: []string{"terraform_unitest.tfvars"}, + } + + // At the end of the test, run `terraform destroy` to clean up any resources that were created + defer terraform.Destroy(t, terraformOptions) + + // Is used mainly for debugging, fail early if plan is not possible + terraform.InitAndPlan(t, terraformOptions) + + // This will run `terraform init` and `terraform apply` and fail the test if there are any errors + terraform.InitAndApply(t, terraformOptions) + + // Check if the outputs exist + assert := assert.New(t) + ua_id := terraform.Output(t, terraformOptions, "ua_id") + assert.NotNil(ua_id) + ua_identity := terraform.Output(t, terraformOptions, "ua_identity") + assert.NotNil(ua_identity) + sa_id := terraform.Output(t, terraformOptions, "sa_id") + assert.NotNil(sa_id) + sa_identity := terraform.Output(t, terraformOptions, "sa_identity") + assert.NotNil(sa_identity) +} \ No newline at end of file diff --git a/terraform/databricks/access-connector/test/variables.tf b/terraform/databricks/access-connector/test/variables.tf new file mode 100644 index 00000000..b025435b --- /dev/null +++ b/terraform/databricks/access-connector/test/variables.tf @@ -0,0 +1,10 @@ +resource "random_string" "postfix" { + length = 8 + special = false + upper = false +} + +variable "location" { + type = string + default = "North Europe" +} \ No newline at end of file diff --git a/terraform/databricks/access-connector/variables.tf b/terraform/databricks/access-connector/variables.tf new file mode 100644 index 00000000..b80e79b3 --- /dev/null +++ b/terraform/databricks/access-connector/variables.tf @@ -0,0 +1,40 @@ +variable "basename" { + type = string + description = "Basename of the module." + validation { + condition = can(regex("^[-\\w]{0,60}$", var.basename)) + error_message = "The name must be between 0 and 60 characters, can contain only alphanumeric characters, underscores, and hyphens." + } +} + +variable "resource_group_name" { + type = string + description = "Resource group name." + validation { + condition = can(regex("^[-\\w\\.\\(\\)]{1,90}$", var.resource_group_name)) && can(regex("[-\\w\\(\\)]+$", var.resource_group_name)) + error_message = "Resource group names must be between 1 and 90 characters and can only include alphanumeric, underscore, parentheses, hyphen, period (except at end)." + } +} + +variable "location" { + type = string + description = "Location of the resource group." +} + +variable "identity_ids" { + type = list(string) + description = "Specifies the IDs of the User Assigned Managed Identities to be assigned to the Databricks Access Connector. Only one User Assigned Managed Identity ID is supported per Databricks Access Connector resource." + default = [] +} + +variable "tags" { + type = map(string) + default = {} + description = "A mapping of tags which should be assigned to the deployed resource." +} + +variable "module_enabled" { + type = bool + description = "Variable to enable or disable the module." + default = true +}