Adding unit and integration tests for azure-simple template. (#142)

This commit is contained in:
Nicholas M. Iodice 2019-06-04 11:24:52 -04:00 коммит произвёл GitHub
Родитель 6a872abb9e
Коммит a65bbe2214
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 255 добавлений и 9 удалений

Просмотреть файл

@ -46,6 +46,8 @@ variables:
value: 'infra-test-harness'
- name: TERM
value: 'xterm-color'
- name: TF_WARN_OUTPUT_ERRORS
value: 1
steps:
- checkout: self
@ -91,6 +93,7 @@ steps:
-e TF_VAR_remote_state_account=$(TF_VAR_remote_state_account) \
-e TF_VAR_remote_state_container=$(TF_VAR_remote_state_container) \
-e ARM_ACCESS_KEY=$(ARM-ACCESS-KEY) \
-e TF_WARN_OUTPUT_ERRORS=$(TF_WARN_OUTPUT_ERRORS) \
--rm $BUILD_TEST_RUN_IMAGE:$BUILD_BUILDID
fi
displayName: 'run the test harness container which deploys, validates and tears down modified terraform templates'
displayName: 'Run the test harness container which deploys, validates and tears down modified terraform templates'

Просмотреть файл

@ -13,7 +13,7 @@ data "azurerm_app_service_plan" "appsvc" {
}
resource "azurerm_app_service" "appsvc" {
name = "${element(keys(var.app_service_name), count.index)}-${terraform.workspace}"
name = "${lower(element(keys(var.app_service_name), count.index))}-${lower(terraform.workspace)}"
resource_group_name = "${data.azurerm_resource_group.appsvc.name}"
location = "${data.azurerm_resource_group.appsvc.location}"
app_service_plan_id = "${data.azurerm_app_service_plan.appsvc.id}"
@ -41,7 +41,7 @@ resource "azurerm_app_service" "appsvc" {
resource "azurerm_app_service_slot" "appsvc_staging_slot" {
name = "staging"
app_service_name = "${element(keys(var.app_service_name), count.index)}-${terraform.workspace}"
app_service_name = "${lower(element(keys(var.app_service_name), count.index))}-${lower(terraform.workspace)}"
count = "${length(keys(var.app_service_name))}"
location = "${data.azurerm_resource_group.appsvc.location}"
resource_group_name = "${data.azurerm_resource_group.appsvc.name}"
@ -55,7 +55,7 @@ resource "azurerm_template_deployment" "access_restriction" {
resource_group_name = "${data.azurerm_resource_group.appsvc.name}"
parameters = {
service_name = "${element(keys(var.app_service_name), count.index)}-${terraform.workspace}"
service_name = "${lower(element(keys(var.app_service_name), count.index))}-${lower(terraform.workspace)}"
vnet_subnet_id = "${var.vnet_subnet_id}"
access_restriction_name = "${local.access_restriction_name}"
access_restriction_description = "${local.access_restriction_description}"

Просмотреть файл

@ -44,7 +44,14 @@ DOT_ENV=<path to your .env file>
export $(cat $DOT_ENV | xargs)
```
2. Execute the following commands to set up your terraform environment
2. Execute the following command to configure your local Azure CLI. **Note**: This is a temporary measure until we are able to break the dependency on the Azure CLI. This work is being tracked as a part of [Issue 153](https://github.com/microsoft/cobalt/issues/153)
```bash
# This logs your local Azure CLI in using the configured service principal.
az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID
```
3. Execute the following commands to set up your terraform environment
```bash
# This configures terraform to leverage a remote backend that will help you and your
# team keep consistent state
@ -55,7 +62,7 @@ terraform init -backend-config "storage_account_name=${TF_VAR_remote_state_accou
terraform workspace new $USER || terraform workspace select $USER
```
3. Create a new terraform template directory and add a `main.tf` file. Here's a sample that uses the `azure-simple` template.
4. Create a new terraform template directory and add a `main.tf` file. Here's a sample that uses the `azure-simple` template.
```HCL
module "azure-simple" {
@ -66,7 +73,7 @@ module "azure-simple" {
}
```
4. Execute the following commands to orchestrate a deployment
5. Execute the following commands to orchestrate a deployment
```bash
# See what terraform will try to deploy without actually deploying
@ -74,7 +81,11 @@ terraform plan
# Execute a deployment
terraform apply
```
6. Optionally execute the following command to teardown your deployment and delete your resources
```bash
# Destroy resources and tear down deployment. Only do this if you want to destroy your deployment.
terraform destroy
```

Просмотреть файл

@ -1,6 +1,6 @@
locals {
prefix = "${var.name}-${terraform.workspace}"
prefix_short = "${substr(local.prefix, 0, 20)}"
prefix = "${lower(var.name)}-${lower(terraform.workspace)}"
prefix_short = "${format("%.20s", local.prefix)}"
tm_profile_name = "${local.prefix}-tf"
vnet_name = "${local.prefix}-vnet"
tm_endpoint_name = "${var.resource_group_location}_${var.name}"

Просмотреть файл

@ -9,3 +9,7 @@ output "app_gateway_health_probe_backend_address" {
output "tm_fqdn" {
value = "${module.traffic_manager.public_pip_fqdn}"
}
output "public_cert" {
value = "${module.keyvault_certificate.public_cert}"
}

Просмотреть файл

@ -0,0 +1,132 @@
package test
import (
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"
"os"
"strings"
"testing"
"github.com/gruntwork-io/terratest/modules/random"
"github.com/gruntwork-io/terratest/modules/shell"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/microsoft/cobalt/test-harness/infratests"
)
var name = "azsimp"
var region = "eastus"
var workspace = "azsimp-" + strings.ToLower(random.UniqueId())
var tfOptions = &terraform.Options{
TerraformDir: "../../",
Upgrade: true,
Vars: map[string]interface{}{
"name": name,
"resource_group_location": region,
},
BackendConfig: map[string]interface{}{
"storage_account_name": os.Getenv("TF_VAR_remote_state_account"),
"container_name": os.Getenv("TF_VAR_remote_state_container"),
},
}
// Validates that the front-end endpoint is available via HTTPS using SSL
func verifyHTTPSSuccessOnFrontEnd(goTest *testing.T, output infratests.TerraformOutput) {
frontEndHost := output["tm_fqdn"].(string)
httpClient := configureHTTPSClient(output)
response, err := httpClient.Get("https://" + frontEndHost)
if err != nil {
goTest.Fatal(err)
}
if response.StatusCode != 200 {
goTest.Fatal(fmt.Errorf("expected status 200 but got %d", response.StatusCode))
}
}
// Validates that the front-end endpoint is not available via HTTP
func verifyHTTPFailsOnFrontEnd(goTest *testing.T, output infratests.TerraformOutput) {
frontEndHost := output["tm_fqdn"].(string)
verifyRequestFails(goTest, frontEndHost, "https", &http.Client{})
}
// Validates that the back-end endpoint is not available via HTTPS
func verifyHTTPSFailsOnBackEnd(goTest *testing.T, output infratests.TerraformOutput) {
backEndHost := output["app_gateway_health_probe_backend_address"].(string)
verifyRequestFails(goTest, backEndHost, "https", configureHTTPSClient(output))
}
// Validates that the back-end endpoint is not available via HTTP
func verifyHTTPFailsOnBackEnd(goTest *testing.T, output infratests.TerraformOutput) {
backEndHost := output["app_gateway_health_probe_backend_address"].(string)
verifyRequestFails(goTest, backEndHost, "http", &http.Client{})
}
// Configures an HTTPS client for the web app
func configureHTTPSClient(output infratests.TerraformOutput) *http.Client {
cert := output["public_cert"].(string)
backEndHost := output["app_gateway_health_probe_backend_address"].(string)
certPool := x509.NewCertPool()
certPool.AppendCertsFromPEM([]byte("-----BEGIN CERTIFICATE-----\n" + cert + "\n-----END CERTIFICATE-----"))
return &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: certPool,
ServerName: backEndHost,
},
},
}
}
// Validates that a request fails
func verifyRequestFails(goTest *testing.T, host string, protocol string, client *http.Client) {
response, err := client.Get(protocol + "://" + host)
if err != nil || response.StatusCode != 200 {
return
}
goTest.Fatal(fmt.Errorf("expected HTTP request to fail but got status code %d", response.StatusCode))
}
func TestAzureSimple(t *testing.T) {
azureLogin(t)
testFixture := infratests.IntegrationTestFixture{
GoTest: t,
TfOptions: tfOptions,
Workspace: workspace,
ExpectedTfOutputCount: 4,
ExpectedTfOutput: infratests.TerraformOutput{
"tm_fqdn": name + "-" + workspace + "-ip-dns." + region + ".cloudapp.azure.com",
"app_gateway_health_probe_backend_address": "cobalt-backend-api-" + workspace + ".azurewebsites.net",
"app_gateway_health_probe_backend_status": "Healthy",
},
TfOutputAssertions: []infratests.TerraformOutputValidation{
verifyHTTPSSuccessOnFrontEnd,
verifyHTTPFailsOnFrontEnd,
verifyHTTPSFailsOnBackEnd,
verifyHTTPFailsOnBackEnd,
},
}
infratests.RunIntegrationTests(&testFixture)
}
func azureLogin(t *testing.T) {
shell.RunCommand(t, shell.Command{
Command: "az",
Args: []string{
"login",
"--service-principal",
"-u",
os.Getenv("ARM_CLIENT_ID"),
"-p",
os.Getenv("ARM_CLIENT_SECRET"),
"--tenant",
os.Getenv("ARM_TENANT_ID"),
},
})
}

Просмотреть файл

@ -0,0 +1,96 @@
package test
import (
"os"
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/microsoft/cobalt/test-harness/infratests"
)
var region = "eastus"
var workspace = "azsimple"
var tf_options = &terraform.Options{
TerraformDir: "../../",
Upgrade: true,
Vars: map[string]interface{}{
"resource_group_location": region,
},
BackendConfig: map[string]interface{}{
"storage_account_name": os.Getenv("TF_VAR_remote_state_account"),
"container_name": os.Getenv("TF_VAR_remote_state_container"),
},
}
func TestTemplate(t *testing.T) {
testFixture := infratests.UnitTestFixture{
GoTest: t,
TfOptions: tf_options,
Workspace: workspace,
PlanAssertions: nil,
ExpectedResourceCount: 28,
ExpectedResourceAttributeValues: infratests.ResourceDescription{
"azurerm_resource_group.svcplan": infratests.AttributeValueMapping{
"location": region,
"name": "cobalt-az-simple-" + workspace,
},
"azurerm_app_service_slot.appsvc_staging_slot": infratests.AttributeValueMapping{
"name": "staging",
},
"azurerm_app_service.appsvc": infratests.AttributeValueMapping{
"name": "cobalt-backend-api-" + workspace,
"site_config.0.linux_fx_version": "DOCKER|msftcse/cobalt-azure-simple:0.1",
},
"data.azurerm_resource_group.appgateway": infratests.AttributeValueMapping{
"name": "cobalt-az-simple-" + workspace,
},
"azurerm_application_gateway.appgateway": infratests.AttributeValueMapping{
"authentication_certificate.0.name": "gateway-public-key",
"frontend_port.0.port": "443",
"http_listener.0.protocol": "Https",
"backend_http_settings.0.port": "443",
"backend_http_settings.0.protocol": "Https",
"probe.0.protocol": "Https",
"probe.0.timeout": "30",
"probe.0.unhealthy_threshold": "3",
"sku.0.capacity": "2",
"sku.0.name": "WAF_Medium",
"sku.0.tier": "WAF",
},
"data.azurerm_resource_group.appinsights": infratests.AttributeValueMapping{
"name": "cobalt-az-simple-" + workspace,
},
"azurerm_monitor_autoscale_setting.app_service_auto_scale": infratests.AttributeValueMapping{
"enabled": "true",
"name": "cobalt-az-simple-" + workspace + "-sp-autoscale",
"notification.0.email.0.send_to_subscription_administrator": "true",
"notification.0.email.0.send_to_subscription_co_administrator": "true",
"profile.0.rule.0.metric_trigger.0.metric_name": "CpuPercentage",
"profile.0.rule.0.metric_trigger.0.operator": "GreaterThan",
"profile.0.rule.0.metric_trigger.0.statistic": "Average",
"profile.0.rule.0.metric_trigger.0.threshold": "70",
"profile.0.rule.0.metric_trigger.0.time_aggregation": "Average",
"profile.0.rule.0.metric_trigger.0.time_grain": "PT1M",
"profile.0.rule.0.metric_trigger.0.time_window": "PT5M",
"profile.0.rule.0.scale_action.0.cooldown": "PT10M",
"profile.0.rule.0.scale_action.0.direction": "Increase",
"profile.0.rule.0.scale_action.0.type": "ChangeCount",
"profile.0.rule.0.scale_action.0.value": "1",
"profile.0.rule.1.metric_trigger.0.metric_name": "CpuPercentage",
"profile.0.rule.1.metric_trigger.0.operator": "GreaterThan",
"profile.0.rule.1.metric_trigger.0.statistic": "Average",
"profile.0.rule.1.metric_trigger.0.threshold": "25",
"profile.0.rule.1.metric_trigger.0.time_aggregation": "Average",
"profile.0.rule.1.metric_trigger.0.time_grain": "PT1M",
"profile.0.rule.1.metric_trigger.0.time_window": "PT5M",
"profile.0.rule.1.scale_action.0.cooldown": "PT1M",
"profile.0.rule.1.scale_action.0.direction": "Decrease",
"profile.0.rule.1.scale_action.0.type": "ChangeCount",
"profile.0.rule.1.scale_action.0.value": "1",
},
},
}
infratests.RunUnitTests(&testFixture)
}