diff --git a/example/.tflint.hcl b/example/.tflint.hcl new file mode 100644 index 0000000..e927895 --- /dev/null +++ b/example/.tflint.hcl @@ -0,0 +1,16 @@ +plugin "terraform" { + enabled = true + preset = "recommended" +} + +rule "terraform_unused_declarations" { + enabled = true +} + +# rule "terraform_documented_outputs" { +# enabled = true +# } + +rule "terraform_documented_variables" { + enabled = true +} diff --git a/example/providers.tf b/example/providers.tf index 502d800..5873620 100644 --- a/example/providers.tf +++ b/example/providers.tf @@ -3,7 +3,7 @@ terraform { required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.54.0" + version = "0.56.0" } } } diff --git a/example/variables.tf b/example/variables.tf index bfeab6e..dff5a62 100644 --- a/example/variables.tf +++ b/example/variables.tf @@ -61,10 +61,6 @@ variable "labels" { default = {} } -# -----------------------------------------------------------------------------# -# Security Groups -# -----------------------------------------------------------------------------# - variable "security_groups" { description = "Map of security group definitions" type = map(object({ @@ -93,10 +89,6 @@ variable "security_groups" { })) } -# -----------------------------------------------------------------------------# -# PostgreSQL -# -----------------------------------------------------------------------------# - variable "postgres_instances" { description = "Map of PostgreSQL instances to create" type = map(object({ @@ -119,10 +111,6 @@ variable "postgres_instances" { })) } -# -----------------------------------------------------------------------------# -# Networks -# -----------------------------------------------------------------------------# - variable "networks" { description = "Map of network definitions per project" type = map(object({ @@ -219,10 +207,6 @@ variable "ske_clusters" { default = {} } -# -----------------------------------------------------------------------------# -# Observability -# -----------------------------------------------------------------------------# - variable "observability_instances" { description = "Map of Observability instances to create" type = map(object({ diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..93ef132 --- /dev/null +++ b/main.tf @@ -0,0 +1,130 @@ +resource "stackit_network_area" "project_sna" { + organization_id = var.organization_id + name = var.SNA_name + network_ranges = var.SNA_network_ranges + transfer_network = var.SNA_transfer_network +} + +module "projects" { + source = "./project" + + projects = var.Projects_map + organization_id = var.organization_id + sna_id = stackit_network_area.project_sna.network_area_id + labels = var.labels != null ? var.labels : {} +} + +locals { + project_ids = { for k, v in module.projects.created_projects : k => v.project_id } +} + +module "stackit_ske_cluster" { + source = "./ske" + for_each = var.ske_clusters + + project_id = local.project_ids[each.value.project_key] + name = each.value.name + node_pools = each.value.node_pools + kubernetes_version_min = lookup(each.value, "kubernetes_version_min", null) + hibernations = lookup(each.value, "hibernations", null) + maintenance = lookup(each.value, "maintenance", null) + extensions = lookup(each.value, "extensions", null) + default_region = var.default_region +} + +module "security_groups" { + source = "./security-group" + + for_each = var.security_groups + + project_id = local.project_ids[each.value.project_key] + name = each.value.name + description = each.value.description != null ? each.value.description : "" + rules = each.value.rules +} + +locals { + security_group_ids_by_name = { + for key, m in module.security_groups : + m.name => m.security_group_id + } +} + +module "net" { + depends_on = [module.security_groups] + source = "./network" + + for_each = var.networks + + project_id = local.project_ids[each.value.project_key] + name = each.value.name + + ipv4_gateway = each.value.ipv4_gateway + ipv4_nameservers = each.value.ipv4_nameservers + ipv4_prefix = each.value.ipv4_prefix + ipv4_prefix_length = each.value.ipv4_prefix_length + + ipv6_gateway = each.value.ipv6_gateway + ipv6_nameservers = each.value.ipv6_nameservers + ipv6_prefix = each.value.ipv6_prefix + ipv6_prefix_length = each.value.ipv6_prefix_length + + no_ipv4_gateway = each.value.no_ipv4_gateway + no_ipv6_gateway = each.value.no_ipv6_gateway + routed = each.value.routed + labels = each.value.labels + + nics = each.value.nics + security_group_ids_by_name = local.security_group_ids_by_name +} + +module "postgres" { + source = "./postgres" + + for_each = var.postgres_instances + + project_id = local.project_ids[each.value.project_key] + name = each.value.name + ver = each.value.version + flavor = each.value.flavor + storage = each.value.storage + replicas = each.value.replicas + acl = each.value.acl + backup_schedule = each.value.backup_schedule + users = each.value.users + databases = each.value.databases +} + +module "observability" { + source = "./observability" + for_each = var.observability_instances + + project_id = local.project_ids[each.value.project_key] + + name = each.value.name + plan_name = each.value.plan_name + + acl = each.value.acl + metrics_retention_days = each.value.metrics_retention_days + metrics_retention_days_5m_downsampling = each.value.metrics_retention_days_5m_downsampling + metrics_retention_days_1h_downsampling = each.value.metrics_retention_days_1h_downsampling + alert_config = each.value.alert_config + parameters = each.value.parameters + + # Credentials + create_credentials = each.value.create_credentials + credentials_count = each.value.credentials_count + + alertgroups = each.value.alertgroups + + logalertgroups = each.value.logalertgroups + + scrapeconfigs = each.value.scrapeconfigs +} + +output "obs_url" { + value = { + for key, instance in module.observability : + key => instance.observability_urls + } +} diff --git a/network/providers.tf b/network/providers.tf index 532c77d..6e038c3 100644 --- a/network/providers.tf +++ b/network/providers.tf @@ -3,7 +3,7 @@ terraform { required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.54.0" + version = "0.56.0" } } } diff --git a/network/variables.tf b/network/variables.tf index b5b2fc7..9c7f985 100644 --- a/network/variables.tf +++ b/network/variables.tf @@ -8,9 +8,6 @@ variable "name" { type = string } -# ----------------------------- -# IPv4 -# ----------------------------- variable "ipv4_gateway" { description = "IPv4 gateway address" type = string @@ -35,9 +32,6 @@ variable "ipv4_prefix_length" { default = null } -# ----------------------------- -# IPv6 -# ----------------------------- variable "ipv6_gateway" { description = "IPv6 gateway address" type = string @@ -62,18 +56,12 @@ variable "ipv6_prefix_length" { default = null } -# ----------------------------- -# Deprecated legacy nameservers (provider still exposes) -# ----------------------------- variable "nameservers" { description = "Deprecated nameserver list (legacy field)" type = list(string) default = [] } -# ----------------------------- -# Labels & flags -# ----------------------------- variable "labels" { description = "Labels map" type = map(string) @@ -98,9 +86,6 @@ variable "routed" { default = null } -# ----------------------------- -# Region override & routing -# ----------------------------- variable "region" { description = "Resource region override (defaults to provider region)" type = string @@ -113,9 +98,6 @@ variable "routing_table_id" { default = null } -# ----------------------------- -# NIC map (zero or many) -# ----------------------------- variable "nics" { description = "Map of NIC configs" type = map(object({ @@ -130,9 +112,6 @@ variable "nics" { default = {} } -# ----------------------------- -# Security group lookup map -# ----------------------------- variable "security_group_ids_by_name" { description = "Security group name→ID map" type = map(string) diff --git a/observability/providers.tf b/observability/providers.tf index a2af181..8962cf6 100644 --- a/observability/providers.tf +++ b/observability/providers.tf @@ -4,7 +4,7 @@ terraform { required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.54.0" + version = "0.56.0" } } } diff --git a/postgres/providers.tf b/postgres/providers.tf index 532c77d..6e038c3 100644 --- a/postgres/providers.tf +++ b/postgres/providers.tf @@ -3,7 +3,7 @@ terraform { required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.54.0" + version = "0.56.0" } } } diff --git a/project/providers.tf b/project/providers.tf index 532c77d..6e038c3 100644 --- a/project/providers.tf +++ b/project/providers.tf @@ -3,7 +3,7 @@ terraform { required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.54.0" + version = "0.56.0" } } } diff --git a/providers.tf b/providers.tf new file mode 100644 index 0000000..5873620 --- /dev/null +++ b/providers.tf @@ -0,0 +1,16 @@ +terraform { + required_version = ">= 1.9.0" + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = "0.56.0" + } + } +} + +provider "stackit" { + default_region = var.region + service_account_token = var.service_account_token != null ? var.service_account_token : null + service_account_key_path = var.service_account_key_path != null ? var.service_account_key_path : null + enable_beta_resources = true +} diff --git a/security-group/providers.tf b/security-group/providers.tf index 532c77d..6e038c3 100644 --- a/security-group/providers.tf +++ b/security-group/providers.tf @@ -3,7 +3,7 @@ terraform { required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.54.0" + version = "0.56.0" } } } diff --git a/security-group/variables.tf b/security-group/variables.tf index 4ca3116..97d6da7 100644 --- a/security-group/variables.tf +++ b/security-group/variables.tf @@ -52,7 +52,6 @@ variable "rules" { error_message = "Each rule.ether_type must be 'IPv4' or 'IPv6' when set." } - # port_range min <= max when provided validation { condition = alltrue([ for r in var.rules : diff --git a/ske/providers.tf b/ske/providers.tf index 532c77d..6e038c3 100644 --- a/ske/providers.tf +++ b/ske/providers.tf @@ -3,7 +3,7 @@ terraform { required_providers { stackit = { source = "stackitcloud/stackit" - version = "0.54.0" + version = "0.56.0" } } } diff --git a/ske/variables.tf b/ske/variables.tf index 6636f12..efa26e0 100644 --- a/ske/variables.tf +++ b/ske/variables.tf @@ -34,10 +34,6 @@ variable "node_pools" { })) } -# -----------------------------------------------------------------------------# -# Optional fields -# -----------------------------------------------------------------------------# - variable "kubernetes_version_min" { description = "Minimum Kubernetes version" type = string diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..54c97e1 --- /dev/null +++ b/variables.tf @@ -0,0 +1,351 @@ +# ----------------------------------------------------------------------------- +# STACKIT Cloud: Core Configuration Variables +# ----------------------------------------------------------------------------- + +# Provider region where STACKIT services will be deployed +variable "region" { + description = "Provider region for STACKIT Cloud" + type = string + default = "eu01" +} + +# Token for service account authentication (sensitive) +variable "service_account_token" { + description = "Service account token for authentication" + sensitive = true + type = string + default = null +} + +# ----------------------------------------------------------------------------- +# Subscriber Network Area (SNA) & Project Settings +# ----------------------------------------------------------------------------- + +# ID of the STACKIT organization container +variable "organization_id" { + description = "STACKIT organization container ID" + type = string +} + +# Local path to the JSON key for the service account +variable "service_account_key_path" { + description = "Path to service account JSON key" + type = string + default = "/Users/schlenz/.stackit/sa.json" +} + +# Fallback region for resources if none specified +variable "default_region" { + description = "Default region fallback for created resources" + type = string + default = "eu01" +} + +# Name for the Service Network Area (SNA) +variable "SNA_name" { + description = "Name of the Service Network Area to create" + type = string +} + +# List of CIDR prefixes for the Service Network Area network ranges +variable "SNA_network_ranges" { + description = "CIDR list for the Service Network Area" + type = list(object({ prefix = string })) +} + +# CIDR block used for transfer network within the SNA +variable "SNA_transfer_network" { + description = "Transfer network CIDR for the SNA" + type = string +} + +# Map of project keys to project definitions (name and owner email) +variable "Projects_map" { + description = "Map of STACKIT projects to create" + type = map(object({ + name = string + owner_email = string + })) +} + +# Default labels applied to resources where supported +variable "labels" { + description = "Default labels to apply where supported" + type = map(string) + default = {} +} + +# Security group definitions, including rules and associations +variable "security_groups" { + description = "Map of security group definitions" + type = map(object({ + name = optional(string) + project_key = string + description = optional(string) + rules = list(object({ + direction = string # e.g., ingress or egress + description = optional(string) # description of the rule + ether_type = optional(string) # IPv4 or IPv6 + icmp_parameters = optional(object({ # ICMP type/code when applicable + type = optional(number) + code = optional(number) + })) + ip_range = optional(string) # source/destination IP range + port_range = optional(object({ # TCP/UDP port range + min = number + max = number + })) + protocol = optional(object({ # protocol name/number + name = optional(string) + number = optional(number) + })) + remote_security_group_id = optional(string) # reference another group + })) + })) +} + +# ----------------------------------------------------------------------------- +# PostgreSQL Database Instances +# ----------------------------------------------------------------------------- + +# Definitions for PostgreSQL instances (name, sizing, ACLs, users, and databases) +variable "postgres_instances" { + description = "Map of PostgreSQL instances to create" + type = map(object({ + name = string + project_key = string + version = number + flavor = object({ cpu = number, ram = number }) + storage = object({ class = string, size = number }) + replicas = number + acl = list(string) # allowed IP CIDRs + backup_schedule = string # cron-like schedule + users = list(object({ # DB users and their roles + username = string + roles = set(string) + })) + databases = list(object({ # databases to create + name = string + owner = string + })) + })) +} + +# ----------------------------------------------------------------------------- +# Virtual Networks per Project +# ----------------------------------------------------------------------------- + +# Network definitions, including IPv4/IPv6 settings, labels, and NICs +variable "networks" { + description = "Map of network definitions per project" + type = map(object({ + name = string + project_key = string + + # IPv4 configuration + ipv4_gateway = optional(string) + ipv4_nameservers = optional(list(string)) + ipv4_prefix = optional(string) + ipv4_prefix_length = optional(number) + + # IPv6 configuration + ipv6_gateway = optional(string) + ipv6_nameservers = optional(list(string)) + ipv6_prefix = optional(string) + ipv6_prefix_length = optional(number) + + # Additional flags and resource labels + labels = optional(map(string)) + no_ipv4_gateway = optional(bool) + no_ipv6_gateway = optional(bool) + routed = optional(bool) + + # Network interface cards (NICs) definitions + nics = optional(map(object({ + nic_ipv4 = optional(string) + nic_name = string + nic_allowed_addresses = optional(list(string)) + nic_labels = optional(map(string)) + nic_security = optional(bool) + nic_security_group_ids = optional(list(string)) + nic_security_group_names = optional(list(string)) + }))) + })) + default = {} +} + +# ----------------------------------------------------------------------------- +# SKE Kubernetes Clusters +# ----------------------------------------------------------------------------- + +# Configuration for SKE clusters, node pools, and optional extensions +variable "ske_clusters" { + description = "Map of SKE cluster definitions" + type = map(object({ + name = string + project_key = string + kubernetes_version_min = optional(string) + + # Scheduled cluster hibernations + hibernations = optional(list(object({ + start = string # local time window start + end = string # local time window end + timezone = optional(string) # timezone of the schedule + }))) + + # Maintenance window settings + maintenance = optional(object({ + enable_kubernetes_version_updates = bool + enable_machine_image_version_updates = bool + start = string + end = string + })) + + # Cluster extensions (ACL, Argus monitoring) + extensions = optional(object({ + acl = optional(object({ + enabled = bool + allowed_cidrs = list(string) + })) + argus = optional(object({ + enabled = bool + argus_instance_id = string + })) + })) + + # Node pool definitions (machine types, scaling, labels, taints) + node_pools = list(object({ + name = string + machine_type = string + availability_zones = list(string) + minimum = number + maximum = number + + allow_system_components = optional(bool) + cri = optional(string) + labels = optional(map(string)) + max_surge = optional(number) + max_unavailable = optional(number) + os_name = optional(string) + os_version_min = optional(string) + volume_size = optional(number) + volume_type = optional(string) + taints = optional(list(object({ + effect = string + key = string + value = optional(string) + }))) + })) + })) + default = {} +} + +# ----------------------------------------------------------------------------- +# Observability Instances (Monitoring & Alerting) +# ----------------------------------------------------------------------------- + +# Definitions for Observability service instances and alert configurations +variable "observability_instances" { + description = "Map of Observability instances to create" + type = map(object({ + name = string + project_key = string + plan_name = string # e.g., Observability-Medium-EU01 + + # Retention and ACL settings + acl = optional(list(string)) + metrics_retention_days = optional(number) + metrics_retention_days_5m_downsampling = optional(number) + metrics_retention_days_1h_downsampling = optional(number) + alert_config = optional(any) + parameters = optional(map(string)) + + # Credential generation settings + create_credentials = optional(bool, true) + credentials_count = optional(number, 1) + + # Alert group definitions + alertgroups = optional(map(object({ + name = string + interval = optional(string) + rules = list(object({ + alert = string + expression = string + for = optional(string) + labels = optional(map(string)) + annotations = optional(map(string)) + })) + })), {}) + + # Log alert group definitions + logalertgroups = optional(map(object({ + name = string + interval = optional(string) + rules = list(object({ + alert = string + expression = string + for = optional(string) + labels = optional(map(string)) + annotations = optional(map(string)) + })) + })), {}) + + # Scrape configuration for metrics collection + scrapeconfigs = optional(map(object({ + name = string + metrics_path = string + targets = list(object({ + urls = list(string) + labels = optional(map(string)) + })) + basic_auth = optional(object({ + username = string + password = string + })) + saml2 = optional(object({ + enable_url_parameters = optional(bool) + })) + sample_limit = optional(number) + scheme = optional(string) + scrape_interval = optional(string) + scrape_timeout = optional(string) + })), {}) + })) + default = {} + + validation { + condition = alltrue([ + for k, v in var.observability_instances : + contains([ + "Observability-Medium-EU01", + "Observability-Monitoring-XL-EU01", + "Observability-Large-EU01", + "Observability-Monitoring-Basic-EU01", + "Observability-Monitoring-Large-EU01", + "Observability-Basic-EU01", + "Observability-Monitoring-Medium-EU01", + "Observability-Monitoring-XXL-EU01", + "Observability-Metrics-Endpoint-100k-EU01", + "Observability-Frontend-Starter-EU01", + "Observability-Monitoring-Starter-EU01", + "Observability-Starter-EU01", + ], v.plan_name) + ]) + error_message = <<-EOM +One or more observability_instances specify an invalid plan_name. +See the provider error output for supported plans. Allowed: + Observability-Medium-EU01 + Observability-Monitoring-XL-EU01 + Observability-Large-EU01 + Observability-Monitoring-Basic-EU01 + Observability-Monitoring-Large-EU01 + Observability-Basic-EU01 + Observability-Monitoring-Medium-EU01 + Observability-Monitoring-XXL-EU01 + Observability-Metrics-Endpoint-100k-EU01 + Observability-Frontend-Starter-EU01 + Observability-Monitoring-Starter-EU01 + Observability-Starter-EU01 +EOM + } +}