From 5612032b586db563540052106873a57409c79fe3 Mon Sep 17 00:00:00 2001 From: ctxch <144136473+ctxch@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:39:58 +0100 Subject: [PATCH] Add Container Instance Terraform module (#8) --- .gitignore | 13 ++---- README.md | 42 +++++++++--------- main.tf | 122 ++++++++++++++++++++++++--------------------------- providers.tf | 14 +++--- schema.yml | 81 ++++++++++++++++++++++++++++++++++ variables.tf | 105 +++++++++++++++++++++++++++++++++++++++++--- 6 files changed, 266 insertions(+), 111 deletions(-) create mode 100644 schema.yml diff --git a/.gitignore b/.gitignore index f593722..227649f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,8 @@ crash.log crash.*.log # Exclude all .tfvars files, which are likely to contain sensitive data, such as -# password, private keys, and other secrets. These should not be part of version -# control as they are data points which are potentially sensitive and subject +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject # to change depending on the environment. *.tfvars *.tfvars.json @@ -32,11 +32,4 @@ override.tf.json # Ignore CLI configuration files .terraformrc terraform.rc -/.idea/.gitignore -/acf_resource_container_instance.iml -/.idea/checkstyle-idea.xml -/.idea/google-java-format.xml -/.idea/jpa-buddy.xml -/.idea/misc.xml -/.idea/modules.xml -/.idea/vcs.xml +.idea/ diff --git a/README.md b/README.md index cc8a886..bad6c6a 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,21 @@ -# Terraform OCI Container Instance +[![Deploy to Oracle Cloud](https://oci-resourcemanager-plugin.plugins.oci.oraclecloud.com/latest/deploy-to-oracle-cloud.svg)](https://cloud.oracle.com/resourcemanager/stacks/create?zipUrl=https://github.com/avaloqcloud/terraform-oci-container-instance/archive/refs/tags/v0.1.0.zip) -The code provides a reusuable Terraform module that provisions -simple container instance on Oracle Cloud Infrastructure. - -## Usage - module "container_instance" { - source = "../terraform-oci-container-instance" - - compartment_ocid = var.compartment_ocid - subnet_id = var.subnet_id - container_instance = var.container_instance - image_pull_secrets = var.image_pull_secrets - } +## Terraform OCI Container Instance +The code provides a reusable Terraform module that provisions a container instance on Oracle Cloud Infrastructure. + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.0.0 | -| [oci](#requirement\_oci) | >= 4.101.0 | +| [terraform](#requirement\_terraform) | ~> 1.2 | +| [oci](#requirement\_oci) | 5.18.0 | ## Providers | Name | Version | |------|---------| -| [oci](#provider\_oci) | >= 4.101.0 | +| [oci](#provider\_oci) | 5.18.0 | ## Modules @@ -34,18 +25,25 @@ No modules. | Name | Type | |------|------| -| [oci_container_instances_container_instance.container_instance](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/container_instances_container_instance) | resource | -| [oci_identity_availability_domains.local_ads](https://registry.terraform.io/providers/oracle/oci/latest/docs/data-sources/identity_availability_domains) | data source | +| [oci_container_instances_container_instance.container_instance](https://registry.terraform.io/providers/oracle/oci/5.18.0/docs/resources/container_instances_container_instance) | resource | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [compartment\_ocid](#input\_compartment\_ocid) | n/a | `string` | `null` | no | -| [container\_instance](#input\_container\_instance) | n/a | `any` | n/a | yes | -| [image\_pull\_secrets](#input\_image\_pull\_secrets) | n/a | `map(any)` | `null` | no | -| [subnet\_id](#input\_subnet\_id) | n/a | `string` | `null` | no | +| [availability\_domain](#input\_availability\_domain) | The availability domain where the container instance runs. | `string` | n/a | yes | +| [compartment\_ocid](#input\_compartment\_ocid) | The OCID of the compartment. | `string` | n/a | yes | +| [container\_restart\_policy](#input\_container\_restart\_policy) | The container restart policy is applied for all containers in container instance. | `string` | `"ALWAYS"` | no | +| [containers](#input\_containers) | The containers to create on this container instance. |
list(object({
display_name = string
image_url = string
environment_variables = optional(map(string))

command = optional(list(string))
arguments = optional(list(string))

volume_mounts = optional(list(object({
volume_name = string
mount_path = string
})))

resource_config = optional(map(object({
memory_limit_in_gbs = optional(number)
vcpus_limit = optional(number)
})))

memory_limit_in_gbs = optional(number)
vcpus_limit = optional(number)

working_directory = optional(string)
}))
| n/a | yes | +| [display\_name](#input\_display\_name) | A user-friendly name. Does not have to be unique, and it's changeable. Avoid entering confidential information. If you don't provide a name, a name is generated automatically. | `string` | n/a | yes | +| [image\_pull\_secrets](#input\_image\_pull\_secrets) | The image pulls secrets so you can access private registry to pull container images. |
list(map(object({
registry_endpoint = string
secret_type = string
secret_id = optional(string)
username = optional(string)
password = optional(string)
})))
| `[]` | no | +| [memory\_in\_gbs](#input\_memory\_in\_gbs) | The total amount of memory available to the container instance, in gigabytes. | `number` | n/a | yes | +| [ocpus](#input\_ocpus) | The total number of OCPUs available to the container instance. | `number` | n/a | yes | +| [shape](#input\_shape) | The shape of the container instance. The shape determines the resources available to the container instance. | `string` | n/a | yes | +| [subnet\_id](#input\_subnet\_id) | The OCID of the subnet to create the VNIC in. | `string` | n/a | yes | +| [volumes](#input\_volumes) | A volume is a directory with data that is accessible across multiple containers in a container instance. |
list(object({
name = string
volume_type = string
backing_store = optional(string)

configs = optional(list(object({
data = optional(string)
file_name = optional(string)
})))
}))
| `[]` | no | ## Outputs No outputs. + diff --git a/main.tf b/main.tf index b727f9a..29942ae 100644 --- a/main.tf +++ b/main.tf @@ -1,82 +1,74 @@ -data "oci_identity_availability_domains" "local_ads" { - compartment_id = var.compartment_ocid -} - - resource "oci_container_instances_container_instance" "container_instance" { - count = length(var.container_instance) - - availability_domain = data.oci_identity_availability_domains.local_ads.availability_domains.0.name - compartment_id = var.compartment_ocid - display_name = var.container_instance[count.index]["container_name"] - container_restart_policy = "ALWAYS" - shape = var.container_instance[count.index]["shape"] - shape_config { - memory_in_gbs = var.container_instance[count.index]["mem"] - ocpus = var.container_instance[count.index]["cpu"] - } + availability_domain = var.availability_domain + compartment_id = var.compartment_ocid - vnics { - subnet_id = var.subnet_id - is_public_ip_assigned = false - } + display_name = var.display_name + container_restart_policy = var.container_restart_policy + shape = var.shape + shape_config { + memory_in_gbs = var.memory_in_gbs + ocpus = var.ocpus + } - containers { - image_url = var.container_instance[count.index]["container_image_url"] - display_name = var.container_instance[count.index]["container_name"] + vnics { + subnet_id = var.subnet_id + is_public_ip_assigned = false + hostname_label = var.display_name + } - environment_variables = var.container_instance[count.index]["env_variables"] + dynamic "containers" { + for_each = var.containers + content { + display_name = containers.value.display_name + image_url = containers.value.image_url + environment_variables = try(containers.value.environment_variables, null) - command = try(var.container_instance[count.index]["command"], null) - arguments = try(var.container_instance[count.index]["arguments"], null) + command = try(containers.value.command, null) + arguments = try(containers.value.arguments, null) - dynamic "volume_mounts" { - for_each = try(var.container_instance[count.index]["volumes"], {}) - content { - volume_name = volume_mounts.key - mount_path = volume_mounts.value.path - } - } + dynamic "volume_mounts" { + for_each = containers.value.volume_mounts == null ? [] : containers.value.volume_mounts + content { + volume_name = volume_mounts.value.volume_name + mount_path = volume_mounts.value.mount_path + } + } - resource_config { - memory_limit_in_gbs = try(var.container_instance[count.index]["memory_limit"], null) - } - - security_context { - run_as_group = try(var.container_instance[count.index]["run_as_group"], null) - run_as_user = try(var.container_instance[count.index]["run_as_user"], null) - security_context_type = "LINUX" + resource_config { + memory_limit_in_gbs = try(containers.value.memory_limit_in_gbs, null) + vcpus_limit = try(containers.value.vcpus_limit, null) + } + + working_directory = try(containers.value.working_directory, null) + } } - working_directory = try(var.container_instance[count.index]["working_directory"], null) - } - - dynamic "image_pull_secrets" { - for_each = try(var.image_pull_secrets, {}) - content { - registry_endpoint = image_pull_secrets.value.registry_endpoint - secret_type = image_pull_secrets.value.secret_type + dynamic "volumes" { + for_each = var.volumes + content { + name = volumes.value.name + volume_type = volumes.value.volume_type - secret_id = try(image_pull_secrets.value.secret_id, null) - username = base64encode(try(image_pull_secrets.value.username, null)) - password = base64encode(try(image_pull_secrets.value.password, null)) + dynamic "configs" { + for_each = try(volumes.value.configs, []) + content { + data = configs.value.data + file_name = configs.value.file_name + } + } + } } - } - dynamic "volumes" { - for_each = try(var.container_instance[count.index]["volumes"], {}) - content { - name = volumes.key - volume_type = volumes.value.volume_type - backing_store = try(volumes.value.backing_store, null) - dynamic "configs" { - for_each = try(volumes.value.configs, {}) + dynamic "image_pull_secrets" { + for_each = var.image_pull_secrets content { - data = try(configs.value, null) - file_name = try(configs.key, null) + registry_endpoint = image_pull_secrets.value.registry_endpoint + secret_type = image_pull_secrets.value.secret_type + + secret_id = try(image_pull_secrets.value.secret_id, null) + username = base64encode(try(image_pull_secrets.value.username, null)) + password = base64encode(try(image_pull_secrets.value.password, null)) } - } } - } } diff --git a/providers.tf b/providers.tf index 7323039..ee3f7e1 100644 --- a/providers.tf +++ b/providers.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.0.0" - required_providers { - oci = { - source = "oracle/oci" - version = ">= 4.101.0" + experiments = [module_variable_optional_attrs] + required_version = "~> 1.2" + required_providers { + oci = { + source = "oracle/oci" + version = "5.18.0" + } } - } } - diff --git a/schema.yml b/schema.yml new file mode 100644 index 0000000..1308c56 --- /dev/null +++ b/schema.yml @@ -0,0 +1,81 @@ +title: "Container Instance Creation" +description: "Container Instance Creation on OCI" +outputGroups: + - title: "Container Instance" +schemaVersion: 1.1.0 +locale: "en" +variableGroups: + - title: "OCI (Oracle Cloud Infrastructure) details" + variables: + - compartment_ocid + - availability_domain + - subnet_id + + - title: "Container Instance configuration details" + variables: + - display_name + - shape + - container_restart_policy + - memory_in_gbs + - ocpus + - containers + - volumes + - image_pull_secrets + +variables: + compartment_ocid: + type: string + required: true + description: "The OCID of the compartment." + title: "Compartment OCID" + availability_domain: + type: string + required: true + visible: complexExpression + subnet_id: + type: string + title: "Subnet ID" + description: "The OCID of the subnet to create the VNIC in." + required: true + display_name: + type: string + pattern: "^[a-z-]+$" + title: "Display Name" + description: "A user-friendly name. Does not have to be unique, and it's changeable. Avoid entering confidential information. If you don't provide a name, a name is generated automatically." + required: true + shape: + type: string + title: "Shape" + description: "The shape of the container instance. The shape determines the resources available to the container instance." + required: true + container_restart_policy: + type: string + pattern: "^(ALWAYS|NEVER|ON_FAILURE)$" + title: "Container Restart Policy" + description: "The container restart policy is applied for all containers in container instance." + required: true + memory_in_gbs: + type: number + title: "Memory in GBs" + description: "The total amount of memory available to the container instance, in gigabytes." + required: true + ocpus: + type: number + title: "OCPUs" + description: "The total number of OCPUs available to the container instance." + required: true + containers: + type: list(map) + title: "Containers" + description: "The containers to create on this container instance." + required: true + volumes: + type: list(map) + title: "Volumes" + description: "A volume is a directory with data that is accessible across multiple containers in a container instance." + required: false + image_pull_secrets: + type: list(map) + title: "Image Pull Secrets" + description: "The image pulls secrets so you can access private registry to pull container images." + required: false diff --git a/variables.tf b/variables.tf index 2ab8817..5636277 100644 --- a/variables.tf +++ b/variables.tf @@ -1,16 +1,107 @@ variable "compartment_ocid" { - type = string - default = null + type = string + description = "The OCID of the compartment." +} + +variable "availability_domain" { + type = string + description = "The availability domain where the container instance runs." +} + +variable "display_name" { + type = string + description = "A user-friendly name. Does not have to be unique, and it's changeable. Avoid entering confidential information. If you don't provide a name, a name is generated automatically." + + validation { + condition = can(regex("^[a-z0-9-]+$", var.display_name)) + error_message = "The display_name must contain only letters, numbers or a dash" + } +} + +variable "shape" { + type = string + description = "The shape of the container instance. The shape determines the resources available to the container instance." } variable "subnet_id" { - type = string - default = null + type = string + description = "The OCID of the subnet to create the VNIC in." +} + +variable "container_restart_policy" { + type = string + default = "ALWAYS" + description = "The container restart policy is applied for all containers in container instance." + + validation { + condition = var.container_restart_policy != "NEVER" || var.container_restart_policy != "ALWAYS" || var.container_restart_policy != "ON_FAILURE" + error_message = "The container_restart_policy value must be \"ALWAYS\", \"NEVER\" or \"ON_FAILURE\"" + } +} + +variable "memory_in_gbs" { + type = number + description = "The total amount of memory available to the container instance, in gigabytes." +} + +variable "ocpus" { + type = number + description = "The total number of OCPUs available to the container instance." } -variable "container_instance" {} +variable "containers" { + type = list(object({ + display_name = string + image_url = string + environment_variables = optional(map(string)) + + command = optional(list(string)) + arguments = optional(list(string)) + + volume_mounts = optional(list(object({ + volume_name = string + mount_path = string + }))) + + resource_config = optional(map(object({ + memory_limit_in_gbs = optional(number) + vcpus_limit = optional(number) + }))) + + memory_limit_in_gbs = optional(number) + vcpus_limit = optional(number) + + working_directory = optional(string) + })) + + description = "The containers to create on this container instance." +} + +variable "volumes" { + type = list(object({ + name = string + volume_type = string + backing_store = optional(string) + + configs = optional(list(object({ + data = optional(string) + file_name = optional(string) + }))) + })) + + description = "A volume is a directory with data that is accessible across multiple containers in a container instance." + default = [] +} variable "image_pull_secrets" { - type = map(any) - default = {} + type = list(map(object({ + registry_endpoint = string + secret_type = string + secret_id = optional(string) + username = optional(string) + password = optional(string) + }))) + + description = "The image pulls secrets so you can access private registry to pull container images." + default = [] }