@thomas.solbjor her er "import" av tofu fra ditt repo med justeringer for å tilpasse patterns her. Også minimalisert til å kun opprette cluster, ingen managed services som postgres etc. Ta en titt.
@thomas.solbjor her er "import" av tofu fra ditt repo med justeringer for å tilpasse patterns her. Også minimalisert til å kun opprette cluster, ingen managed services som postgres etc. Ta en titt.
Ghost
added the ai-review label 2026-04-28 06:51:52 +00:00
Kubeconfig should not be exposed as a Terraform output as it contains sensitive cluster credentials that could be logged or stored insecurely.
#ai-review-inline
Kubeconfig should not be exposed as a Terraform output as it contains sensitive cluster credentials that could be logged or stored insecurely.
#ai-review-inline
Example file contains placeholder values that could be mistaken for real credentials.
```suggestion
AZURE_TENANT_ID=00000000-0000-0000-0000-000000000000
```
#ai-review-inline
Example file contains placeholder values that could be mistaken for real credentials.
```suggestion
AZURE_SUBSCRIPTION_ID=00000000-0000-0000-0000-000000000000
```
#ai-review-inline
Example credential files should use placeholder values that cannot be mistaken for real tokens.
UPCLOUD_TOKEN=upcloud-api-token-here
#ai-review-inline
Example credential files should use placeholder values that cannot be mistaken for real tokens.
```suggestion
UPCLOUD_TOKEN=upcloud-api-token-here
```
#ai-review-inline
Example file contains placeholder values that could be mistaken for real credentials.
```suggestion
AZURE_TENANT_ID=00000000-0000-0000-0000-000000000000
```
#ai-review-inline
Example file contains placeholder values that could be mistaken for real credentials.
```suggestion
AZURE_SUBSCRIPTION_ID=00000000-0000-0000-0000-000000000000
```
#ai-review-inline
Example credential files should use placeholder values that cannot be mistaken for real tokens.
UPCLOUD_TOKEN=upcloud-api-token-here
#ai-review-inline
Example credential files should use placeholder values that cannot be mistaken for real tokens.
```suggestion
UPCLOUD_TOKEN=upcloud-api-token-here
```
#ai-review-inline
Standard_B2s is a burstable instance type which may not provide consistent performance for AKS workloads.
aks_node_vm_size = "Standard_D2s_v3"
#ai-review-inline
Standard_B2s is a burstable instance type which may not provide consistent performance for AKS workloads.
```suggestion
aks_node_vm_size = "Standard_D2s_v3"
```
#ai-review-inline
Production AKS cluster has only 3 nodes which may not provide sufficient availability for production workloads.
aks_node_count = 5
#ai-review-inline
Production AKS cluster has only 3 nodes which may not provide sufficient availability for production workloads.
```suggestion
aks_node_count = 5
```
#ai-review-inline
Use a more specific version constraint to ensure reproducible deployments and avoid potential breaking changes.
version = "~> 4.8"
#ai-review-inline
Use a more specific version constraint to ensure reproducible deployments and avoid potential breaking changes.
```suggestion
version = "~> 4.8"
```
#ai-review-inline
Kubernetes version should be pinned to a specific patch version for reproducibility and security patching control.
kubernetes_version = "1.30.6"
#ai-review-inline
Kubernetes version should be pinned to a specific patch version for reproducibility and security patching control.
```suggestion
kubernetes_version = "1.30.6"
```
#ai-review-inline
Consider using a more specific version constraint to ensure reproducible builds and avoid unexpected updates.
version = "~> 4.0.5"
#ai-review-inline
Consider using a more specific version constraint to ensure reproducible builds and avoid unexpected updates.
```suggestion
version = "~> 4.0.5"
```
#ai-review-inline
Consider using a more specific version constraint to ensure reproducible builds and avoid unexpected updates.
version = "~> 4.0.5"
#ai-review-inline
Consider using a more specific version constraint to ensure reproducible builds and avoid unexpected updates.
```suggestion
version = "~> 4.0.5"
```
#ai-review-inline
Consider using a smaller default CIDR range to avoid potential IP conflicts with on-premises networks.
default = "10.240.0.0/16"
#ai-review-inline
Consider using a smaller default CIDR range to avoid potential IP conflicts with on-premises networks.
```suggestion
default = "10.240.0.0/16"
```
#ai-review-inline
Consider adding validation to restrict to supported Azure VM sizes for better cost control and compatibility.
variable "aks_node_vm_size" {
description = "VM size for AKS worker nodes (e.g., Standard_B2s, Standard_D4s_v3)"
type = string
validation {
condition = can(regex("^Standard_[BDEFGHKLMNPRS][0-9]+[a-z]*_v[0-9]+$|^Standard_[BDEFGHKLMNPRS][0-9]+[a-z]*$", var.aks_node_vm_size))
error_message = "VM size must be a valid Azure VM size (e.g., Standard_B2s, Standard_D4s_v3)."
}
}
#ai-review-inline
Consider adding validation to restrict to supported Azure VM sizes for better cost control and compatibility.
```suggestion
variable "aks_node_vm_size" {
description = "VM size for AKS worker nodes (e.g., Standard_B2s, Standard_D4s_v3)"
type = string
validation {
condition = can(regex("^Standard_[BDEFGHKLMNPRS][0-9]+[a-z]*_v[0-9]+$|^Standard_[BDEFGHKLMNPRS][0-9]+[a-z]*$", var.aks_node_vm_size))
error_message = "VM size must be a valid Azure VM size (e.g., Standard_B2s, Standard_D4s_v3)."
}
}
```
#ai-review-inline
Consider adding validation to ensure node count is within reasonable bounds (1-100).
variable "aks_node_count" {
description = "Number of AKS worker nodes"
type = number
validation {
condition = var.aks_node_count >= 1 && var.aks_node_count <= 100
error_message = "Node count must be between 1 and 100."
}
}
#ai-review-inline
Consider adding validation to ensure node count is within reasonable bounds (1-100).
```suggestion
variable "aks_node_count" {
description = "Number of AKS worker nodes"
type = number
validation {
condition = var.aks_node_count >= 1 && var.aks_node_count <= 100
error_message = "Node count must be between 1 and 100."
}
}
```
#ai-review-inline
Consider using a more specific version constraint to ensure reproducible builds and avoid unexpected updates.
version = "~> 5.70"
#ai-review-inline
Consider using a more specific version constraint to ensure reproducible builds and avoid unexpected updates.
```suggestion
version = "~> 5.70"
```
#ai-review-inline
Consider using a smaller default CIDR range to avoid potential IP conflicts with on-premises networks.
default = "10.240.0.0/16"
#ai-review-inline
Consider using a smaller default CIDR range to avoid potential IP conflicts with on-premises networks.
```suggestion
default = "10.240.0.0/16"
```
#ai-review-inline
Consider adding validation to restrict to supported Azure VM sizes for better cost control and compatibility.
variable "aks_node_vm_size" {
description = "VM size for AKS worker nodes (e.g., Standard_B2s, Standard_D4s_v3)"
type = string
validation {
condition = can(regex("^Standard_[BDEFGHKLMNPRS][0-9]+[a-z]*_v[0-9]+$|^Standard_[BDEFGHKLMNPRS][0-9]+[a-z]*$", var.aks_node_vm_size))
error_message = "VM size must be a valid Azure VM size (e.g., Standard_B2s, Standard_D4s_v3)."
}
}
#ai-review-inline
Consider adding validation to restrict to supported Azure VM sizes for better cost control and compatibility.
```suggestion
variable "aks_node_vm_size" {
description = "VM size for AKS worker nodes (e.g., Standard_B2s, Standard_D4s_v3)"
type = string
validation {
condition = can(regex("^Standard_[BDEFGHKLMNPRS][0-9]+[a-z]*_v[0-9]+$|^Standard_[BDEFGHKLMNPRS][0-9]+[a-z]*$", var.aks_node_vm_size))
error_message = "VM size must be a valid Azure VM size (e.g., Standard_B2s, Standard_D4s_v3)."
}
}
```
#ai-review-inline
Consider adding validation to ensure node count is within reasonable bounds (1-100).
variable "aks_node_count" {
description = "Number of AKS worker nodes"
type = number
validation {
condition = var.aks_node_count >= 1 && var.aks_node_count <= 100
error_message = "Node count must be between 1 and 100."
}
}
#ai-review-inline
Consider adding validation to ensure node count is within reasonable bounds (1-100).
```suggestion
variable "aks_node_count" {
description = "Number of AKS worker nodes"
type = number
validation {
condition = var.aks_node_count >= 1 && var.aks_node_count <= 100
error_message = "Node count must be between 1 and 100."
}
}
```
#ai-review-inline
Consider using a more specific version constraint to ensure reproducible builds and avoid unexpected updates.
version = "~> 5.70"
#ai-review-inline
Consider using a more specific version constraint to ensure reproducible builds and avoid unexpected updates.
```suggestion
version = "~> 5.70"
```
#ai-review-inline
Kubernetes version should be pinned to a specific patch version (e.g., '1.30.6') to ensure consistent deployments and avoid unexpected upgrades.
kubernetes_version = "1.30.6"
#ai-review-inline
Kubernetes version should be pinned to a specific patch version (e.g., '1.30.6') to ensure consistent deployments and avoid unexpected upgrades.
```suggestion
kubernetes_version = "1.30.6"
```
#ai-review-inline
VM size Standard_B2s may be too small for production workloads and could cause resource constraints.
default = "Standard_D2s_v3"
#ai-review-inline
VM size Standard_B2s may be too small for production workloads and could cause resource constraints.
```suggestion
default = "Standard_D2s_v3"
```
#ai-review-inline
Domain variable lacks a default value and has no validation, which could cause deployment failures.
variable "domain" {
description = "Public domain name — must have an existing Azure DNS zone"
type = string
validation {
condition = can(regex("^[a-z0-9.-]+\\.[a-z]{2,}$", var.domain))
error_message = "Domain must be a valid FQDN."
}
}
#ai-review-inline
Domain variable lacks a default value and has no validation, which could cause deployment failures.
```suggestion
variable "domain" {
description = "Public domain name — must have an existing Azure DNS zone"
type = string
validation {
condition = can(regex("^[a-z0-9.-]+\\.[a-z]{2,}$", var.domain))
error_message = "Domain must be a valid FQDN."
}
}
```
#ai-review-inline
VM size Standard_B2s may be too small for production workloads and could cause resource constraints.
default = "Standard_D2s_v3"
#ai-review-inline
VM size Standard_B2s may be too small for production workloads and could cause resource constraints.
```suggestion
default = "Standard_D2s_v3"
```
#ai-review-inline
Domain variable lacks a default value and has no validation, which could cause deployment failures.
variable "domain" {
description = "Public domain name — must have an existing Azure DNS zone"
type = string
validation {
condition = can(regex("^[a-z0-9.-]+\\.[a-z]{2,}$", var.domain))
error_message = "Domain must be a valid FQDN."
}
}
#ai-review-inline
Domain variable lacks a default value and has no validation, which could cause deployment failures.
```suggestion
variable "domain" {
description = "Public domain name — must have an existing Azure DNS zone"
type = string
validation {
condition = can(regex("^[a-z0-9.-]+\\.[a-z]{2,}$", var.domain))
error_message = "Domain must be a valid FQDN."
}
}
```
#ai-review-inline
Public API endpoint access should be restricted to specific CIDR blocks for security.
```suggestion
endpoint_public_access = true
public_access_cidrs = ["YOUR_OFFICE_IP/32"]
```
#ai-review-inline
Node instance types should be configurable as a list to support multiple instance types for better availability and cost optimization.
instance_types = var.node_instance_types
#ai-review-inline
Node instance types should be configurable as a list to support multiple instance types for better availability and cost optimization.
```suggestion
instance_types = var.node_instance_types
```
#ai-review-inline
Public API endpoint access should be restricted to specific CIDR blocks for security.
```suggestion
endpoint_public_access = true
public_access_cidrs = ["YOUR_OFFICE_IP/32"]
```
#ai-review-inline
Node instance types should be configurable as a list to support multiple instance types for better availability and cost optimization.
instance_types = var.node_instance_types
#ai-review-inline
Node instance types should be configurable as a list to support multiple instance types for better availability and cost optimization.
```suggestion
instance_types = var.node_instance_types
```
#ai-review-inline
Public API endpoint access should be restricted to specific CIDR blocks for security.
```suggestion
endpoint_public_access = true
public_access_cidrs = ["YOUR_OFFICE_IP/32"]
```
#ai-review-inline
Node instance types should be configurable as a list to support multiple instance types for better availability and cost optimization.
instance_types = var.node_instance_types
#ai-review-inline
Node instance types should be configurable as a list to support multiple instance types for better availability and cost optimization.
```suggestion
instance_types = var.node_instance_types
```
#ai-review-inline
Consider using max_unavailable_percentage instead of a fixed number for better scaling flexibility.
max_unavailable_percentage = 25
#ai-review-inline
Consider using `max_unavailable_percentage` instead of a fixed number for better scaling flexibility.
```suggestion
max_unavailable_percentage = 25
```
#ai-review-inline
Use exact version pinning instead of pessimistic version constraint for better reproducibility in production environments.
version = "5.73.0"
#ai-review-inline
Use exact version pinning instead of pessimistic version constraint for better reproducibility in production environments.
```suggestion
version = "5.73.0"
```
#ai-review-inline
Use exact version pinning instead of pessimistic version constraint for better reproducibility in production environments.
version = "4.0.6"
#ai-review-inline
Use exact version pinning instead of pessimistic version constraint for better reproducibility in production environments.
```suggestion
version = "4.0.6"
```
#ai-review-inline
Variable lacks validation to ensure it follows GCP project ID naming rules (6-30 chars, lowercase, numbers, hyphens).
variable "project_id" {
description = "GCP project ID for the dev environment"
type = string
validation {
condition = can(regex("^[a-z][a-z0-9-]{4,28}[a-z0-9]$", var.project_id))
error_message = "Project ID must be 6-30 characters, start with lowercase letter, and contain only lowercase letters, numbers, and hyphens."
}
}
#ai-review-inline
Variable lacks validation to ensure it follows GCP project ID naming rules (6-30 chars, lowercase, numbers, hyphens).
```suggestion
variable "project_id" {
description = "GCP project ID for the dev environment"
type = string
validation {
condition = can(regex("^[a-z][a-z0-9-]{4,28}[a-z0-9]$", var.project_id))
error_message = "Project ID must be 6-30 characters, start with lowercase letter, and contain only lowercase letters, numbers, and hyphens."
}
}
```
#ai-review-inline
Consider pinning to a more specific version range to avoid potential breaking changes from minor version updates.
version = "~> 6.11"
#ai-review-inline
Consider pinning to a more specific version range to avoid potential breaking changes from minor version updates.
```suggestion
version = "~> 6.11"
```
#ai-review-inline
The 'roles/dns.admin' role is overly permissive and grants broad DNS management access.
role = "roles/dns.recordsOwner"
#ai-review-inline
The 'roles/dns.admin' role is overly permissive and grants broad DNS management access.
```suggestion
role = "roles/dns.recordsOwner"
```
#ai-review-inline
The 'roles/dns.admin' role is overly permissive and grants broad DNS management access.
role = "roles/dns.recordsOwner"
#ai-review-inline
The 'roles/dns.admin' role is overly permissive and grants broad DNS management access.
```suggestion
role = "roles/dns.recordsOwner"
```
#ai-review-inline
Missing validation for domain variable - should ensure it's a valid FQDN.
variable "domain" {
description = "Public domain name — must have an existing Route53 hosted zone"
type = string
validation {
condition = can(regex("^[a-z0-9.-]+\\.[a-z]{2,}$", var.domain))
error_message = "Domain must be a valid FQDN (e.g., example.com)."
}
}
#ai-review-inline
Missing validation for domain variable - should ensure it's a valid FQDN.
```suggestion
variable "domain" {
description = "Public domain name — must have an existing Route53 hosted zone"
type = string
validation {
condition = can(regex("^[a-z0-9.-]+\\.[a-z]{2,}$", var.domain))
error_message = "Domain must be a valid FQDN (e.g., example.com)."
}
}
```
#ai-review-inline
Node group using only one instance type - consider adding a secondary type for better availability.
```suggestion
instance_types = [var.node_instance_type, "t3.large"]
```
#ai-review-inline
Missing validation for domain variable - should ensure it's a valid FQDN.
variable "domain" {
description = "Public domain name — must have an existing Route53 hosted zone"
type = string
validation {
condition = can(regex("^[a-z0-9.-]+\\.[a-z]{2,}$", var.domain))
error_message = "Domain must be a valid FQDN (e.g., example.com)."
}
}
#ai-review-inline
Missing validation for domain variable - should ensure it's a valid FQDN.
```suggestion
variable "domain" {
description = "Public domain name — must have an existing Route53 hosted zone"
type = string
validation {
condition = can(regex("^[a-z0-9.-]+\\.[a-z]{2,}$", var.domain))
error_message = "Domain must be a valid FQDN (e.g., example.com)."
}
}
```
#ai-review-inline
Node group using only one instance type - consider adding a secondary type for better availability.
```suggestion
instance_types = [var.node_instance_type, "t3.large"]
```
#ai-review-inline
Missing validation for domain variable - should ensure it's a valid FQDN.
variable "domain" {
description = "Public domain name — must have an existing Route53 hosted zone"
type = string
validation {
condition = can(regex("^[a-z0-9.-]+\\.[a-z]{2,}$", var.domain))
error_message = "Domain must be a valid FQDN (e.g., example.com)."
}
}
#ai-review-inline
Missing validation for domain variable - should ensure it's a valid FQDN.
```suggestion
variable "domain" {
description = "Public domain name — must have an existing Route53 hosted zone"
type = string
validation {
condition = can(regex("^[a-z0-9.-]+\\.[a-z]{2,}$", var.domain))
error_message = "Domain must be a valid FQDN (e.g., example.com)."
}
}
```
#ai-review-inline
Node group using only one instance type - consider adding a secondary type for better availability.
```suggestion
instance_types = [var.node_instance_type, "t3.large"]
```
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
output "cluster_name" { value = google_container_cluster.main.name } # added
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
```suggestion
output "cluster_name" { value = google_container_cluster.main.name } # added
```
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
output "project_id" { value = var.project_id } # added
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
```suggestion
output "project_id" { value = var.project_id } # added
```
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
output "region" { value = var.region } # added
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
```suggestion
output "region" { value = var.region } # added
```
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
output "external_dns_gsa_email" { value = google_service_account.external_dns.email } # added
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
```suggestion
output "external_dns_gsa_email" { value = google_service_account.external_dns.email } # added
```
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
output "cluster_name" { value = google_container_cluster.main.name } # added
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
```suggestion
output "cluster_name" { value = google_container_cluster.main.name } # added
```
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
output "project_id" { value = var.project_id } # added
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
```suggestion
output "project_id" { value = var.project_id } # added
```
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
output "region" { value = var.region } # added
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
```suggestion
output "region" { value = var.region } # added
```
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
output "external_dns_gsa_email" { value = google_service_account.external_dns.email } # added
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
```suggestion
output "external_dns_gsa_email" { value = google_service_account.external_dns.email } # added
```
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
output "cluster_name" { value = google_container_cluster.main.name } # added
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
```suggestion
output "cluster_name" { value = google_container_cluster.main.name } # added
```
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
output "project_id" { value = var.project_id } # added
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
```suggestion
output "project_id" { value = var.project_id } # added
```
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
output "region" { value = var.region } # added
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
```suggestion
output "region" { value = var.region } # added
```
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
output "external_dns_gsa_email" { value = google_service_account.external_dns.email } # added
#ai-review-inline
Output value should be wrapped in quotes for consistency with Terraform string outputs.
```suggestion
output "external_dns_gsa_email" { value = google_service_account.external_dns.email } # added
```
#ai-review-inline
Provider authentication should reference environment variables explicitly or use a more secure method than username/password.
# Authentication via environment variables:
# UPCLOUD_USERNAME and UPCLOUD_PASSWORD
# Consider using API tokens instead of username/password for better security
#ai-review-inline
Provider authentication should reference environment variables explicitly or use a more secure method than username/password.
```suggestion
# Authentication via environment variables:
# UPCLOUD_USERNAME and UPCLOUD_PASSWORD
# Consider using API tokens instead of username/password for better security
```
#ai-review-inline
Hardcoded CIDR range should be configurable via variable to support different network architectures.
ip_cidr_range = var.subnet_cidr
#ai-review-inline
Hardcoded CIDR range should be configurable via variable to support different network architectures.
```suggestion
ip_cidr_range = var.subnet_cidr
```
#ai-review-inline
Hardcoded pod CIDR range should be configurable via variable for flexibility across environments.
ip_cidr_range = var.pod_cidr
#ai-review-inline
Hardcoded pod CIDR range should be configurable via variable for flexibility across environments.
```suggestion
ip_cidr_range = var.pod_cidr
```
#ai-review-inline
Hardcoded service CIDR range should be configurable via variable for flexibility across environments.
ip_cidr_range = var.service_cidr
#ai-review-inline
Hardcoded service CIDR range should be configurable via variable for flexibility across environments.
```suggestion
ip_cidr_range = var.service_cidr
```
#ai-review-inline
Default control plane access allows all IPs (0.0.0.0/0), which exposes the Kubernetes API to the entire internet - this is a significant security risk.
default = []
#ai-review-inline
Default control plane access allows all IPs (0.0.0.0/0), which exposes the Kubernetes API to the entire internet - this is a significant security risk.
```suggestion
default = []
```
#ai-review-inline
Control plane access is exposed to the entire internet (0.0.0.0/0), which is a critical security risk for production.
control_plane_ip_filter = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] # Restrict to private networks
#ai-review-inline
Control plane access is exposed to the entire internet (0.0.0.0/0), which is a critical security risk for production.
```suggestion
control_plane_ip_filter = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] # Restrict to private networks
```
#ai-review-inline
Authentication credentials should be explicitly documented or validated to ensure they're properly set via environment variables.
# Authentication via environment variables: UPCLOUD_USERNAME and UPCLOUD_PASSWORD
# Verify these are set in your CI/CD pipeline or local environment
#ai-review-inline
Authentication credentials should be explicitly documented or validated to ensure they're properly set via environment variables.
```suggestion
# Authentication via environment variables: UPCLOUD_USERNAME and UPCLOUD_PASSWORD
# Verify these are set in your CI/CD pipeline or local environment
```
#ai-review-inline
Control plane API is exposed to the entire internet with 0.0.0.0/0 CIDR which creates a security risk.
```suggestion
default = ["10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"]
```
#ai-review-inline
Document the required environment variables in a README or use Terraform variables with descriptions instead of relying solely on comments.
#ai-review-inline
Document the required environment variables in a README or use Terraform variables with descriptions instead of relying solely on comments.
#ai-review-inline
Using exec with user-provided arguments ($@) before --destroy creates a command injection risk if malicious arguments are passed.
```suggestion
exec "$SCRIPT_DIR/setup-cluster.sh" --destroy "$@"
```
#ai-review-inline
Unquoted variable substitution could lead to command injection if cluster name contains special characters.
```suggestion
RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}-rg")
```
#ai-review-inline
Unquoted variable substitution could lead to command injection if cluster name contains special characters.
```suggestion
RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}-rg")
```
#ai-review-inline
Unquoted variable substitution could lead to command injection if cluster name contains special characters.
```suggestion
RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}-rg")
```
#ai-review-inline
Unquoted variable substitution could lead to command injection if cluster name contains special characters.
```suggestion
RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}-rg")
```
#ai-review-inline
Unquoted variable substitution could lead to command injection if cluster name contains special characters.
```suggestion
RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}-rg")
```
#ai-review-inline
This PR adds extensive OpenTofu infrastructure provisioning capabilities to the Kubernetes GitOps platform, introducing multi-cloud cluster provisioning automation.
What changed: Added comprehensive OpenTofu infrastructure-as-code modules and scripts for provisioning Kubernetes clusters across Azure AKS, AWS EKS, GCP GKE, and UpCloud platforms.
Affected services/namespaces: No direct Kubernetes namespace changes - this adds infrastructure provisioning layer that creates clusters where GitOps applications will be deployed. The infrastructure provisions clusters that will host namespaces like argocd, external-dns, cert-manager, etc.
Infrastructure impact: Major infrastructure addition - introduces automated cluster provisioning with configurable node counts, VM sizes, networking (VPCs/VNets with subnets), and resource scaling. AKS dev uses 2x Standard_B2s nodes, prod uses 3x Standard_D4s_v3 nodes. EKS dev uses 2x t3.medium, prod 3x m5.xlarge with auto-scaling 1-4/3-6 nodes. Includes workload identity/IRSA configurations for cloud-native authentication.
Security notes: Contains cloud provider credential configurations in git-ignored .env files, implements workload identity for keyless service access (Azure managed identity, AWS IRSA, GCP workload identity), and includes RBAC roles for external-dns DNS management. The credential handling requires extra review attention - ensure .env files remain git-ignored and follow least-privilege principles for cloud service account permissions.
#ai-review-summary
This PR adds extensive OpenTofu infrastructure provisioning capabilities to the Kubernetes GitOps platform, introducing multi-cloud cluster provisioning automation.
**What changed**: Added comprehensive OpenTofu infrastructure-as-code modules and scripts for provisioning Kubernetes clusters across Azure AKS, AWS EKS, GCP GKE, and UpCloud platforms.
**Affected services/namespaces**: No direct Kubernetes namespace changes - this adds infrastructure provisioning layer that creates clusters where GitOps applications will be deployed. The infrastructure provisions clusters that will host namespaces like argocd, external-dns, cert-manager, etc.
**Infrastructure impact**: Major infrastructure addition - introduces automated cluster provisioning with configurable node counts, VM sizes, networking (VPCs/VNets with subnets), and resource scaling. AKS dev uses 2x Standard_B2s nodes, prod uses 3x Standard_D4s_v3 nodes. EKS dev uses 2x t3.medium, prod 3x m5.xlarge with auto-scaling 1-4/3-6 nodes. Includes workload identity/IRSA configurations for cloud-native authentication.
**Security notes**: Contains cloud provider credential configurations in git-ignored .env files, implements workload identity for keyless service access (Azure managed identity, AWS IRSA, GCP workload identity), and includes RBAC roles for external-dns DNS management. The credential handling requires extra review attention - ensure .env files remain git-ignored and follow least-privilege principles for cloud service account permissions.
#ai-review-summary
Ghost
requested review from thomas.solbjor 2026-04-28 09:42:45 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
@thomas.solbjor her er "import" av tofu fra ditt repo med justeringer for å tilpasse patterns her. Også minimalisert til å kun opprette cluster, ingen managed services som postgres etc. Ta en titt.
Inline review
@@ -17,2 +17,3 @@bash.exe.stackdumpbash.exe.stackdumpEmpty line with trailing space should be cleaned up.
#ai-review-inline
Inline review
@@ -0,0 +21,4 @@}output "kubeconfig" {value = module.cluster.kubeconfigKubeconfig should not be exposed as a Terraform output as it contains sensitive cluster credentials that could be logged or stored insecurely.
#ai-review-inline
Inline review
@@ -0,0 +2,4 @@# NEVER commit aks.env to git!# RequiredAZURE_TENANT_ID=your-azure-tenant-idExample file contains placeholder values that could be mistaken for real credentials.
#ai-review-inline
@@ -0,0 +3,4 @@# RequiredAZURE_TENANT_ID=your-azure-tenant-idAZURE_SUBSCRIPTION_ID=your-azure-subscription-idExample file contains placeholder values that could be mistaken for real credentials.
#ai-review-inline
@@ -0,0 +1,9 @@# GCP GKE credentials — copy to gke.env and fill in values# NEVER commit gke.env to git!Remove empty line with trailing comment marker to clean up the example file format.
#ai-review-inline
@@ -0,0 +4,4 @@# RequiredGCP_PROJECT_ID=your-gcp-project-idGCP_REGION=europe-west4Remove empty line with trailing comment marker to clean up the example file format.
#ai-review-inline
@@ -0,0 +2,4 @@# NEVER commit upc.env to git!# RequiredUPCLOUD_TOKEN=your-upcloud-api-tokenExample credential files should use placeholder values that cannot be mistaken for real tokens.
#ai-review-inline
Inline review
Inline review
@@ -0,0 +2,4 @@# NEVER commit aks.env to git!# RequiredAZURE_TENANT_ID=your-azure-tenant-idExample file contains placeholder values that could be mistaken for real credentials.
#ai-review-inline
@@ -0,0 +3,4 @@# RequiredAZURE_TENANT_ID=your-azure-tenant-idAZURE_SUBSCRIPTION_ID=your-azure-subscription-idExample file contains placeholder values that could be mistaken for real credentials.
#ai-review-inline
@@ -0,0 +1,9 @@# GCP GKE credentials — copy to gke.env and fill in values# NEVER commit gke.env to git!Remove empty line with trailing comment marker to clean up the example file format.
#ai-review-inline
@@ -0,0 +4,4 @@# RequiredGCP_PROJECT_ID=your-gcp-project-idGCP_REGION=europe-west4Remove empty line with trailing comment marker to clean up the example file format.
#ai-review-inline
@@ -0,0 +2,4 @@# NEVER commit upc.env to git!# RequiredUPCLOUD_TOKEN=your-upcloud-api-tokenExample credential files should use placeholder values that cannot be mistaken for real tokens.
#ai-review-inline
Inline review
Inline review
Inline review
@@ -0,0 +6,4 @@resource_group_name = "clst-dev-rg"# AKS — small dev nodesaks_node_vm_size = "Standard_B2s"Standard_B2s is a burstable instance type which may not provide consistent performance for AKS workloads.
#ai-review-inline
@@ -0,0 +7,4 @@# AKS — small dev nodesaks_node_vm_size = "Standard_B2s"aks_node_count = 2Two nodes provides no redundancy; consider at least 3 nodes for high availability.
#ai-review-inline
Inline review
Inline review
@@ -0,0 +27,4 @@output "kubeconfig" {description = "Kubeconfig for the AKS cluster"value = azurerm_kubernetes_cluster.main.kube_config_rawExposing raw kubeconfig as output can lead to credential leakage in Terraform state and logs.
#ai-review-inline
Inline review
@@ -0,0 +7,4 @@# AKS — general-purpose nodes for productionaks_node_vm_size = "Standard_D4s_v3"aks_node_count = 3Production AKS cluster has only 3 nodes which may not provide sufficient availability for production workloads.
#ai-review-inline
Inline review
@@ -0,0 +4,4 @@required_providers {azurerm = {source = "hashicorp/azurerm"version = "~> 4.0"Use a more specific version constraint to ensure reproducible deployments and avoid potential breaking changes.
#ai-review-inline
Inline review
@@ -0,0 +12,4 @@node_count = 2node_min_count = 1node_max_count = 4kubernetes_version = "1.30"Kubernetes version should be pinned to a specific patch version for reproducibility and security patching control.
#ai-review-inline
Inline review
@@ -0,0 +37,4 @@# ─── AKS Cluster ──────────────────────────────────────────────────────resource "azurerm_kubernetes_cluster" "main" {AKS cluster lacks essential security configurations like private cluster mode and authorized IP ranges.
#ai-review-inline
@@ -0,0 +45,4 @@kubernetes_version = var.aks_kubernetes_versiontags = var.tagsdefault_node_pool {Default node pool lacks resource limits (max_count) and auto-scaling, which can lead to unbounded resource consumption.
#ai-review-inline
@@ -0,0 +61,4 @@type = "SystemAssigned"}network_profile {Network profile is missing pod_cidr and service_cidr configuration, which may cause IP allocation conflicts.
#ai-review-inline
Inline review
Inline review
Inline review
@@ -0,0 +6,4 @@}tls = {source = "hashicorp/tls"version = "~> 4.0"Consider using a more specific version constraint to ensure reproducible builds and avoid unexpected updates.
#ai-review-inline
Inline review
@@ -0,0 +6,4 @@}tls = {source = "hashicorp/tls"version = "~> 4.0"Consider using a more specific version constraint to ensure reproducible builds and avoid unexpected updates.
#ai-review-inline
Inline review
@@ -0,0 +18,4 @@variable "vnet_address_space" {description = "Address space for the virtual network"type = stringdefault = "10.100.0.0/16"Consider using a smaller default CIDR range to avoid potential IP conflicts with on-premises networks.
#ai-review-inline
@@ -0,0 +24,4 @@variable "aks_subnet_cidr" {description = "CIDR block for the AKS node subnet"type = stringdefault = "10.100.0.0/22"Default subnet CIDR should align with the updated VNet address space if changed.
#ai-review-inline
@@ -0,0 +30,4 @@variable "aks_node_vm_size" {description = "VM size for AKS worker nodes (e.g., Standard_B2s, Standard_D4s_v3)"type = string}Consider adding validation to restrict to supported Azure VM sizes for better cost control and compatibility.
#ai-review-inline
@@ -0,0 +35,4 @@variable "aks_node_count" {description = "Number of AKS worker nodes"type = number}Consider adding validation to ensure node count is within reasonable bounds (1-100).
#ai-review-inline
@@ -0,0 +2,4 @@required_providers {aws = {source = "hashicorp/aws"version = "~> 5.0"Consider using a more specific version constraint to ensure reproducible builds and avoid unexpected updates.
#ai-review-inline
Inline review
@@ -0,0 +18,4 @@variable "vnet_address_space" {description = "Address space for the virtual network"type = stringdefault = "10.100.0.0/16"Consider using a smaller default CIDR range to avoid potential IP conflicts with on-premises networks.
#ai-review-inline
@@ -0,0 +24,4 @@variable "aks_subnet_cidr" {description = "CIDR block for the AKS node subnet"type = stringdefault = "10.100.0.0/22"Default subnet CIDR should align with the updated VNet address space if changed.
#ai-review-inline
@@ -0,0 +30,4 @@variable "aks_node_vm_size" {description = "VM size for AKS worker nodes (e.g., Standard_B2s, Standard_D4s_v3)"type = string}Consider adding validation to restrict to supported Azure VM sizes for better cost control and compatibility.
#ai-review-inline
@@ -0,0 +35,4 @@variable "aks_node_count" {description = "Number of AKS worker nodes"type = number}Consider adding validation to ensure node count is within reasonable bounds (1-100).
#ai-review-inline
@@ -0,0 +2,4 @@required_providers {aws = {source = "hashicorp/aws"version = "~> 5.0"Consider using a more specific version constraint to ensure reproducible builds and avoid unexpected updates.
#ai-review-inline
Inline review
Inline review
Inline review
@@ -0,0 +12,4 @@node_count = 3node_min_count = 3node_max_count = 6kubernetes_version = "1.30"Kubernetes version should be pinned to a specific patch version (e.g., '1.30.6') to ensure consistent deployments and avoid unexpected upgrades.
#ai-review-inline
Inline review
@@ -0,0 +47,4 @@variable "aks_node_vm_size" {description = "VM size for AKS worker nodes"type = stringdefault = "Standard_B2s"VM size Standard_B2s may be too small for production workloads and could cause resource constraints.
#ai-review-inline
@@ -0,0 +53,4 @@variable "aks_node_count" {description = "Number of AKS worker nodes"type = numberdefault = 2Node count of 2 provides no high availability; consider at least 3 nodes for production resilience.
#ai-review-inline
@@ -0,0 +64,4 @@variable "domain" {description = "Public domain name — must have an existing Azure DNS zone"type = stringDomain variable lacks a default value and has no validation, which could cause deployment failures.
#ai-review-inline
@@ -0,0 +117,4 @@kubernetes_version = var.aks_kubernetes_versiontags = var.tagsdefault_node_pool {Default node pool is missing enable_auto_scaling and lacks resource limits which could cause node exhaustion.
#ai-review-inline
Inline review
Inline review
@@ -0,0 +47,4 @@variable "aks_node_vm_size" {description = "VM size for AKS worker nodes"type = stringdefault = "Standard_B2s"VM size Standard_B2s may be too small for production workloads and could cause resource constraints.
#ai-review-inline
@@ -0,0 +53,4 @@variable "aks_node_count" {description = "Number of AKS worker nodes"type = numberdefault = 2Node count of 2 provides no high availability; consider at least 3 nodes for production resilience.
#ai-review-inline
@@ -0,0 +64,4 @@variable "domain" {description = "Public domain name — must have an existing Azure DNS zone"type = stringDomain variable lacks a default value and has no validation, which could cause deployment failures.
#ai-review-inline
@@ -0,0 +117,4 @@kubernetes_version = var.aks_kubernetes_versiontags = var.tagsdefault_node_pool {Default node pool is missing enable_auto_scaling and lacks resource limits which could cause node exhaustion.
#ai-review-inline
Inline review
Inline review
@@ -0,0 +43,4 @@})}# NAT Gateway (single, in first public subnet — use one per AZ for prod HA)Using a single NAT Gateway creates a single point of failure; consider one NAT Gateway per AZ for production high availability.
#ai-review-inline
@@ -0,0 +121,4 @@vpc_config {subnet_ids = concat(aws_subnet.private[*].id, aws_subnet.public[*].id)endpoint_private_access = trueendpoint_public_access = truePublic API endpoint access should be restricted to specific CIDR blocks for security.
#ai-review-inline
@@ -0,0 +185,4 @@node_role_arn = aws_iam_role.eks_nodes.arnsubnet_ids = aws_subnet.private[*].idinstance_types = [var.node_instance_type]Node instance types should be configurable as a list to support multiple instance types for better availability and cost optimization.
#ai-review-inline
Inline review
@@ -0,0 +43,4 @@})}# NAT Gateway (single, in first public subnet — use one per AZ for prod HA)Using a single NAT Gateway creates a single point of failure; consider one NAT Gateway per AZ for production high availability.
#ai-review-inline
@@ -0,0 +121,4 @@vpc_config {subnet_ids = concat(aws_subnet.private[*].id, aws_subnet.public[*].id)endpoint_private_access = trueendpoint_public_access = truePublic API endpoint access should be restricted to specific CIDR blocks for security.
#ai-review-inline
@@ -0,0 +185,4 @@node_role_arn = aws_iam_role.eks_nodes.arnsubnet_ids = aws_subnet.private[*].idinstance_types = [var.node_instance_type]Node instance types should be configurable as a list to support multiple instance types for better availability and cost optimization.
#ai-review-inline
Inline review
@@ -0,0 +43,4 @@})}# NAT Gateway (single, in first public subnet — use one per AZ for prod HA)Using a single NAT Gateway creates a single point of failure; consider one NAT Gateway per AZ for production high availability.
#ai-review-inline
@@ -0,0 +121,4 @@vpc_config {subnet_ids = concat(aws_subnet.private[*].id, aws_subnet.public[*].id)endpoint_private_access = trueendpoint_public_access = truePublic API endpoint access should be restricted to specific CIDR blocks for security.
#ai-review-inline
@@ -0,0 +185,4 @@node_role_arn = aws_iam_role.eks_nodes.arnsubnet_ids = aws_subnet.private[*].idinstance_types = [var.node_instance_type]Node instance types should be configurable as a list to support multiple instance types for better availability and cost optimization.
#ai-review-inline
Inline review
@@ -0,0 +194,4 @@}update_config {max_unavailable = 1Consider using
max_unavailable_percentageinstead of a fixed number for better scaling flexibility.#ai-review-inline
Inline review
@@ -0,0 +8,4 @@# GKE — small dev nodesnode_machine_type = "e2-standard-2"node_count = 2deletion_protection = falseDeletion protection disabled in dev environment creates risk of accidental cluster deletion.
#ai-review-inline
Inline review
@@ -0,0 +2,4 @@required_providers {aws = {source = "hashicorp/aws"version = "~> 5.0"Use exact version pinning instead of pessimistic version constraint for better reproducibility in production environments.
#ai-review-inline
@@ -0,0 +6,4 @@}tls = {source = "hashicorp/tls"version = "~> 4.0"Use exact version pinning instead of pessimistic version constraint for better reproducibility in production environments.
#ai-review-inline
Inline review
Inline review
@@ -0,0 +15,4 @@}variable "project_id" {description = "GCP project ID for the dev environment"Variable lacks validation to ensure it follows GCP project ID naming rules (6-30 chars, lowercase, numbers, hyphens).
#ai-review-inline
Inline review
@@ -0,0 +8,4 @@# GKE — general-purpose nodes for productionnode_machine_type = "e2-standard-4"node_count = 3deletion_protection = trueConsider enabling deletion protection for production clusters to prevent accidental deletion.
#ai-review-inline
Inline review
@@ -0,0 +2,4 @@required_providers {google = {source = "hashicorp/google"version = "~> 6.0"Consider pinning to a more specific version range to avoid potential breaking changes from minor version updates.
#ai-review-inline
Inline review
@@ -0,0 +56,4 @@# ─── Required APIs ────────────────────────────────────────────────────resource "google_project_service" "compute" {project = var.project_idVariable 'project_id' is referenced but not defined in this file.
#ai-review-inline
@@ -0,0 +93,4 @@project = var.project_idname = "${var.prefix}-subnet"ip_cidr_range = "10.110.0.0/22"region = var.regionVariable 'region' is referenced but not defined in this file.
#ai-review-inline
@@ -0,0 +183,4 @@resource "google_project_iam_member" "external_dns_dns_admin" {project = var.project_idrole = "roles/dns.admin"The 'roles/dns.admin' role is overly permissive and grants broad DNS management access.
#ai-review-inline
Inline review
@@ -0,0 +56,4 @@# ─── Required APIs ────────────────────────────────────────────────────resource "google_project_service" "compute" {project = var.project_idVariable 'project_id' is referenced but not defined in this file.
#ai-review-inline
@@ -0,0 +93,4 @@project = var.project_idname = "${var.prefix}-subnet"ip_cidr_range = "10.110.0.0/22"region = var.regionVariable 'region' is referenced but not defined in this file.
#ai-review-inline
@@ -0,0 +183,4 @@resource "google_project_iam_member" "external_dns_dns_admin" {project = var.project_idrole = "roles/dns.admin"The 'roles/dns.admin' role is overly permissive and grants broad DNS management access.
#ai-review-inline
Inline review
Inline review
@@ -0,0 +64,4 @@variable "domain" {description = "Public domain name — must have an existing Route53 hosted zone"type = stringMissing validation for domain variable - should ensure it's a valid FQDN.
#ai-review-inline
@@ -0,0 +195,4 @@vpc_config {subnet_ids = concat(aws_subnet.private[*].id, aws_subnet.public[*].id)endpoint_private_access = trueEKS endpoint_public_access enabled - consider restricting public access with public_access_cidrs for better security.
#ai-review-inline
@@ -0,0 +257,4 @@node_role_arn = aws_iam_role.eks_nodes.arnsubnet_ids = aws_subnet.private[*].idinstance_types = [var.node_instance_type]Node group using only one instance type - consider adding a secondary type for better availability.
#ai-review-inline
@@ -0,0 +328,4 @@statement {effect = "Allow"actions = ["route53:ListHostedZones", "route53:ListResourceRecordSets", "route53:ListTagsForResource"]resources = ["*"]Route53 IAM policy uses wildcard resource for list operations - consider scoping to specific hosted zones if possible.
#ai-review-inline
Inline review
@@ -0,0 +64,4 @@variable "domain" {description = "Public domain name — must have an existing Route53 hosted zone"type = stringMissing validation for domain variable - should ensure it's a valid FQDN.
#ai-review-inline
@@ -0,0 +195,4 @@vpc_config {subnet_ids = concat(aws_subnet.private[*].id, aws_subnet.public[*].id)endpoint_private_access = trueEKS endpoint_public_access enabled - consider restricting public access with public_access_cidrs for better security.
#ai-review-inline
@@ -0,0 +257,4 @@node_role_arn = aws_iam_role.eks_nodes.arnsubnet_ids = aws_subnet.private[*].idinstance_types = [var.node_instance_type]Node group using only one instance type - consider adding a secondary type for better availability.
#ai-review-inline
@@ -0,0 +328,4 @@statement {effect = "Allow"actions = ["route53:ListHostedZones", "route53:ListResourceRecordSets", "route53:ListTagsForResource"]resources = ["*"]Route53 IAM policy uses wildcard resource for list operations - consider scoping to specific hosted zones if possible.
#ai-review-inline
Inline review
Inline review
@@ -0,0 +64,4 @@variable "domain" {description = "Public domain name — must have an existing Route53 hosted zone"type = stringMissing validation for domain variable - should ensure it's a valid FQDN.
#ai-review-inline
@@ -0,0 +195,4 @@vpc_config {subnet_ids = concat(aws_subnet.private[*].id, aws_subnet.public[*].id)endpoint_private_access = trueEKS endpoint_public_access enabled - consider restricting public access with public_access_cidrs for better security.
#ai-review-inline
@@ -0,0 +257,4 @@node_role_arn = aws_iam_role.eks_nodes.arnsubnet_ids = aws_subnet.private[*].idinstance_types = [var.node_instance_type]Node group using only one instance type - consider adding a secondary type for better availability.
#ai-review-inline
@@ -0,0 +328,4 @@statement {effect = "Allow"actions = ["route53:ListHostedZones", "route53:ListResourceRecordSets", "route53:ListTagsForResource"]resources = ["*"]Route53 IAM policy uses wildcard resource for list operations - consider scoping to specific hosted zones if possible.
#ai-review-inline
Inline review
@@ -0,0 +1,4 @@output "cluster_name" { value = google_container_cluster.main.name }Output value should be wrapped in quotes for consistency with Terraform string outputs.
#ai-review-inline
@@ -0,0 +1,4 @@output "cluster_name" { value = google_container_cluster.main.name }output "project_id" { value = var.project_id }Output value should be wrapped in quotes for consistency with Terraform string outputs.
#ai-review-inline
@@ -0,0 +1,4 @@output "cluster_name" { value = google_container_cluster.main.name }output "project_id" { value = var.project_id }output "region" { value = var.region }Output value should be wrapped in quotes for consistency with Terraform string outputs.
#ai-review-inline
@@ -0,0 +1,4 @@output "cluster_name" { value = google_container_cluster.main.name }output "project_id" { value = var.project_id }output "region" { value = var.region }output "external_dns_gsa_email" { value = google_service_account.external_dns.email }Output value should be wrapped in quotes for consistency with Terraform string outputs.
#ai-review-inline
Inline review
@@ -0,0 +1,4 @@output "cluster_name" { value = google_container_cluster.main.name }Output value should be wrapped in quotes for consistency with Terraform string outputs.
#ai-review-inline
@@ -0,0 +1,4 @@output "cluster_name" { value = google_container_cluster.main.name }output "project_id" { value = var.project_id }Output value should be wrapped in quotes for consistency with Terraform string outputs.
#ai-review-inline
@@ -0,0 +1,4 @@output "cluster_name" { value = google_container_cluster.main.name }output "project_id" { value = var.project_id }output "region" { value = var.region }Output value should be wrapped in quotes for consistency with Terraform string outputs.
#ai-review-inline
@@ -0,0 +1,4 @@output "cluster_name" { value = google_container_cluster.main.name }output "project_id" { value = var.project_id }output "region" { value = var.region }output "external_dns_gsa_email" { value = google_service_account.external_dns.email }Output value should be wrapped in quotes for consistency with Terraform string outputs.
#ai-review-inline
Inline review
@@ -0,0 +1,4 @@output "cluster_name" { value = google_container_cluster.main.name }Output value should be wrapped in quotes for consistency with Terraform string outputs.
#ai-review-inline
@@ -0,0 +1,4 @@output "cluster_name" { value = google_container_cluster.main.name }output "project_id" { value = var.project_id }Output value should be wrapped in quotes for consistency with Terraform string outputs.
#ai-review-inline
@@ -0,0 +1,4 @@output "cluster_name" { value = google_container_cluster.main.name }output "project_id" { value = var.project_id }output "region" { value = var.region }Output value should be wrapped in quotes for consistency with Terraform string outputs.
#ai-review-inline
@@ -0,0 +1,4 @@output "cluster_name" { value = google_container_cluster.main.name }output "project_id" { value = var.project_id }output "region" { value = var.region }output "external_dns_gsa_email" { value = google_service_account.external_dns.email }Output value should be wrapped in quotes for consistency with Terraform string outputs.
#ai-review-inline
Inline review
Inline review
@@ -0,0 +10,4 @@}provider "upcloud" {# Set via environment variables: UPCLOUD_USERNAME, UPCLOUD_PASSWORDProvider authentication should reference environment variables explicitly or use a more secure method than username/password.
#ai-review-inline
Inline review
@@ -0,0 +25,4 @@resource "google_compute_subnetwork" "main" {project = var.project_idname = "${var.prefix}-subnet"ip_cidr_range = "10.100.0.0/22"Hardcoded CIDR range should be configurable via variable to support different network architectures.
#ai-review-inline
@@ -0,0 +32,4 @@# Secondary ranges required for GKE VPC-native clustersecondary_ip_range {range_name = "pods"ip_cidr_range = "10.200.0.0/14" # /14 = ~262k pod IPsHardcoded pod CIDR range should be configurable via variable for flexibility across environments.
#ai-review-inline
@@ -0,0 +37,4 @@secondary_ip_range {range_name = "services"ip_cidr_range = "10.204.0.0/20" # /20 = ~4k service IPsHardcoded service CIDR range should be configurable via variable for flexibility across environments.
#ai-review-inline
@@ -0,0 +91,4 @@cluster = google_container_cluster.main.namenode_count = var.node_countnode_config {Missing security hardening options like boot disk encryption and security group configuration.
#ai-review-inline
@@ -0,0 +100,4 @@}oauth_scopes = ["https://www.googleapis.com/auth/cloud-platform",OAuth scope 'cloud-platform' is overly broad and grants excessive permissions - use specific scopes instead.
#ai-review-inline
Inline review
Inline review
Inline review
Inline review
Inline review
@@ -0,0 +35,4 @@name = "${var.prefix}-${var.cluster_name}"zone = var.zonenetwork = upcloud_network.kubernetes.idcontrol_plane_ip_filter = var.control_plane_ip_filterConsider making control plane IP filter more restrictive than default to limit management access.
#ai-review-inline
Inline review
@@ -0,0 +35,4 @@name = "${var.prefix}-${var.cluster_name}"zone = var.zonenetwork = upcloud_network.kubernetes.idcontrol_plane_ip_filter = var.control_plane_ip_filterConsider making control plane IP filter more restrictive than default to limit management access.
#ai-review-inline
Inline review
@@ -0,0 +37,4 @@network = upcloud_network.kubernetes.idcontrol_plane_ip_filter = var.control_plane_ip_filterprivate_node_groups = truePrivate node groups is a good security practice for isolating worker nodes from public internet.
#ai-review-inline
@@ -0,0 +35,4 @@variable "control_plane_ip_filter" {description = "CIDRs allowed to access the K8s API"type = list(string)default = ["0.0.0.0/0"]Default control plane access allows all IPs (0.0.0.0/0), which exposes the Kubernetes API to the entire internet - this is a significant security risk.
#ai-review-inline
Inline review
@@ -0,0 +7,4 @@node_count = 3network_cidr = "10.100.0.0/24"control_plane_ip_filter = ["0.0.0.0/0"] # TODO: restrict to known CIDRsControl plane access is exposed to the entire internet (0.0.0.0/0), which is a critical security risk for production.
#ai-review-inline
Inline review
@@ -0,0 +10,4 @@}provider "upcloud" {# Set via environment variables: UPCLOUD_USERNAME, UPCLOUD_PASSWORDAuthentication credentials should be explicitly documented or validated to ensure they're properly set via environment variables.
#ai-review-inline
Inline review
@@ -0,0 +47,4 @@variable "control_plane_ip_filter" {description = "CIDRs allowed to access the K8s API"type = list(string)default = ["0.0.0.0/0"]Control plane API is exposed to the entire internet with 0.0.0.0/0 CIDR which creates a security risk.
#ai-review-inline
Inline review
@@ -0,0 +10,4 @@}provider "upcloud" {# Set via environment variables: UPCLOUD_USERNAME, UPCLOUD_PASSWORDDocument the required environment variables in a README or use Terraform variables with descriptions instead of relying solely on comments.
#ai-review-inline
Inline review
@@ -0,0 +4,4 @@SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"# Delegate to setup-cluster.sh with --destroy flagexec "$SCRIPT_DIR/setup-cluster.sh" "$@" --destroyUsing exec with user-provided arguments ($@) before --destroy creates a command injection risk if malicious arguments are passed.
#ai-review-inline
Inline review
@@ -0,0 +30,4 @@case "$PLATFORM" inaks)cd "$TOFU_DIR"RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "$CLUSTER-rg")Command substitution with 'tofu output' could fail silently; add error handling or validation for the fallback values.
#ai-review-inline
@@ -0,0 +36,4 @@;;eks)cd "$TOFU_DIR"NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "$CLUSTER")Command substitution with 'tofu output' could fail silently; add error handling or validation for the fallback values.
#ai-review-inline
@@ -0,0 +42,4 @@;;gke)cd "$TOFU_DIR"NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "$CLUSTER")Command substitution with 'tofu output' could fail silently; add error handling or validation for the fallback values.
#ai-review-inline
@@ -0,0 +44,4 @@cd "$TOFU_DIR"NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "$CLUSTER")REGION=$(tofu output -raw region 2>/dev/null || echo "${GCP_REGION:-europe-west4}")PROJECT=$(tofu output -raw project_id 2>/dev/null || echo "${GCP_PROJECT_ID:-}")Missing validation for required GCP_PROJECT_ID variable; script could fail with empty project ID.
#ai-review-inline
@@ -0,0 +50,4 @@;;upc)cd "$TOFU_DIR"CLUSTER_ID=$(tofu output -raw cluster_id 2>/dev/null || echo "${UPCLOUD_CLUSTER_ID:-}")Missing validation for required UPCLOUD_CLUSTER_ID variable; script could fail with empty cluster ID.
#ai-review-inline
Inline review
Inline review
Inline review
@@ -0,0 +30,4 @@case "$PLATFORM" inaks)cd "$TOFU_DIR"RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "$CLUSTER-rg")Command substitution with 'tofu output' could fail silently; add error handling or validation for the fallback values.
#ai-review-inline
@@ -0,0 +36,4 @@;;eks)cd "$TOFU_DIR"NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "$CLUSTER")Command substitution with 'tofu output' could fail silently; add error handling or validation for the fallback values.
#ai-review-inline
@@ -0,0 +42,4 @@;;gke)cd "$TOFU_DIR"NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "$CLUSTER")Command substitution with 'tofu output' could fail silently; add error handling or validation for the fallback values.
#ai-review-inline
@@ -0,0 +44,4 @@cd "$TOFU_DIR"NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "$CLUSTER")REGION=$(tofu output -raw region 2>/dev/null || echo "${GCP_REGION:-europe-west4}")PROJECT=$(tofu output -raw project_id 2>/dev/null || echo "${GCP_PROJECT_ID:-}")Missing validation for required GCP_PROJECT_ID variable; script could fail with empty project ID.
#ai-review-inline
@@ -0,0 +50,4 @@;;upc)cd "$TOFU_DIR"CLUSTER_ID=$(tofu output -raw cluster_id 2>/dev/null || echo "${UPCLOUD_CLUSTER_ID:-}")Missing validation for required UPCLOUD_CLUSTER_ID variable; script could fail with empty cluster ID.
#ai-review-inline
Inline review
Inline review
@@ -0,0 +118,4 @@if [[ -f "$CLUSTER_CONFIG" ]]; thenecho " Loading cluster config from clusters/$CLUSTER.yaml"if command -v yq >/dev/null 2>&1; theneval "$(yq -r 'to_entries[] | "export CLUSTER_\(.key)=\"\(.value)\""' "$CLUSTER_CONFIG")"Potential command injection vulnerability when using yq with user-controlled cluster config.
#ai-review-inline
@@ -0,0 +184,4 @@echo " Saved from tofu output"elseecho " Fetching from Azure CLI..."RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}-rg")Unquoted variable substitution could lead to command injection if cluster name contains special characters.
#ai-review-inline
@@ -0,0 +185,4 @@elseecho " Fetching from Azure CLI..."RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}-rg")NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")Unquoted variable substitution could lead to command injection.
#ai-review-inline
@@ -0,0 +190,4 @@fi;;eks)NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")Unquoted variable substitution could lead to command injection.
#ai-review-inline
@@ -0,0 +195,4 @@aws eks update-kubeconfig --name "$NAME" --region "$REGION" --kubeconfig "$KUBECONFIG_FILE";;gke)NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")Unquoted variable substitution could lead to command injection.
#ai-review-inline
Inline review
@@ -0,0 +118,4 @@if [[ -f "$CLUSTER_CONFIG" ]]; thenecho " Loading cluster config from clusters/$CLUSTER.yaml"if command -v yq >/dev/null 2>&1; theneval "$(yq -r 'to_entries[] | "export CLUSTER_\(.key)=\"\(.value)\""' "$CLUSTER_CONFIG")"Potential command injection vulnerability when using yq with user-controlled cluster config.
#ai-review-inline
@@ -0,0 +184,4 @@echo " Saved from tofu output"elseecho " Fetching from Azure CLI..."RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}-rg")Unquoted variable substitution could lead to command injection if cluster name contains special characters.
#ai-review-inline
@@ -0,0 +185,4 @@elseecho " Fetching from Azure CLI..."RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}-rg")NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")Unquoted variable substitution could lead to command injection.
#ai-review-inline
@@ -0,0 +190,4 @@fi;;eks)NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")Unquoted variable substitution could lead to command injection.
#ai-review-inline
@@ -0,0 +195,4 @@aws eks update-kubeconfig --name "$NAME" --region "$REGION" --kubeconfig "$KUBECONFIG_FILE";;gke)NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")Unquoted variable substitution could lead to command injection.
#ai-review-inline
Inline review
@@ -0,0 +118,4 @@if [[ -f "$CLUSTER_CONFIG" ]]; thenecho " Loading cluster config from clusters/$CLUSTER.yaml"if command -v yq >/dev/null 2>&1; theneval "$(yq -r 'to_entries[] | "export CLUSTER_\(.key)=\"\(.value)\""' "$CLUSTER_CONFIG")"Potential command injection vulnerability when using yq with user-controlled cluster config.
#ai-review-inline
@@ -0,0 +184,4 @@echo " Saved from tofu output"elseecho " Fetching from Azure CLI..."RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}-rg")Unquoted variable substitution could lead to command injection if cluster name contains special characters.
#ai-review-inline
@@ -0,0 +185,4 @@elseecho " Fetching from Azure CLI..."RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}-rg")NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")Unquoted variable substitution could lead to command injection.
#ai-review-inline
@@ -0,0 +190,4 @@fi;;eks)NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")Unquoted variable substitution could lead to command injection.
#ai-review-inline
@@ -0,0 +195,4 @@aws eks update-kubeconfig --name "$NAME" --region "$REGION" --kubeconfig "$KUBECONFIG_FILE";;gke)NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")Unquoted variable substitution could lead to command injection.
#ai-review-inline
Inline review
@@ -0,0 +118,4 @@if [[ -f "$CLUSTER_CONFIG" ]]; thenecho " Loading cluster config from clusters/$CLUSTER.yaml"if command -v yq >/dev/null 2>&1; theneval "$(yq -r 'to_entries[] | "export CLUSTER_\(.key)=\"\(.value)\""' "$CLUSTER_CONFIG")"Potential command injection vulnerability when using yq with user-controlled cluster config.
#ai-review-inline
@@ -0,0 +184,4 @@echo " Saved from tofu output"elseecho " Fetching from Azure CLI..."RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}-rg")Unquoted variable substitution could lead to command injection if cluster name contains special characters.
#ai-review-inline
@@ -0,0 +185,4 @@elseecho " Fetching from Azure CLI..."RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}-rg")NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")Unquoted variable substitution could lead to command injection.
#ai-review-inline
@@ -0,0 +190,4 @@fi;;eks)NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")Unquoted variable substitution could lead to command injection.
#ai-review-inline
@@ -0,0 +195,4 @@aws eks update-kubeconfig --name "$NAME" --region "$REGION" --kubeconfig "$KUBECONFIG_FILE";;gke)NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")Unquoted variable substitution could lead to command injection.
#ai-review-inline
Inline review
@@ -0,0 +118,4 @@if [[ -f "$CLUSTER_CONFIG" ]]; thenecho " Loading cluster config from clusters/$CLUSTER.yaml"if command -v yq >/dev/null 2>&1; theneval "$(yq -r 'to_entries[] | "export CLUSTER_\(.key)=\"\(.value)\""' "$CLUSTER_CONFIG")"Potential command injection vulnerability when using yq with user-controlled cluster config.
#ai-review-inline
@@ -0,0 +184,4 @@echo " Saved from tofu output"elseecho " Fetching from Azure CLI..."RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}-rg")Unquoted variable substitution could lead to command injection if cluster name contains special characters.
#ai-review-inline
@@ -0,0 +185,4 @@elseecho " Fetching from Azure CLI..."RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}-rg")NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")Unquoted variable substitution could lead to command injection.
#ai-review-inline
@@ -0,0 +190,4 @@fi;;eks)NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")Unquoted variable substitution could lead to command injection.
#ai-review-inline
@@ -0,0 +195,4 @@aws eks update-kubeconfig --name "$NAME" --region "$REGION" --kubeconfig "$KUBECONFIG_FILE";;gke)NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")Unquoted variable substitution could lead to command injection.
#ai-review-inline
This PR adds extensive OpenTofu infrastructure provisioning capabilities to the Kubernetes GitOps platform, introducing multi-cloud cluster provisioning automation.
What changed: Added comprehensive OpenTofu infrastructure-as-code modules and scripts for provisioning Kubernetes clusters across Azure AKS, AWS EKS, GCP GKE, and UpCloud platforms.
Affected services/namespaces: No direct Kubernetes namespace changes - this adds infrastructure provisioning layer that creates clusters where GitOps applications will be deployed. The infrastructure provisions clusters that will host namespaces like argocd, external-dns, cert-manager, etc.
Infrastructure impact: Major infrastructure addition - introduces automated cluster provisioning with configurable node counts, VM sizes, networking (VPCs/VNets with subnets), and resource scaling. AKS dev uses 2x Standard_B2s nodes, prod uses 3x Standard_D4s_v3 nodes. EKS dev uses 2x t3.medium, prod 3x m5.xlarge with auto-scaling 1-4/3-6 nodes. Includes workload identity/IRSA configurations for cloud-native authentication.
Security notes: Contains cloud provider credential configurations in git-ignored .env files, implements workload identity for keyless service access (Azure managed identity, AWS IRSA, GCP workload identity), and includes RBAC roles for external-dns DNS management. The credential handling requires extra review attention - ensure .env files remain git-ignored and follow least-privilege principles for cloud service account permissions.
#ai-review-summary