Skip to content

Commit

Permalink
Terraform tests (#8)
Browse files Browse the repository at this point in the history
* Added terraform HCL integration tests and integrated test execution into CI pipeline.

* Removed superfluous comments from tf tests.
  • Loading branch information
johncollinson2001 authored Sep 18, 2024
1 parent b366831 commit 8ced5a7
Show file tree
Hide file tree
Showing 14 changed files with 340 additions and 52 deletions.
10 changes: 8 additions & 2 deletions .github/workflows/ci-pipeline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: Install Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.5.0
terraform_version: 1.9.3

- name: Terraform Init
run: terraform init -backend=false
Expand All @@ -34,6 +34,12 @@ jobs:
run: terraform validate
working-directory: infrastructure

- name: Run Integration Tests
run: |
terraform init -backend=false
terraform test
working-directory: tests/integration-tests

static-code-analysis:
name: Static Code Analysis
runs-on: ubuntu-latest
Expand All @@ -45,7 +51,7 @@ jobs:
- name: Install Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.5.0
terraform_version: 1.9.3

- name: Run Terraform Format
run: terraform fmt -check
Expand Down
85 changes: 76 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ The following diagram illustrates the high level architecture

The repository consists of the following directories:

* `./.github`

Contains the GitHub workflows in `yaml` format.

[See the YAML schema documentation for more details.](https://learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/?view=azure-pipelines)

* `./.pipelines`

Contains the Azure Pipelines in `yaml` format.
Expand All @@ -86,28 +92,30 @@ The repository consists of the following directories:

Contains scripts that are used to create and maintain the environment.

* `./tests`

Contains the different types of tests used to verify the solution.

## Developer Guide

### Environment Setup

The following are pre-reqs to working with the solution:

* An Azure subscription
* Azure CLI installed
* Terraform installed
* An Azure identity with the following roles:
* Contributor role on the subscription (required to create resources)
* RBAC Administrator role on the resources being backed up (required to assign roles on the resource to the backup vault managed identity)
* An Azure identity assigned the subscription Contributor role (required to create resources)
* [Azure CLI installed](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-windows?tabs=azure-cli)
* [Terraform installed](https://developer.hashicorp.com/terraform/install)

[See the following link for further information.](https://learn.microsoft.com/en-us/azure/developer/terraform/get-started-windows-powershell)
> Ensure all installed components have been added to the `%PATH%` - e.g. `az` and `terraform`.
### Getting Started

Take the following steps to get started in configuring and verify the infrastructure:
Take the following steps to get started in configuring and verifying the infrastructure for your development environment:

1. Login to Azure

Use the Azure CLI to login to Azure by running the following command:
Use Azure CLI to login to Azure by running the following command:

```pwsh
az login
Expand Down Expand Up @@ -167,6 +175,36 @@ Take the following steps to get started in configuring and verify the infrastruc

The repo contains an `example` module which can be utilised to further extend the sample infrastructure with some resources and backup instances. To use this module for dev/test purposes, include the module in `main.tf` and run `terraform apply` again.

### Running the Tests

#### Integration Tests

The test suite consists of a number Terraform HCL integration tests that use a mock azurerm provider.

[See this link for more information.](https://developer.hashicorp.com/terraform/language/tests)

Take the following steps to run the test suite:

1. Initialise Terraform

Change the working directory to `./tests/integration-tests`.

Terraform can now be initialised by running the following command:

````pwsh
terraform init -backend=false
````

> NOTE: There's no need to initialise a backend for the purposes of running the tests.
2. Run the Tests

Run the tests with the following command:

````pwsh
terraform test
````

### Contributing

If you want to contribute to the project, raise a PR on GitHub.
Expand All @@ -185,4 +223,33 @@ We use pre-commit to run analysis and checks on the changes being committed. Tak
* Install pre-commit within the repository with the following command: `pre-commit install`
* Run `pre-commit run --all-files` to check pre-commit is working

> For full details [see this link](https://pre-commit.com/#installation)
> For full details [see this link](https://pre-commit.com/#installation)
## CI Pipeline

The CI pipeline builds and verifies the solution and runs a number of static code analysis steps on the code base.

### End to End Testing

Part of the build verification is the end to end testing step. This requires the pipeline to login to Azure in order to deploy an environment on which to execute the tests.

In order for the CI pipeline to login to Azure the following GitHub actions secret must be created called `AZURE_CREDENTIALS` set as a JSON object in the following structure:

```json
{
"clientSecret": "******",
"subscriptionId": "******",
"tenantId": "******",
"clientId": "******"
}
```

### Static Code Analysis

The following static code analysis checks are executed:

* [Terraform format](https://developer.hashicorp.com/terraform/cli/commands/fmt)
* [Terraform lint](https://github.com/terraform-linters/tflint)
* [Checkov scan](https://www.checkov.io/)
* [Gitleaks scan](https://github.com/gitleaks/gitleaks)
* [Trivy vulnerability scan](https://github.com/aquasecurity/trivy)
16 changes: 16 additions & 0 deletions infrastructure/backup_policy.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module "blob_storage_policy" {
source = "./modules/backup_policy/blob_storage"
policy_name = "bkpol-${var.vault_name}-blobstorage"
vault_id = azurerm_data_protection_backup_vault.backup_vault.id
retention_period = "P7D" # 7 days
# NOTE - this blob policy has been configured for operational backup
# only, which continuously backs up data and does not need a schedule
}

module "managed_disk_policy" {
source = "./modules/backup_policy/managed_disk"
policy_name = "bkpol-${var.vault_name}-manageddisk"
vault_id = azurerm_data_protection_backup_vault.backup_vault.id
retention_period = "P7D" # 7 days
backup_intervals = ["R/2024-01-01T00:00:00+00:00/P1D"] # Once per day at 00:00
}
11 changes: 11 additions & 0 deletions infrastructure/backup_vault.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
resource "azurerm_data_protection_backup_vault" "backup_vault" {
name = "bvault-${var.vault_name}"
resource_group_name = azurerm_resource_group.resource_group.name
location = var.vault_location
datastore_type = "VaultStore"
redundancy = var.vault_redundancy
soft_delete = "Off"
identity {
type = "SystemAssigned"
}
}
41 changes: 0 additions & 41 deletions infrastructure/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,4 @@ terraform {

provider "azurerm" {
features {}
}

resource "azurerm_resource_group" "resource_group" {
location = var.vault_location
name = "rg-nhsbackup-${var.vault_name}"
}

# Create the vault
###########################################################################

resource "azurerm_data_protection_backup_vault" "backup_vault" {
name = "bvault-${var.vault_name}"
resource_group_name = azurerm_resource_group.resource_group.name
location = var.vault_location
datastore_type = "VaultStore"
redundancy = var.vault_redundancy
soft_delete = "Off"
identity {
type = "SystemAssigned"
}
}


# Create some backup policies
###########################################################################

module "blob_storage_policy" {
source = "./modules/backup_policy/blob_storage"
policy_name = "bkpol-${var.vault_name}-blobstorage"
vault_id = azurerm_data_protection_backup_vault.backup_vault.id
retention_period = "P7D" # 7 days
# NOTE - this blob policy has been configured for operational backup
# only, which continuously backs up data and does not need a schedule
}

module "managed_disk_policy" {
source = "./modules/backup_policy/managed_disk"
policy_name = "bkpol-${var.vault_name}-manageddisk"
vault_id = azurerm_data_protection_backup_vault.backup_vault.id
retention_period = "P7D" # 7 days
backup_intervals = ["R/2024-01-01T00:00:00+00:00/P1D"] # Once per day at 00:00
}
12 changes: 12 additions & 0 deletions infrastructure/modules/backup_policy/blob_storage/output.tf
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
output "id" {
value = azurerm_data_protection_backup_policy_blob_storage.backup_policy.id
}

output "name" {
value = azurerm_data_protection_backup_policy_blob_storage.backup_policy.name
}

output "vault_id" {
value = azurerm_data_protection_backup_policy_blob_storage.backup_policy.vault_id
}

output "retention_period" {
value = azurerm_data_protection_backup_policy_blob_storage.backup_policy.operational_default_retention_duration
}
16 changes: 16 additions & 0 deletions infrastructure/modules/backup_policy/managed_disk/output.tf
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
output "id" {
value = azurerm_data_protection_backup_policy_disk.backup_policy.id
}

output "name" {
value = azurerm_data_protection_backup_policy_disk.backup_policy.name
}

output "vault_id" {
value = azurerm_data_protection_backup_policy_disk.backup_policy.vault_id
}

output "retention_period" {
value = azurerm_data_protection_backup_policy_disk.backup_policy.default_retention_duration
}

output "backup_intervals" {
value = azurerm_data_protection_backup_policy_disk.backup_policy.backup_repeating_time_intervals
}
4 changes: 4 additions & 0 deletions infrastructure/resource_group.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
resource "azurerm_resource_group" "resource_group" {
location = var.vault_location
name = "rg-nhsbackup-${var.vault_name}"
}
5 changes: 5 additions & 0 deletions tests/integration-tests/azurerm/data.tfmock.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mock_resource "azurerm_data_protection_backup_vault" {
defaults = {
id = "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group/providers/Microsoft.DataProtection/backupVaults/bvault-testvault"
}
}
78 changes: 78 additions & 0 deletions tests/integration-tests/backup_policy.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
mock_provider "azurerm" {
source = "./azurerm"
}

run "setup_tests" {
module {
source = "./setup"
}
}

run "create_blob_storage_policy" {
command = apply

module {
source = "../../infrastructure"
}

variables {
vault_name = run.setup_tests.vault_name
}

assert {
condition = length(module.blob_storage_policy.id) > 0
error_message = "Blob storage policy id not as expected."
}

assert {
condition = module.blob_storage_policy.name == "bkpol-${var.vault_name}-blobstorage"
error_message = "Blob storage policy name not as expected."
}
assert {
condition = module.blob_storage_policy.vault_id == azurerm_data_protection_backup_vault.backup_vault.id
error_message = "Blob storage policy vault id not as expected."
}
assert {
condition = module.blob_storage_policy.retention_period == "P7D"
error_message = "Blob storage policy retention period not as expected."
}
}
run "create_managed_disk_policy" {
command = apply

module {
source = "../../infrastructure"
}

variables {
vault_name = run.setup_tests.vault_name
}

assert {
condition = length(module.managed_disk_policy.id) > 0
error_message = "Managed disk policy id not as expected."
}

assert {
condition = module.managed_disk_policy.name == "bkpol-${var.vault_name}-manageddisk"
error_message = "Managed disk policy name not as expected."
}
assert {
condition = module.managed_disk_policy.vault_id == azurerm_data_protection_backup_vault.backup_vault.id
error_message = "Managed disk policy vault id not as expected."
}
assert {
condition = module.managed_disk_policy.retention_period == "P7D"
error_message = "Managed disk policy retention period not as expected."
}
assert {
condition = can(module.managed_disk_policy.backup_intervals) && length(module.managed_disk_policy.backup_intervals) == 1 && module.managed_disk_policy.backup_intervals[0] == "R/2024-01-01T00:00:00+00:00/P1D"
error_message = "Managed disk policy backup intervals not as expected."
}
}
Loading

0 comments on commit 8ced5a7

Please sign in to comment.