diff --git a/deployment/terraform/modules/cogstack-docker-services/ansible.tf b/deployment/terraform/modules/cogstack-docker-services/ansible.tf index d1e9069..b4275f4 100644 --- a/deployment/terraform/modules/cogstack-docker-services/ansible.tf +++ b/deployment/terraform/modules/cogstack-docker-services/ansible.tf @@ -10,6 +10,7 @@ resource "ansible_playbook" "playbook" { replayable = false lifecycle { replace_triggered_by = [terraform_data.hash_of_all_config_files] + ignore_changes = [extra_vars] } extra_vars = { diff --git a/deployment/terraform/modules/openstack-cogstack-infra/compute.tf b/deployment/terraform/modules/openstack-cogstack-infra/compute.tf index ada0a55..9b2b31f 100644 --- a/deployment/terraform/modules/openstack-cogstack-infra/compute.tf +++ b/deployment/terraform/modules/openstack-cogstack-infra/compute.tf @@ -37,6 +37,41 @@ resource "openstack_compute_instance_v2" "cogstack_ops_compute" { ] } + lifecycle { + precondition { + condition = (length([for x in var.host_instances : x if x.is_controller == true]) == 1) || var.preexisting_controller_host != null + error_message = <<-EOT +Invalid variable input. +Must have exactly one controller host defined in host_instances if preexisting_controller_host is not provided. +Either pass a host with is_controller = true in host_instances, or provide details of an existing host using preexisting_controller_host. + +Valid module input to create a controller: + host_instances = [ + { name = "example-test", is_controller = true }, + ] + + +Valid module input with details of an existing controller: + host_instances = [ + { name = "example-test" }, + ] + preexisting_controller_host = { + ip_address = "10.1.1.1" + name = "existing-controller-host" + unique_name = "WFXGH-existing-controller-host" + } + +Invalid module input: +{ + host_instances = [ + { name = "example-test" }, + ] +} + + EOT + } + } + } # TODO: Read content from files and put into cloud-init config diff --git a/deployment/terraform/modules/openstack-cogstack-infra/networking.tf b/deployment/terraform/modules/openstack-cogstack-infra/networking.tf index 3651fa0..d756b5d 100644 --- a/deployment/terraform/modules/openstack-cogstack-infra/networking.tf +++ b/deployment/terraform/modules/openstack-cogstack-infra/networking.tf @@ -1,7 +1,7 @@ locals { - devops_controller_cidr = "${local.controller_host_instance.access_ip_v4}/32" + devops_controller_cidr = "${local.controller_host_instance.ip_address}/32" cogstack_apps_ingress_rules = [ { port = 22, cidr = var.allowed_ingress_ips_cidr, description = "SSH" }, @@ -15,6 +15,8 @@ locals { ] cogstack_apps_ingress_rules_map = { for rule in local.cogstack_apps_ingress_rules : "${rule.port}-${rule.description}" => rule } cogstack_apps_devops_controller_rules_map = { for rule in local.cogstack_apps_devops_controller_rules : "${rule.port}-${rule.description}" => rule } + + user_provided_security_rules_map = { for rule in var.allowed_security_group_rules : "${rule.port}-${rule.description}" => rule } } resource "openstack_networking_secgroup_v2" "cogstack_apps_security_group" { @@ -44,3 +46,15 @@ resource "openstack_networking_secgroup_rule_v2" "cogstack_apps_devops_controlle description = each.value.description security_group_id = openstack_networking_secgroup_v2.cogstack_apps_security_group.id } + +resource "openstack_networking_secgroup_rule_v2" "user_provided_security_rules_map" { + for_each = local.user_provided_security_rules_map + direction = "ingress" + ethertype = "IPv4" + protocol = "tcp" + port_range_min = each.value.port + port_range_max = each.value.port + remote_ip_prefix = each.value.cidr + description = each.value.description + security_group_id = openstack_networking_secgroup_v2.cogstack_apps_security_group.id +} diff --git a/deployment/terraform/modules/openstack-cogstack-infra/outputs.tf b/deployment/terraform/modules/openstack-cogstack-infra/outputs.tf index ec613cf..65605c1 100644 --- a/deployment/terraform/modules/openstack-cogstack-infra/outputs.tf +++ b/deployment/terraform/modules/openstack-cogstack-infra/outputs.tf @@ -9,13 +9,7 @@ output "created_hosts" { } output "created_controller_host" { - value = { - name = (local.controller_host.name) - ip_address = local.controller_host_instance.access_ip_v4 - unique_name = local.controller_host_instance.name - - } - + value = local.controller_host_instance description = "Created Controller Host: A map of { hostname: { data } }" } @@ -27,10 +21,15 @@ output "compute_keypair" { description = "Absolute path to a public and private SSH key pair that is granted login on created VMs" } +output "created_security_group" { + value = openstack_networking_secgroup_v2.cogstack_apps_security_group + description = "Security group associated to the created hosts" +} + output "portainer_instance" { sensitive = true value = { - endpoint = "https://${local.controller_host_instance.access_ip_v4}:9443" + endpoint = "https://${local.controller_host_instance.ip_address}:9443" username = "admin" password = local.portainer_admin_password } diff --git a/deployment/terraform/modules/openstack-cogstack-infra/shared-locals.tf b/deployment/terraform/modules/openstack-cogstack-infra/shared-locals.tf index 5bc9dd0..6423851 100644 --- a/deployment/terraform/modules/openstack-cogstack-infra/shared-locals.tf +++ b/deployment/terraform/modules/openstack-cogstack-infra/shared-locals.tf @@ -5,8 +5,20 @@ locals { locals { - controller_host = one([for host in var.host_instances : host if host.is_controller]) - controller_host_instance = openstack_compute_instance_v2.cogstack_ops_compute[local.controller_host.name] + # Desired controller host + controller_host = var.preexisting_controller_host != null ? var.preexisting_controller_host : one([for host in var.host_instances : host if host.is_controller]) + + # Created controller host instance + created_controller_host = var.preexisting_controller_host != null ? null : openstack_compute_instance_v2.cogstack_ops_compute[local.controller_host.name] + + # Final controller host instance (either created or preexisting) + controller_host_instance = { + name = local.controller_host.name + ip_address = var.preexisting_controller_host != null ? var.preexisting_controller_host.ip_address : local.created_controller_host.access_ip_v4 + unique_name = var.preexisting_controller_host != null && var.preexisting_controller_host.unique_name != null ? var.preexisting_controller_host.unique_name : local.created_controller_host.name + } + + } resource "random_id" "server" { @@ -20,9 +32,19 @@ resource "random_id" "server" { } resource "random_password" "portainer_password" { - count = var.portainer_secrets.admin_password != null ? 0 : 1 + count = (var.portainer_secrets.admin_password == null) && (var.preexisting_controller_host == null) ? 1 : 0 length = 16 } locals { - portainer_admin_password = var.portainer_secrets.admin_password != null ? var.portainer_secrets.admin_password : random_password.portainer_password[0].result + + # Nested ternary for portainer password: + # admin_password preexisting_host Result + # notnull any provided password + # null not null "unknown" + # null null generated password + portainer_admin_password = ( + var.portainer_secrets.admin_password != null ? var.portainer_secrets.admin_password : + var.preexisting_controller_host != null ? "unknown" : + random_password.portainer_password[0].result + ) } diff --git a/deployment/terraform/modules/openstack-cogstack-infra/variables.tf b/deployment/terraform/modules/openstack-cogstack-infra/variables.tf index ab2c46e..aed9dee 100644 --- a/deployment/terraform/modules/openstack-cogstack-infra/variables.tf +++ b/deployment/terraform/modules/openstack-cogstack-infra/variables.tf @@ -46,12 +46,44 @@ EOT } validation { - condition = length([for x in var.host_instances : x if x.is_controller == true]) == 1 - error_message = "Must have exactly one controller host" + condition = length([for x in var.host_instances : x if x.is_controller == true]) <= 1 + error_message = "Must have at most 1 controller host" } } +variable "preexisting_controller_host" { + type = object({ + name = string, + ip_address = string, + unique_name = optional(string) + }) + default = null + description = <