Skip to content

Commit

Permalink
Merge pull request #46 from Azure/e-36
Browse files Browse the repository at this point in the history
Add support for `init_container`
  • Loading branch information
jiaweitao001 authored Dec 25, 2023
2 parents 3118da2 + 8cd05d5 commit 448c44d
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ No modules.
| <a name="input_container_app_environment_name"></a> [container\_app\_environment\_name](#input\_container\_app\_environment\_name) | (Required) The name of the container apps managed environment. Changing this forces a new resource to be created. | `string` | n/a | yes |
| <a name="input_container_app_environment_tags"></a> [container\_app\_environment\_tags](#input\_container\_app\_environment\_tags) | A map of the tags to use on the resources that are deployed with this module. | `map(string)` | `{}` | no |
| <a name="input_container_app_secrets"></a> [container\_app\_secrets](#input\_container\_app\_secrets) | (Optional) The secrets of the container apps. The key of the map should be aligned with the corresponding container app. | <pre>map(list(object({<br> name = string<br> value = string<br> })))</pre> | `{}` | no |
| <a name="input_container_apps"></a> [container\_apps](#input\_container\_apps) | The container apps to deploy. | <pre>map(object({<br> name = string<br> tags = optional(map(string))<br> revision_mode = string<br> workload_profile_name = optional(string)<br><br> template = object({<br> containers = set(object({<br> name = string<br> image = string<br> args = optional(list(string))<br> command = optional(list(string))<br> cpu = string<br> memory = string<br> env = optional(set(object({<br> name = string<br> secret_name = optional(string)<br> value = optional(string)<br> })))<br> liveness_probe = optional(object({<br> failure_count_threshold = optional(number)<br> header = optional(object({<br> name = string<br> value = string<br> }))<br> host = optional(string)<br> initial_delay = optional(number, 1)<br> interval_seconds = optional(number, 10)<br> path = optional(string)<br> port = number<br> timeout = optional(number, 1)<br> transport = string<br> }))<br> readiness_probe = optional(object({<br> failure_count_threshold = optional(number)<br> header = optional(object({<br> name = string<br> value = string<br> }))<br> host = optional(string)<br> interval_seconds = optional(number, 10)<br> path = optional(string)<br> port = number<br> success_count_threshold = optional(number, 3)<br> timeout = optional(number)<br> transport = string<br> }))<br> startup_probe = optional(object({<br> failure_count_threshold = optional(number)<br> header = optional(object({<br> name = string<br> value = string<br> }))<br> host = optional(string)<br> interval_seconds = optional(number, 10)<br> path = optional(string)<br> port = number<br> timeout = optional(number)<br> transport = string<br> }))<br> volume_mounts = optional(object({<br> name = string<br> path = string<br> }))<br> }))<br> max_replicas = optional(number)<br> min_replicas = optional(number)<br> revision_suffix = optional(string)<br><br> volume = optional(set(object({<br> name = string<br> storage_name = optional(string)<br> storage_type = optional(string)<br> })))<br> })<br><br> ingress = optional(object({<br> allow_insecure_connections = optional(bool, false)<br> external_enabled = optional(bool, false)<br> target_port = number<br> transport = optional(string)<br> traffic_weight = object({<br> label = optional(string)<br> latest_revision = optional(string)<br> revision_suffix = optional(string)<br> percentage = number<br> })<br> }))<br><br> identity = optional(object({<br> type = string<br> identity_ids = optional(list(string))<br> }))<br><br> dapr = optional(object({<br> app_id = string<br> app_port = number<br> app_protocol = optional(string)<br> }))<br><br> registry = optional(list(object({<br> server = string<br> username = optional(string)<br> password_secret_name = optional(string)<br> identity = optional(string)<br> })))<br> }))</pre> | n/a | yes |
| <a name="input_container_apps"></a> [container\_apps](#input\_container\_apps) | The container apps to deploy. | <pre>map(object({<br> name = string<br> tags = optional(map(string))<br> revision_mode = string<br> workload_profile_name = optional(string)<br><br> template = object({<br> init_containers = optional(set(object({<br> args = optional(list(string))<br> command = optional(list(string))<br> cpu = optional(number)<br> image = string<br> name = string<br> memory = optional(string)<br> env = optional(list(object({<br> name = string<br> secret_name = optional(string)<br> value = optional(string)<br> })))<br> volume_mounts = optional(list(object({<br> name = string<br> path = string<br> })))<br> })), [])<br> containers = set(object({<br> name = string<br> image = string<br> args = optional(list(string))<br> command = optional(list(string))<br> cpu = string<br> memory = string<br> env = optional(set(object({<br> name = string<br> secret_name = optional(string)<br> value = optional(string)<br> })))<br> liveness_probe = optional(object({<br> failure_count_threshold = optional(number)<br> header = optional(object({<br> name = string<br> value = string<br> }))<br> host = optional(string)<br> initial_delay = optional(number, 1)<br> interval_seconds = optional(number, 10)<br> path = optional(string)<br> port = number<br> timeout = optional(number, 1)<br> transport = string<br> }))<br> readiness_probe = optional(object({<br> failure_count_threshold = optional(number)<br> header = optional(object({<br> name = string<br> value = string<br> }))<br> host = optional(string)<br> interval_seconds = optional(number, 10)<br> path = optional(string)<br> port = number<br> success_count_threshold = optional(number, 3)<br> timeout = optional(number)<br> transport = string<br> }))<br> startup_probe = optional(object({<br> failure_count_threshold = optional(number)<br> header = optional(object({<br> name = string<br> value = string<br> }))<br> host = optional(string)<br> interval_seconds = optional(number, 10)<br> path = optional(string)<br> port = number<br> timeout = optional(number)<br> transport = string<br> }))<br> volume_mounts = optional(object({<br> name = string<br> path = string<br> }))<br> }))<br> max_replicas = optional(number)<br> min_replicas = optional(number)<br> revision_suffix = optional(string)<br><br> volume = optional(set(object({<br> name = string<br> storage_name = optional(string)<br> storage_type = optional(string)<br> })))<br> })<br><br> ingress = optional(object({<br> allow_insecure_connections = optional(bool, false)<br> external_enabled = optional(bool, false)<br> target_port = number<br> transport = optional(string)<br> traffic_weight = object({<br> label = optional(string)<br> latest_revision = optional(string)<br> revision_suffix = optional(string)<br> percentage = number<br> })<br> }))<br><br> identity = optional(object({<br> type = string<br> identity_ids = optional(list(string))<br> }))<br><br> dapr = optional(object({<br> app_id = string<br> app_port = number<br> app_protocol = optional(string)<br> }))<br><br> registry = optional(list(object({<br> server = string<br> username = optional(string)<br> password_secret_name = optional(string)<br> identity = optional(string)<br> })))<br> }))</pre> | n/a | yes |
| <a name="input_dapr_component"></a> [dapr\_component](#input\_dapr\_component) | (Optional) The Dapr component to deploy. | <pre>map(object({<br> name = string<br> component_type = string<br> version = string<br> ignore_errors = optional(bool, false)<br> init_timeout = optional(string, "5s")<br> scopes = optional(list(string))<br> metadata = optional(set(object({<br> name = string<br> secret_name = optional(string)<br> value = string<br> })))<br> }))</pre> | `{}` | no |
| <a name="input_dapr_component_secrets"></a> [dapr\_component\_secrets](#input\_dapr\_component\_secrets) | (Optional) The secrets of the Dapr components. The key of the map should be aligned with the corresponding Dapr component. | <pre>map(list(object({<br> name = string<br> value = string<br> })))</pre> | `{}` | no |
| <a name="input_env_storage"></a> [env\_storage](#input\_env\_storage) | (Optional) Manages a Container App Environment Storage, writing files to this file share to make data accessible by other systems. | <pre>map(object({<br> name = string<br> account_name = string<br> share_name = string<br> access_mode = string<br> }))</pre> | `{}` | no |
Expand Down
84 changes: 84 additions & 0 deletions examples/init-container/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
resource "random_id" "rg_name" {
byte_length = 8
}

resource "random_id" "env_name" {
byte_length = 8
}

resource "random_id" "container_name" {
byte_length = 4
}

resource "azurerm_resource_group" "test" {
location = var.location
name = "example-container-app-${random_id.rg_name.hex}-init-container"
}

module "container_apps" {
source = "../.."
resource_group_name = azurerm_resource_group.test.name
location = var.location
container_app_environment_name = "example-env-${random_id.env_name.hex}"

container_apps = {
example = {
name = "example"
revision_mode = "Single"

template = {
init_containers = [
{
name = "debian"
image = "debian:latest"
memory = "0.5Gi"
cpu = 0.25
command = [
"/bin/sh",
]
args = [
"-c", "echo Hello from the debian container > /shared/index.html"
]
volume_mounts = [
{
name = "shared"
path = "/shared"
}
]
}
],
containers = [
{
name = "nginx"
image = "nginx:latest"
memory = "1Gi"
cpu = 0.5
volume_mounts = {
name = "shared"
path = "/usr/share/nginx/html"
}
}
],
volume = [
{
name = "shared"
storage_type = "EmptyDir"
}
]
}


ingress = {
allow_insecure_connections = false
target_port = 80
external_enabled = true

traffic_weight = {
latest_revision = true
percentage = 100
}
}
},
}
log_analytics_workspace_name = "container-app-module-lawn-${random_id.container_name.hex}"
}
3 changes: 3 additions & 0 deletions examples/init-container/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "url" {
value = module.container_apps.container_app_fqdn["example"]
}
3 changes: 3 additions & 0 deletions examples/init-container/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
variable "location" {
default = "eastus"
}
18 changes: 18 additions & 0 deletions examples/init-container/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
terraform {
required_version = ">= 1.2"

required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">= 3.85, < 4.0"
}
random = {
source = "hashicorp/random"
version = ">= 3.0.0"
}
}
}

provider "azurerm" {
features {}
}
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github.com/Azure/terraform-module-test-helper v0.7.1/go.mod h1:7DojWQTd4S94vojNvnidodLskyZDHaDuOwnjKl27sH4=
28 changes: 28 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,34 @@ resource "azurerm_container_app" "container_app" {
}
}
}
dynamic "init_container" {
for_each = each.value.template.init_containers == null ? [] : each.value.template.init_containers

content {
image = init_container.value.image
name = init_container.value.name
args = init_container.value.args
command = init_container.value.command
cpu = init_container.value.cpu
memory = init_container.value.memory

dynamic "env" {
for_each = init_container.value.env == null ? [] : init_container.value.env
content {
name = env.value.name
secret_name = env.value.secret_name
value = env.value.value
}
}
dynamic "volume_mounts" {
for_each = init_container.value.volume_mounts == null ? [] : init_container.value.volume_mounts
content {
name = volume_mounts.value.name
path = volume_mounts.value.path
}
}
}
}
dynamic "volume" {
for_each = each.value.template.volume == null ? [] : each.value.template.volume

Expand Down
16 changes: 16 additions & 0 deletions test/e2e/terraform_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
)

func TestExamplesDapr(t *testing.T) {
t.Parallel()
// For dapr example
var vars map[string]any
managedIdentityId := os.Getenv("MSI_ID")
Expand All @@ -29,6 +30,7 @@ func TestExamplesDapr(t *testing.T) {
}

func TestExamplesStartup(t *testing.T) {
t.Parallel()
vars := make(map[string]interface{})

test_helper.RunE2ETest(t, "../../", "examples/startup", terraform.Options{
Expand All @@ -38,6 +40,7 @@ func TestExamplesStartup(t *testing.T) {
}

func TestExampleAcr(t *testing.T) {
t.Parallel()
test_helper.RunE2ETest(t, "../..", "examples/acr", terraform.Options{
Upgrade: true,
}, func(t *testing.T, output test_helper.TerraformOutput) {
Expand All @@ -50,6 +53,19 @@ func TestExampleAcr(t *testing.T) {
})
}

func TestInitContainer(t *testing.T) {
t.Parallel()
test_helper.RunE2ETest(t, "../..", "examples/init-container", terraform.Options{
Upgrade: true,
}, func(t *testing.T, output test_helper.TerraformOutput) {
url, ok := output["url"].(string)
require.True(t, ok)
html, err := getHTML(url)
require.NoError(t, err)
assert.Contains(t, html, "Hello from the debian container")
})
}

func getHTML(url string) (string, error) {
resp, err := http.Get(url) // #nosec G107
if err != nil {
Expand Down
17 changes: 17 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,23 @@ variable "container_apps" {
workload_profile_name = optional(string)

template = object({
init_containers = optional(set(object({
args = optional(list(string))
command = optional(list(string))
cpu = optional(number)
image = string
name = string
memory = optional(string)
env = optional(list(object({
name = string
secret_name = optional(string)
value = optional(string)
})))
volume_mounts = optional(list(object({
name = string
path = string
})))
})), [])
containers = set(object({
name = string
image = string
Expand Down

0 comments on commit 448c44d

Please sign in to comment.