From 7463483642468e2ccfd7b6468e1dec09a795ea8d Mon Sep 17 00:00:00 2001 From: Adam Geiger Date: Thu, 9 Jun 2022 10:18:13 -0400 Subject: [PATCH] init vsi terraform module --- .docs/vsi-lb.png | Bin 0 -> 64751 bytes README.md | 135 +++++++++++++++- load_balancer.tf | 90 +++++++++++ main.tf | 117 ++++++++++++++ outputs.tf | 54 +++++++ security_group.tf | 213 ++++++++++++++++++++++++++ storage.tf | 73 +++++++++ variables.tf | 382 ++++++++++++++++++++++++++++++++++++++++++++++ versions.tf | 16 ++ 9 files changed, 1079 insertions(+), 1 deletion(-) create mode 100644 .docs/vsi-lb.png create mode 100644 load_balancer.tf create mode 100644 main.tf create mode 100644 outputs.tf create mode 100644 security_group.tf create mode 100644 storage.tf create mode 100644 variables.tf create mode 100644 versions.tf diff --git a/.docs/vsi-lb.png b/.docs/vsi-lb.png new file mode 100644 index 0000000000000000000000000000000000000000..f93dacfd2c1fd2da82e7e2eb4d795ccbd0e1ffba GIT binary patch literal 64751 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 z%0D-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 literal 0 HcmV?d00001 diff --git a/README.md b/README.md index 7eadab10..8c6695a8 100644 --- a/README.md +++ b/README.md @@ -1 +1,134 @@ -# terraform-ibm-landing-zone-vsi \ No newline at end of file +# VSI on VPC Module + +This module allows users to create any number of VSI across multple subnets with any number of block storage volumes, connected by any number of load balancers. + +![vsi-module](./.docs/vsi-lb.png) + +## Table of Contents + +1. [Prerequisites](##Prerequisites)s +2. [Virtual Servers](##Virtual-Servers) +3. [Block Storage](##Block-Storage) +4. [Floating IPs](##Floating-IPs) +5. [Load Balancers](##Load-Balancers) +6. [Module Variables](##module-vairables) +7. [Module Outputs](##module-outputs) +8. [As A Module in a Larger Architecture](##As-A-Module-in-a-Larger-Architecture) + +--- + +## Prerequisites + +An existing VPC and VPC SSH Key + +--- + +## Virtual Servers + +This module creates Virtual servers across any number of subnets in a single VPC connected by a single security group. Users can specify how many virtual servers to provision on each subnet by using the `vsi_per_subnet` variable. Virtual servers use the prefix to dynamically create names. These names are also used as the terraform address for each Virtual Server, allowing for easy reference: + +```terraform +module.vsi["test-vsi"].ibm_is_instance.vsi["test-vsi-1"] +module.vsi["test-vsi"].ibm_is_instance.vsi["test-vsi-2"] +module.vsi["test-vsi"].ibm_is_instance.vsi["test-vsi-3"] +``` +--- + +## Block Storage Volumes + +This module allows users to create any number of identical block storage volumes. One of each storage volume specified in the `volumes` variable will be created and attached to each virtual server. These block storage volumes use the Virtual Server name and the volume name to create easily identifiable and manageble addressess within terraform: + +```terraform +module.vsi["test-vsi"].ibm_is_volume.volume["test-vsi-1-one"] +module.vsi["test-vsi"].ibm_is_volume.volume["test-vsi-2-one"] +module.vsi["test-vsi"].ibm_is_volume.volume["test-vsi-3-one"] +module.vsi["test-vsi"].ibm_is_volume.volume["test-vsi-1-two"] +module.vsi["test-vsi"].ibm_is_volume.volume["test-vsi-2-two"] +module.vsi["test-vsi"].ibm_is_volume.volume["test-vsi-3-two"] +``` + +--- + +## Floating IPs + +By using the `enable_floating_ip` a floating IP will be assigned to each VSI created by this module. This floating IP will be displayed in the output if provisioned. + +--- + +## Load Balancers + +This module allows users to create any number of application Load Balancers to balance traffic between all Virtual Servers created by this module. Each Load Balancer can optionally be added to it's own security group. The `load_balancers` variable allows users to configure the back end pool and front end listener for each load balancer. + +--- + +## Module Variables + +Name | Type | Description +-------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ +resource_group_id | string | id of resource group to create VPC +prefix | string | The IBM Cloud platform API key needed to deploy IAM enabled resources +tags | list(string) | List of tags to apply to resources created by this module. +vpc_id | string | ID of VPC +subnets | list( object({ name = string id = string zone = string cidr = string }) ) | A list of subnet IDs where VSI will be deployed +image_id | string | Image ID used for VSI. Run 'ibmcloud is images' to find available images in a region +ssh_key_ids | list(string) | ssh key ids to use in creating vsi +machine_type | string | VSI machine type. Run 'ibmcloud is instance-profiles' to get a list of regional profiles +vsi_per_subnet | number | Number of VSI instances for each subnet +user_data | string | User data to initialize VSI deployment +boot_volume_encryption_key | string | CRN of boot volume encryption key +enable_floating_ip | bool | Create a floating IP for each virtual server created +allow_ip_spoofing | bool | Allow IP spoofing on the primary network interface +create_security_group | bool | Create security group for VSI. If this is passed as false, the default will be used +security_group | object({ name = string rules = list( object({ name = string direction = string source = string tcp = optional( object({ port_max = number port_min = number }) ) udp = optional( object({ port_max = number port_min = number }) ) icmp = optional( object({ type = number code = number }) ) }) ) }) | Security group created for VSI +security_group_ids | list(string) | IDs of additional security groups to be added to VSI deployment primary interface. A VSI interface can have a maximum of 5 security groups. +block_storage_volumes | list( object({ name = string profile = string capacity = optional(number) iops = optional(number) encryption_key = optional(string) }) ) | List describing the block storage volumes that will be attached to each vsi +load_balancers | list( object({ name = string type = string listener_port = number listener_protocol = string connection_limit = number algorithm = string protocol = string health_delay = number health_retries = number health_timeout = number health_type = string pool_member_port = string security_group = optional( object({ name = string rules = list( object({ name = string direction = string source = string tcp = optional( object({ port_max = number port_min = number }) ) udp = optional( object({ port_max = number port_min = number }) ) icmp = optional( object({ type = number code = number }) ) }) ) }) ) }) ) | Load balancers to add to VSI +secondary_subnets | list( object({ name = string id = string zone = string cidr = string }) ) | List of secondary network interfaces to add to vsi secondary subnets must be in the same zone as VSI. This is only recommended for use with a deployment of 1 VSI. +secondary_use_vsi_security_group | bool | Use the security group created by this module in the secondary interface +secondary_security_group_ids | list(string) | IDs of additional security groups to be added to VSI deployment secondary interfaces. A VSI interface can have a maximum of 5 security groups. +secondary_allow_ip_spoofing | bool | Allow IP spoofing on additional network interfaces + +--- + +## Module Outputs + +Name | Description +-------------------- | ----------------------------------------------------------- +ids | The IDs of the VSI +vsi_security_group | Security group for the VSI +list | A list of VSI with name, id, zone, and primary ipv4 address +lb_hostnames | Hostnames for the Load Balancer created +lb_security_groups | Load Balancer security groups + +--- + +## As A Module in a Larger Architecture + +## Usage +```terraform +module vsi { + source = "github.com/Cloud-Schematics/vsi-module.git" + resource_group_id = var.resource_group_id + prefix = var.prefix + tags = var.tags + vpc_id = var.vpc_id + subnets = var.subnets + image_id = var.image_id + ssh_key_ids = var.ssh_key_ids + machine_type = var.machine_type + vsi_per_subnet = var.vsi_per_subnet + user_data = var.user_data + boot_volume_encryption_key = var.boot_volume_encryption_key + enable_floating_ip = var.enable_floating_ip + allow_ip_spoofing = var.allow_ip_spoofing + create_security_group = var.create_security_group + security_group = var.security_group + security_group_ids = var.security_group_ids + block_storage_volumes = var.block_storage_volumes + load_balancers = var.load_balancers + secondary_subnets = var.secondary_subnets + secondary_use_vsi_security_group = var.secondary_use_vsi_security_group + secondary_security_group_ids = var.secondary_security_group_ids + secondary_allow_ip_spoofing = var.secondary_allow_ip_spoofing +} +``` \ No newline at end of file diff --git a/load_balancer.tf b/load_balancer.tf new file mode 100644 index 00000000..88085f84 --- /dev/null +++ b/load_balancer.tf @@ -0,0 +1,90 @@ +############################################################################## +# Load Balancer +############################################################################## + +locals { + load_balancer_map = { + for load_balancer in var.load_balancers : + (load_balancer.name) => load_balancer + } +} + +resource "ibm_is_lb" "lb" { + for_each = local.load_balancer_map + name = "${var.prefix}-${each.value.name}-lb" + subnets = var.subnets.*.id + type = each.value.type + security_groups = each.value.security_group == null ? null : [ibm_is_security_group.security_group[each.value.security_group.name].id] + resource_group = var.resource_group_id + tags = var.tags +} + +############################################################################## + + +############################################################################## +# Load Balancer Pool +############################################################################## + +resource "ibm_is_lb_pool" "pool" { + for_each = local.load_balancer_map + lb = ibm_is_lb.lb[each.value.name].id + name = "${var.prefix}-${each.value.name}-lb-pool" + algorithm = each.value.algorithm + protocol = each.value.protocol + health_delay = each.value.health_delay + health_retries = each.value.health_retries + health_timeout = each.value.health_timeout + health_type = each.value.health_type +} + +############################################################################## + +############################################################################## +# Load Balancer Pool Member +############################################################################## + +locals { + pool_members = flatten([ + for load_balancer in var.load_balancers : + [ + for ipv4_address in [ + for server in ibm_is_instance.vsi : + lookup(server, "primary_network_interface", null) == null ? null : server.primary_network_interface.0.primary_ipv4_address + ] : + { + port = load_balancer.pool_member_port + target_address = ipv4_address + lb = load_balancer.name + } + ] + ]) +} + +resource "ibm_is_lb_pool_member" "pool_members" { + count = length(local.pool_members) + port = local.pool_members[count.index].port + lb = ibm_is_lb.lb[local.pool_members[count.index].lb].id + pool = element(split("/", ibm_is_lb_pool.pool[local.pool_members[count.index].lb].id), 1) + target_address = local.pool_members[count.index].target_address +} + +############################################################################## + + + +############################################################################## +# Load Balancer Listener +############################################################################## + +resource "ibm_is_lb_listener" "listener" { + for_each = local.load_balancer_map + lb = ibm_is_lb.lb[each.value.name].id + default_pool = ibm_is_lb_pool.pool[each.value.name].id + port = each.value.listener_port + protocol = each.value.listener_protocol + connection_limit = each.value.connection_limit > 0 ? each.value.connection_limit : null + depends_on = [ibm_is_lb_pool_member.pool_members] +} + +############################################################################## \ No newline at end of file diff --git a/main.tf b/main.tf new file mode 100644 index 00000000..f653bf76 --- /dev/null +++ b/main.tf @@ -0,0 +1,117 @@ +############################################################################## +# Virtual Server Data +############################################################################## +locals { + + # Create list of VSI using subnets and VSI per subnet + vsi_list = flatten([ + # For each subnet + for subnet in range(length(var.subnets)) : [ + # For each number in a range from 0 to VSI per subnet + for count in range(var.vsi_per_subnet) : + { + name = "${var.prefix}-${(subnet) * (var.vsi_per_subnet) + count + 1}" + subnet_id = var.subnets[subnet].id + zone = var.subnets[subnet].zone + subnet_name = var.subnets[subnet].name + } + ] + ]) + + # Create map of VSI from list + vsi_map = { + for server in local.vsi_list : + server.name => server + } + + secondary_fip_list = flatten([ + # For each interface in list of floating ips + for interface in var.secondary_floating_ips : + [ + # For each virtual server + for instance in ibm_is_instance.vsi : + { + # fip name + name = "${instance.name}-${interface}-fip" + # target interface at the same index as subnet name + target = instance.network_interfaces[index(var.secondary_subnets.*.name, interface)].id + } + ] + ]) +} + +############################################################################## + + +############################################################################## +# Create Virtual Servers +############################################################################## + +resource "ibm_is_instance" "vsi" { + for_each = local.vsi_map + name = each.key + image = var.image_id + profile = var.machine_type + resource_group = var.resource_group_id + vpc = var.vpc_id + zone = each.value.zone + user_data = var.user_data + keys = var.ssh_key_ids + + primary_network_interface { + subnet = each.value.subnet_id + security_groups = flatten([ + (var.create_security_group ? [ibm_is_security_group.security_group[var.security_group.name].id] : []), + var.security_group_ids + ]) + allow_ip_spoofing = var.allow_ip_spoofing + } + + dynamic "network_interfaces" { + for_each = var.secondary_subnets == null ? [] : var.secondary_subnets + content { + subnet = network_interfaces.value.id + security_groups = flatten([ + (var.create_security_group && var.secondary_use_vsi_security_group ? [ibm_is_security_group.security_group[var.security_group.name].id] : []), + [ + for group in var.secondary_security_groups : + group.security_group_id if group.interface_name == network_interfaces.value.name + ] + ]) + allow_ip_spoofing = var.secondary_allow_ip_spoofing + } + } + + boot_volume { + encryption = var.boot_volume_encryption_key == "" ? null : var.boot_volume_encryption_key + } + + # Only add volumes if volumes are being created by the module + volumes = length(var.block_storage_volumes) == 0 ? [] : local.volume_by_vsi[each.key] +} + + + +############################################################################## + + +############################################################################## +# Optionally create floating IP +############################################################################## + +resource "ibm_is_floating_ip" "vsi_fip" { + for_each = var.enable_floating_ip ? ibm_is_instance.vsi : {} + name = "${each.value.name}-fip" + target = each.value.primary_network_interface.0.id +} + +resource "ibm_is_floating_ip" "secondary_fip" { + for_each = length(var.secondary_floating_ips) == 0 ? {} : { + for interface in local.secondary_fip_list : + (interface.name) => interface + } + name = each.key + target = each.value.target +} + +############################################################################## \ No newline at end of file diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 00000000..ec078b3a --- /dev/null +++ b/outputs.tf @@ -0,0 +1,54 @@ +############################################################################## +# VSI Outputs +############################################################################## + +output "ids" { + description = "The IDs of the VSI" + value = [ + for virtual_server in ibm_is_instance.vsi : + virtual_server.id + ] +} + +output "vsi_security_group" { + description = "Security group for the VSI" + value = var.security_group == null ? null : ibm_is_security_group.security_group[var.security_group.name] +} + +output "list" { + description = "A list of VSI with name, id, zone, and primary ipv4 address" + value = [ + for virtual_server in ibm_is_instance.vsi : + { + name = virtual_server.name + id = virtual_server.id + zone = virtual_server.zone + ipv4_address = virtual_server.primary_network_interface.0.primary_ipv4_address + floating_ip = var.enable_floating_ip ? ibm_is_floating_ip.vsi_fip[virtual_server.name].address : null + } + ] +} + +############################################################################## + +############################################################################## +# Load Balancer Outputs +############################################################################## + +output "lb_hostnames" { + description = "Hostnames for the Load Balancer created" + value = [ + for load_balancer in ibm_is_lb.lb : + load_balancer.hostname + ] +} + +output "lb_security_groups" { + description = "Load Balancer security groups" + value = { + for load_balancer in var.load_balancers : + (load_balancer.name) => ibm_is_security_group.security_group[load_balancer.security_group.name] if load_balancer.security_group != null + } +} + +############################################################################## \ No newline at end of file diff --git a/security_group.tf b/security_group.tf new file mode 100644 index 00000000..7df6146d --- /dev/null +++ b/security_group.tf @@ -0,0 +1,213 @@ +############################################################################## +# ibm_is_security_group +############################################################################## + +locals { + vsi_security_group = [var.create_security_group ? var.security_group : null] + # Create list of all security groups including the ones for load balancers + security_groups = flatten([ + [ + for group in local.vsi_security_group : + group if group != null + ], + [ + for load_balancer in var.load_balancers : + load_balancer.security_group if load_balancer.security_group != null + ] + ]) + + # Convert list to map + security_group_map = { + for group in local.security_groups : + (group.name) => group + } +} + +resource "ibm_is_security_group" "security_group" { + for_each = local.security_group_map + name = each.value.name + resource_group = var.resource_group_id + vpc = var.vpc_id + tags = var.tags +} + +############################################################################## + + +############################################################################## +# Change Security Group (Optional) +############################################################################## + +locals { + # Create list of all sg rules to create adding the name + security_group_rule_list = flatten([ + for group in local.security_groups : + [ + for rule in group.rules : + merge({ + sg_name = group.name + }, rule) + ] + ]) + + # Convert list to map + security_group_rules = { + for rule in local.security_group_rule_list : + ("${rule.sg_name}-${rule.name}") => rule + } +} + + + +resource "ibm_is_security_group_rule" "security_group_rules" { + for_each = local.security_group_rules + group = ibm_is_security_group.security_group[each.value.sg_name].id + direction = each.value.direction + remote = each.value.source + + + ############################################################################## + # Dynamicaly create ICMP Block + ############################################################################## + + dynamic "icmp" { + + # Runs a for each loop, if the rule block contains icmp, it looks through the block + # Otherwise the list will be empty + + for_each = ( + # Only allow creation of icmp rules if all of the keys are not null. + # This allows the use of the optional variable in landing zone patterns + # to convert to a single typed list by adding `null` as the value. + each.value.icmp == null + ? [] + : length([ + for value in ["type", "code"] : + true if lookup(each.value["icmp"], value, null) == null + ]) == 2 + ? [] # if all values null empty array + : [each.value] + ) + # Conditianally add content if sg has icmp + content { + type = lookup( + lookup( + each.value, + "icmp" + ), + "type", + null + ) + code = lookup( + lookup( + each.value, + "icmp" + ), + "code", + null + ) + } + } + + ############################################################################## + + ############################################################################## + # Dynamically create TCP Block + ############################################################################## + + dynamic "tcp" { + + # Runs a for each loop, if the rule block contains tcp, it looks through the block + # Otherwise the list will be empty + + for_each = ( + # Only allow creation of tcp rules if all of the keys are not null. + # This allows the use of the optional variable in landing zone patterns + # to convert to a single typed list by adding `null` as the value. + # the default behavior will be to set `null` `port_min` values to 1 if null + # and `port_max` to 65535 if null + each.value.tcp == null + ? [] + : length([ + for value in ["port_min", "port_max"] : + true if lookup(each.value["tcp"], value, null) == null + ]) == 2 + ? [] # if all values null empty array + : [each.value] + ) + + # Conditionally adds content if sg has tcp + content { + port_min = lookup( + lookup( + each.value, + "tcp" + ), + "port_min", + null + ) + + port_max = lookup( + lookup( + each.value, + "tcp" + ), + "port_max", + null + ) + } + } + + ############################################################################## + + ############################################################################## + # Dynamically create UDP Block + ############################################################################## + + dynamic "udp" { + + # Runs a for each loop, if the rule block contains udp, it looks through the block + # Otherwise the list will be empty + + for_each = ( + # Only allow creation of udp rules if all of the keys are not null. + # This allows the use of the optional variable in landing zone patterns + # to convert to a single typed list by adding `null` as the value. + # the default behavior will be to set `null` `port_min` values to 1 if null + # and `port_max` to 65535 if null + each.value.udp == null + ? [] + : length([ + for value in ["port_min", "port_max"] : + true if lookup(each.value["udp"], value, null) == null + ]) == 2 + ? [] # if all values null empty array + : [each.value] + ) + + # Conditionally adds content if sg has udp + content { + port_min = lookup( + lookup( + each.value, + "udp" + ), + "port_min", + null + ) + port_max = lookup( + lookup( + each.value, + "udp" + ), + "port_max", + null + ) + } + } + + ############################################################################## + +} + +############################################################################## diff --git a/storage.tf b/storage.tf new file mode 100644 index 00000000..fadc49d1 --- /dev/null +++ b/storage.tf @@ -0,0 +1,73 @@ +############################################################################## +# Create Volumes +############################################################################## + +locals { + + # List of volumes for each VSI + volume_list = flatten([ + # For each subnet + for subnet in range(length(var.subnets)) : [ + # For each number in a range from 0 to VSI per subnet + for count in range(var.vsi_per_subnet) : [ + # For each volume + for volume in var.block_storage_volumes : + { + name = "${var.prefix}-${(subnet) * (var.vsi_per_subnet) + count + 1}-${volume.name}" + zone = var.subnets[subnet].zone + profile = volume.profile + capacity = volume.capacity + vsi_name = "${var.prefix}-${(subnet) * (var.vsi_per_subnet) + count + 1}" + iops = volume.iops + encryption_key = volume.encryption_key + } + ] + ] + ]) + + # Map of all volumes + volume_map = { + for volume in local.volume_list : + volume.name => volume + } +} + +############################################################################## + +############################################################################## +# Create Volumes +############################################################################## + +resource "ibm_is_volume" "volume" { + for_each = local.volume_map + name = each.value.name + profile = each.value.profile + zone = each.value.zone + iops = each.value.iops + capacity = each.value.capacity + encryption_key = each.value.encryption_key +} + +############################################################################## + + +############################################################################## +# Map Volumes to VSI Name +############################################################################## + +locals { + # Create a map that groups lists of volumes by VSI name to be referenced in + # instance creation + volume_by_vsi = { + # For each distinct server name + for virtual_server in distinct(local.volume_list.*.vsi_name) : + # Create an object where the key is the name of the server + (virtual_server) => [ + # That includes the ids of only volumes with the matching `vsi_name` + for volume in local.volume_list : + ibm_is_volume.volume[volume.name].id if volume.vsi_name == virtual_server + ] + } +} + +############################################################################## \ No newline at end of file diff --git a/variables.tf b/variables.tf new file mode 100644 index 00000000..76b2ff48 --- /dev/null +++ b/variables.tf @@ -0,0 +1,382 @@ +############################################################################## +# Account Variables +############################################################################## + +variable "resource_group_id" { + description = "id of resource group to create VPC" + type = string +} + +variable "prefix" { + description = "The IBM Cloud platform API key needed to deploy IAM enabled resources" + type = string + + validation { + error_message = "Prefix must begin and end with a letter and contain only letters, numbers, and - characters." + condition = can(regex("^([A-z]|[a-z][-a-z0-9]*[a-z0-9])$", var.prefix)) + } +} + +variable "tags" { + description = "List of tags to apply to resources created by this module." + type = list(string) + default = [] +} + +############################################################################## + + +############################################################################## +# VPC Variables +############################################################################## + +variable "vpc_id" { + description = "ID of VPC" + type = string +} + +variable "subnets" { + description = "A list of subnet IDs where VSI will be deployed" + type = list( + object({ + name = string + id = string + zone = string + cidr = string + }) + ) +} + +############################################################################## + + +############################################################################## +# VSI Variables +############################################################################## + +variable "image_id" { + description = "Image ID used for VSI. Run 'ibmcloud is images' to find available images in a region" + type = string +} + +variable "ssh_key_ids" { + description = "ssh key ids to use in creating vsi" + type = list(string) +} + +variable "machine_type" { + description = "VSI machine type. Run 'ibmcloud is instance-profiles' to get a list of regional profiles" + type = string +} + +variable "vsi_per_subnet" { + description = "Number of VSI instances for each subnet" + type = number +} + +variable "user_data" { + description = "User data to initialize VSI deployment" + type = string +} + +variable "boot_volume_encryption_key" { + description = "CRN of boot volume encryption key" + type = string +} + +variable "enable_floating_ip" { + description = "Create a floating IP for each virtual server created" + type = bool + default = false +} + +variable "allow_ip_spoofing" { + description = "Allow IP spoofing on the primary network interface" + type = bool + default = false +} + +variable "create_security_group" { + description = "Create security group for VSI. If this is passed as false, the default will be used" + type = bool +} + +variable "security_group" { + description = "Security group created for VSI" + type = object({ + name = string + rules = list( + object({ + name = string + direction = string + source = string + tcp = optional( + object({ + port_max = number + port_min = number + }) + ) + udp = optional( + object({ + port_max = number + port_min = number + }) + ) + icmp = optional( + object({ + type = number + code = number + }) + ) + }) + ) + }) + + validation { + error_message = "Each security group rule must have a unique name." + condition = ( + var.security_group == null + ? true + : length(distinct(var.security_group.rules.*.name)) == length(var.security_group.rules.*.name) + ) + } + + validation { + error_message = "Security group rules can only use one of the following blocks: `tcp`, `udp`, `icmp`." + condition = var.security_group == null ? true : length( + distinct( + flatten([ + for rule in var.security_group.rules : + true if length( + [ + for type in ["tcp", "udp", "icmp"] : + true if rule[type] != null + ] + ) > 1 + ]) + ) + ) == 0 + } + + validation { + error_message = "Security group rule direction can only be `inbound` or `outbound`." + condition = var.security_group == null ? true : length( + distinct( + flatten([ + for rule in var.security_group.rules : + false if !contains(["inbound", "outbound"], rule.direction) + ]) + ) + ) == 0 + } + +} + +variable "security_group_ids" { + description = "IDs of additional security groups to be added to VSI deployment primary interface. A VSI interface can have a maximum of 5 security groups." + type = list(string) + default = [] + + validation { + error_message = "Security group IDs must be unique." + condition = length(var.security_group_ids) == length(distinct(var.security_group_ids)) + } + + validation { + error_message = "No more than 5 security groups can be added to a VSI deployment." + condition = length(var.security_group_ids) <= 5 + } +} + +variable "block_storage_volumes" { + description = "List describing the block storage volumes that will be attached to each vsi" + type = list( + object({ + name = string + profile = string + capacity = optional(number) + iops = optional(number) + encryption_key = optional(string) + }) + ) + default = [] + + validation { + error_message = "Each block storage volume must have a unique name." + condition = length(distinct(var.block_storage_volumes.*.name)) == length(var.block_storage_volumes) + } +} + +variable "load_balancers" { + description = "Load balancers to add to VSI" + type = list( + object({ + name = string + type = string + listener_port = number + listener_protocol = string + connection_limit = number + algorithm = string + protocol = string + health_delay = number + health_retries = number + health_timeout = number + health_type = string + pool_member_port = string + security_group = optional( + object({ + name = string + rules = list( + object({ + name = string + direction = string + source = string + tcp = optional( + object({ + port_max = number + port_min = number + }) + ) + udp = optional( + object({ + port_max = number + port_min = number + }) + ) + icmp = optional( + object({ + type = number + code = number + }) + ) + }) + ) + }) + ) + }) + ) + default = [] + + validation { + error_message = "Load balancer names must match the regex pattern ^([a-z]|[a-z][-a-z0-9]*[a-z0-9])$." + condition = length(distinct( + flatten([ + # Check through rules + for load_balancer in var.load_balancers : + # Return false if direction is not valid + false if !can(regex("^([a-z]|[a-z][-a-z0-9]*[a-z0-9])$", load_balancer.name)) + ]) + )) == 0 + } + + validation { + error_message = "Load Balancer Pool algorithm can only be `round_robin`, `weighted_round_robin`, or `least_connections`." + condition = length( + flatten([ + for load_balancer in var.load_balancers : + true if !contains(["round_robin", "weighted_round_robin", "least_connections"], load_balancer.algorithm) + ]) + ) == 0 + } + + validation { + error_message = "Load Balancer Pool Protocol can only be `http`, `https`, or `tcp`." + condition = length( + flatten([ + for load_balancer in var.load_balancers : + true if !contains(["http", "https", "tcp"], load_balancer.protocol) + ]) + ) == 0 + } + + validation { + error_message = "Pool health delay must be greater than the timeout." + condition = length( + flatten([ + for load_balancer in var.load_balancers : + true if load_balancer.health_delay < load_balancer.health_timeout + ]) + ) == 0 + } + + validation { + error_message = "Load Balancer Pool Health Check Type can only be `http`, `https`, or `tcp`." + condition = length( + flatten([ + for load_balancer in var.load_balancers : + true if !contains(["http", "https", "tcp"], load_balancer.health_type) + ]) + ) == 0 + } + + validation { + error_message = "Each load balancer must have a unique name." + condition = length(distinct(var.load_balancers.*.name)) == length(var.load_balancers.*.name) + } +} + +############################################################################## + + +############################################################################## +# Secondary Interface Variables +############################################################################## + +variable "secondary_subnets" { + description = "List of secondary network interfaces to add to vsi secondary subnets must be in the same zone as VSI. This is only recommended for use with a deployment of 1 VSI." + type = list( + object({ + name = string + id = string + zone = string + cidr = string + }) + ) + default = [] +} + +variable "secondary_use_vsi_security_group" { + description = "Use the security group created by this module in the secondary interface" + type = bool + default = false +} + +variable "secondary_security_groups" { + description = "IDs of additional security groups to be added to VSI deployment secondary interfaces. A VSI interface can have a maximum of 5 security groups." + type = list( + object({ + security_group_id = string + interface_name = string + }) + ) + default = [] + + validation { + error_message = "Security group IDs must be unique." + condition = length(var.secondary_security_groups) == length(distinct(var.secondary_security_groups)) + } + + validation { + error_message = "No more than 5 security groups can be added to a VSI deployment." + condition = length(var.secondary_security_groups) <= 5 + } +} + +variable "secondary_floating_ips" { + description = "List of secondary interfaces to add floating ips" + type = list(string) + default = [] + + validation { + error_message = "Secondary floating IPs must contain a unique list of interfaces." + condition = length(var.secondary_floating_ips) == length(distinct(var.secondary_floating_ips)) + } +} + +variable "secondary_allow_ip_spoofing" { + description = "Allow IP spoofing on additional network interfaces" + type = bool + default = false +} + +############################################################################## \ No newline at end of file diff --git a/versions.tf b/versions.tf new file mode 100644 index 00000000..5673dd46 --- /dev/null +++ b/versions.tf @@ -0,0 +1,16 @@ +############################################################################## +# Terraform Providers +############################################################################## + +terraform { + required_providers { + ibm = { + source = "IBM-Cloud/ibm" + version = "~>1.41.0" + } + } + required_version = ">=1.0" + experiments = [module_variable_optional_attrs] +} + +##############################################################################