Skip to content

Commit

Permalink
feat: adding Azure Container Instances module (#593)
Browse files Browse the repository at this point in the history
* adding Azure Container Instances module

* Update Azure ACI example

* Update ACI example readme

* Update Containers module to support ACI

* remove unused aci.go helpers

* Add ACI helpers unit tests

* Update ACI sample test

Co-authored-by: Hadwa Abdelhalem <[email protected]>
  • Loading branch information
polatengin and HadwaAbdelhalem authored Aug 11, 2021
1 parent c0bf11e commit 850b50f
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 1 deletion.
34 changes: 34 additions & 0 deletions examples/azure/terraform-azure-aci-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Terraform Azure Example

This folder contains a Terraform module that deploys resources in [Azure](https://azure.microsoft.com/) to demonstrate
how you can use Terratest to write automated tests for your Azure Terraform code. This module deploys an [Azure Container Instance](https://azure.microsoft.com/en-us/services/container-instances/).

Check out [test/azure/terraform_azure_aci_example_test.go](/test/azure/terraform_azure_aci_example_test.go) to see how you can write
automated tests for this module.

**WARNING**: This module and the automated tests for it deploy real resources into your Azure account which can cost you
money. The resources are all part of the [Azure Free Account](https://azure.microsoft.com/en-us/free/), so if you haven't used that up,
it should be free, but you are completely responsible for all Azure charges.

## Running this module manually

1. Sign up for [Azure](https://azure.microsoft.com/)
1. Configure your Azure credentials using one of the [supported methods for Azure CLI
tools](https://docs.microsoft.com/cli/azure/azure-cli-configuration?view=azure-cli-latest)
1. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH`
1. Ensure [environment variables](../README.md#review-environment-variables) are available
1. Run `terraform init`
1. Run `terraform apply`
1. When you're done, run `terraform destroy`

## Running automated tests against this module

1. Sign up for [Azure](https://azure.microsoft.com/)
1. Configure your Azure credentials using one of the [supported methods for Azure CLI
tools](https://docs.microsoft.com/en-us/cli/azure/azure-cli-configuration?view=azure-cli-latest)
1. Install [Terraform](https://www.terraform.io/) and make sure it's on your `PATH`
1. Configure your Terratest [Go test environment](../README.md)
1. `cd test/azure`
1. `go build terraform_azure_aci_example_test.go`
1. `go test -v -timeout 60m -tags azure -run TestTerraformAzureACIExample`

53 changes: 53 additions & 0 deletions examples/azure/terraform-azure-aci-example/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# ---------------------------------------------------------------------------------------------------------------------
# DEPLOY AN AZURE CONTAINER Instance
# This is an example of how to deploy an Azure Container Instance
# See test/terraform_azure_aci_example_test.go for how to write automated tests for this code.
# ---------------------------------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# CONFIGURE OUR AZURE CONNECTION
# ------------------------------------------------------------------------------

provider "azurerm" {
version = "~>2.29.0"
features {}
}

# ---------------------------------------------------------------------------------------------------------------------
# DEPLOY A RESOURCE GROUP
# ---------------------------------------------------------------------------------------------------------------------

resource "azurerm_resource_group" "rg" {
name = "terratest-aci-rg-${var.postfix}"
location = var.location
}

# ---------------------------------------------------------------------------------------------------------------------
# DEPLOY AN AZURE CONTAINER INSTANCE
# ---------------------------------------------------------------------------------------------------------------------

resource "azurerm_container_group" "aci" {
name = "aci${var.postfix}"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name

ip_address_type = "public"
dns_name_label = "aci${var.postfix}"
os_type = "Linux"

container {
name = "hello-world"
image = "microsoft/aci-helloworld:latest"
cpu = "0.5"
memory = "1.5"

ports {
port = 443
protocol = "TCP"
}
}

tags = {
Environment = "Development"
}
}
15 changes: 15 additions & 0 deletions examples/azure/terraform-azure-aci-example/output.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
output "resource_group_name" {
value = azurerm_resource_group.rg.name
}

output "ip_address" {
value = azurerm_container_group.aci.ip_address
}

output "fqdn" {
value = azurerm_container_group.aci.fqdn
}

output "container_instance_name" {
value = azurerm_container_group.aci.name
}
31 changes: 31 additions & 0 deletions examples/azure/terraform-azure-aci-example/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# ---------------------------------------------------------------------------------------------------------------------
# ENVIRONMENT VARIABLES
# Define these secrets as environment variables
# ---------------------------------------------------------------------------------------------------------------------

# ARM_CLIENT_ID
# ARM_CLIENT_SECRET
# ARM_SUBSCRIPTION_ID
# ARM_TENANT_ID

# ---------------------------------------------------------------------------------------------------------------------
# REQUIRED PARAMETERS
# You must provide a value for each of these parameters.
# ---------------------------------------------------------------------------------------------------------------------

# ---------------------------------------------------------------------------------------------------------------------
# OPTIONAL PARAMETERS
# These parameters have reasonable defaults.
# ---------------------------------------------------------------------------------------------------------------------

variable "location" {
description = "The supported azure location where the resource exists"
type = string
default = "West US2"
}

variable "postfix" {
description = "A postfix string to centrally mitigate resource name collisions."
type = string
default = "1276"
}
21 changes: 21 additions & 0 deletions modules/azure/client_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/Azure/azure-sdk-for-go/profiles/preview/cosmos-db/mgmt/documentdb"
"github.com/Azure/azure-sdk-for-go/profiles/preview/preview/monitor/mgmt/insights"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute"
"github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2018-10-01/containerinstance"
"github.com/Azure/azure-sdk-for-go/services/containerregistry/mgmt/2019-05-01/containerregistry"
"github.com/Azure/azure-sdk-for-go/services/containerservice/mgmt/2019-11-01/containerservice"
kvmng "github.com/Azure/azure-sdk-for-go/services/keyvault/mgmt/2016-10-01/keyvault"
Expand Down Expand Up @@ -686,6 +687,26 @@ func CreateContainerRegistryClientE(subscriptionID string) (*containerregistry.R
return &registryClient, nil
}

// CreateContainerInstanceClientE returns an ACI client instance configured with the
// correct BaseURI depending on the Azure environment that is currently setup (or "Public", if none is setup).
func CreateContainerInstanceClientE(subscriptionID string) (*containerinstance.ContainerGroupsClient, error) {
// Validate Azure subscription ID
subscriptionID, err := getTargetAzureSubscription(subscriptionID)
if err != nil {
return nil, err
}

// Lookup environment URI
baseURI, err := getEnvironmentEndpointE(ResourceManagerEndpointName)
if err != nil {
return nil, err
}

// create client
instanceClient := containerinstance.NewContainerGroupsClientWithBaseURI(baseURI, subscriptionID)
return &instanceClient, nil
}

// GetKeyVaultURISuffixE returns the proper KeyVault URI suffix for the configured Azure environment.
// This function would fail the test if there is an error.
func GetKeyVaultURISuffixE() (string, error) {
Expand Down
74 changes: 73 additions & 1 deletion modules/azure/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"context"
"testing"

"github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2018-10-01/containerinstance"
"github.com/Azure/azure-sdk-for-go/services/containerregistry/mgmt/2019-05-01/containerregistry"

"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -61,7 +63,7 @@ func GetContainerRegistryE(registryName string, resGroupName string, subscriptio

// GetContainerRegistryClientE is a helper function that will setup an Azure Container Registry client on your behalf
func GetContainerRegistryClientE(subscriptionID string) (*containerregistry.RegistriesClient, error) {
// Create an Apps client
// Create an ACR client
registryClient, err := CreateContainerRegistryClientE(subscriptionID)
if err != nil {
return nil, err
Expand All @@ -77,3 +79,73 @@ func GetContainerRegistryClientE(subscriptionID string) (*containerregistry.Regi
registryClient.Authorizer = *authorizer
return registryClient, nil
}

// ContainerInstanceExists indicates whether the specified container instance exists.
// This function would fail the test if there is an error.
func ContainerInstanceExists(t *testing.T, instanceName string, resourceGroupName string, subscriptionID string) bool {
exists, err := ContainerInstanceExistsE(instanceName, resourceGroupName, subscriptionID)
require.NoError(t, err)

return exists
}

// ContainerInstanceExistsE indicates whether the specified container instance exists.
func ContainerInstanceExistsE(instanceName string, resourceGroupName string, subscriptionID string) (bool, error) {
_, err := GetContainerInstanceE(instanceName, resourceGroupName, subscriptionID)
if err != nil {
if ResourceNotFoundErrorExists(err) {
return false, nil
}
return false, err
}
return true, nil
}

// GetContainerInstance gets the container instance object
// This function would fail the test if there is an error.
func GetContainerInstance(t *testing.T, instanceName string, resGroupName string, subscriptionID string) *containerinstance.ContainerGroup {
instance, err := GetContainerInstanceE(instanceName, resGroupName, subscriptionID)

require.NoError(t, err)

return instance
}

// GetContainerInstanceE gets the container instance object
func GetContainerInstanceE(instanceName string, resGroupName string, subscriptionID string) (*containerinstance.ContainerGroup, error) {
rgName, err := getTargetAzureResourceGroupName(resGroupName)
if err != nil {
return nil, err
}

client, err := GetContainerInstanceClientE(subscriptionID)
if err != nil {
return nil, err
}

instance, err := client.Get(context.Background(), rgName, instanceName)
if err != nil {
return nil, err
}

return &instance, nil
}

// GetContainerInstanceClientE is a helper function that will setup an Azure Container Instance client on your behalf
func GetContainerInstanceClientE(subscriptionID string) (*containerinstance.ContainerGroupsClient, error) {
// Create an ACI client
instanceClient, err := CreateContainerInstanceClientE(subscriptionID)
if err != nil {
return nil, err
}

// Create an authorizer
authorizer, err := NewAuthorizer()
if err != nil {
return nil, err
}

// Attach authorizer to the client
instanceClient.Authorizer = *authorizer
return instanceClient, nil
}
31 changes: 31 additions & 0 deletions modules/azure/containers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,34 @@ func TestGetContainerRegistryClientE(t *testing.T) {
_, err := GetContainerRegistryClientE(subscriptionID)
require.NoError(t, err)
}

func TestContainerInstanceExistsE(t *testing.T) {
t.Parallel()

resGroupName := ""
instanceName := ""
subscriptionID := ""

_, err := ContainerInstanceExistsE(instanceName, resGroupName, subscriptionID)
require.Error(t, err)
}

func TestGetContainerInstanceE(t *testing.T) {
t.Parallel()

resGroupName := ""
instanceName := ""
subscriptionID := ""

_, err := GetContainerInstanceE(instanceName, resGroupName, subscriptionID)
require.Error(t, err)
}

func TestGetContainerInstanceClientE(t *testing.T) {
t.Parallel()

subscriptionID := ""

_, err := GetContainerInstanceClientE(subscriptionID)
require.NoError(t, err)
}
51 changes: 51 additions & 0 deletions test/azure/terraform_azure_aci_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// +build azure

// NOTE: We use build tags to differentiate azure testing because we currently do not have azure access setup for
// CircleCI.

package test

import (
"strings"

"testing"

"github.com/gruntwork-io/terratest/modules/azure"
"github.com/gruntwork-io/terratest/modules/random"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)

func TestTerraformAzureACIExample(t *testing.T) {
t.Parallel()

uniquePostfix := strings.ToLower(random.UniqueId())

// website::tag::1:: Configure Terraform setting up a path to Terraform code.
terraformOptions := &terraform.Options{
TerraformDir: "../../examples/azure/terraform-azure-aci-example",
Vars: map[string]interface{}{
"postfix": uniquePostfix,
},
}

// website::tag::5:: At the end of the test, run `terraform destroy` to clean up any resources that were created
defer terraform.Destroy(t, terraformOptions)

// website::tag::2:: Run `terraform init` and `terraform apply`. Fail the test if there are any errors.
terraform.InitAndApply(t, terraformOptions)

// website::tag::3:: Run `terraform output` to get the values of output variables
resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name")
aciName := terraform.Output(t, terraformOptions, "container_instance_name")
ipAddress := terraform.Output(t, terraformOptions, "ip_address")
fqdn := terraform.Output(t, terraformOptions, "fqdn")

// website::tag::4:: Assert
assert.True(t, azure.ContainerInstanceExists(t, aciName, resourceGroupName, ""))

actualInstance := azure.GetContainerInstance(t, aciName, resourceGroupName, "")

assert.Equal(t, ipAddress, *actualInstance.ContainerGroupProperties.IPAddress.IP)
assert.Equal(t, fqdn, *actualInstance.ContainerGroupProperties.IPAddress.Fqdn)
}

0 comments on commit 850b50f

Please sign in to comment.