diff --git a/contexts/_template/blueprint.jsonnet b/contexts/_template/blueprint.jsonnet index b2eaae0d..d5007aad 100644 --- a/contexts/_template/blueprint.jsonnet +++ b/contexts/_template/blueprint.jsonnet @@ -53,15 +53,16 @@ local terraformConfigs = { "local": [ { path: "cluster/talos", + parallelism: 1, }, { path: "gitops/flux", destroy: false, - values: { + values: if rawProvider == "local" then { git_username: "local", - git_password: "local", + git_password: "local", webhook_token: "abcdef123456", - }, + } else {}, } ] }; @@ -431,15 +432,7 @@ local blueprintMetadata = { }; // Source configuration -local sourceConfig = [ - { - name: "core", - url: "github.com/windsorcli/core", - ref: { - branch: "main", - }, - }, -]; +local sourceConfig = []; // Start of Blueprint blueprintMetadata + { diff --git a/terraform/cluster/talos/README.md b/terraform/cluster/talos/README.md index 73f53b19..14602c83 100644 --- a/terraform/cluster/talos/README.md +++ b/terraform/cluster/talos/README.md @@ -25,9 +25,7 @@ | Name | Type | |------|------| -| [local_sensitive_file.kubeconfig](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/sensitive_file) | resource | | [local_sensitive_file.talosconfig](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/sensitive_file) | resource | -| [talos_cluster_kubeconfig.this](https://registry.terraform.io/providers/siderolabs/talos/0.8.1/docs/resources/cluster_kubeconfig) | resource | | [talos_machine_secrets.this](https://registry.terraform.io/providers/siderolabs/talos/0.8.1/docs/resources/machine_secrets) | resource | | [talos_client_configuration.this](https://registry.terraform.io/providers/siderolabs/talos/0.8.1/docs/data-sources/client_configuration) | data source | @@ -49,4 +47,4 @@ ## Outputs No outputs. - \ No newline at end of file + diff --git a/terraform/cluster/talos/main.tf b/terraform/cluster/talos/main.tf index 98038977..8f669430 100644 --- a/terraform/cluster/talos/main.tf +++ b/terraform/cluster/talos/main.tf @@ -6,6 +6,9 @@ terraform { source = "siderolabs/talos" version = "0.8.1" } + local = { + source = "hashicorp/local" + } } } @@ -24,7 +27,6 @@ resource "talos_machine_secrets" "this" { locals { // Local variables for configuration paths and data talosconfig = data.talos_client_configuration.this.talos_config - kubeconfig = talos_cluster_kubeconfig.this.kubeconfig_raw talosconfig_path = "${var.context_path}/.talos/config" kubeconfig_path = "${var.context_path}/.kube/config" @@ -51,6 +53,7 @@ module "controlplane_bootstrap" { endpoint = var.controlplanes[0].endpoint bootstrap = true // Bootstrap the first control plane node talosconfig_path = local.talosconfig_path + kubeconfig_path = local.kubeconfig_path enable_health_check = true config_patches = compact(concat([ var.common_config_patches, @@ -79,6 +82,7 @@ module "controlplanes" { endpoint = var.controlplanes[count.index + 1].endpoint bootstrap = false // Do not bootstrap other control plane nodes talosconfig_path = local.talosconfig_path + kubeconfig_path = local.kubeconfig_path enable_health_check = true config_patches = compact(concat([ var.common_config_patches, @@ -110,6 +114,7 @@ module "workers" { machine_type = "worker" endpoint = var.workers[count.index].endpoint talosconfig_path = local.talosconfig_path + kubeconfig_path = local.kubeconfig_path enable_health_check = true config_patches = compact(concat([ var.common_config_patches, @@ -122,34 +127,12 @@ module "workers" { # Config Files #----------------------------------------------------------------------------------------------------------------------- -resource "talos_cluster_kubeconfig" "this" { - depends_on = [module.controlplane_bootstrap] - - client_configuration = talos_machine_secrets.this.client_configuration - node = var.controlplanes[0].node - endpoint = var.controlplanes[0].endpoint -} - data "talos_client_configuration" "this" { cluster_name = var.cluster_name client_configuration = talos_machine_secrets.this.client_configuration endpoints = var.controlplanes.*.endpoint } -// Write kubeconfig to a local file -resource "local_sensitive_file" "kubeconfig" { - count = trim(var.context_path, " ") != "" ? 1 : 0 // Create file only if path is specified and not empty/whitespace - depends_on = [local_sensitive_file.talosconfig] // Ensure Talos config is written first - - content = talos_cluster_kubeconfig.this.kubeconfig_raw - filename = local.kubeconfig_path - file_permission = "0600" // Set file permissions to read/write for owner only - - lifecycle { - ignore_changes = [content] // Ignore changes to content to prevent unnecessary updates - } -} - // Write Talos config to a local file resource "local_sensitive_file" "talosconfig" { count = trim(var.context_path, " ") != "" ? 1 : 0 // Create file only if path is specified and not empty/whitespace diff --git a/terraform/cluster/talos/modules/machine/.terraform.lock.hcl b/terraform/cluster/talos/modules/machine/.terraform.lock.hcl index 8c436b2f..2418f380 100644 --- a/terraform/cluster/talos/modules/machine/.terraform.lock.hcl +++ b/terraform/cluster/talos/modules/machine/.terraform.lock.hcl @@ -1,6 +1,25 @@ # This file is maintained automatically by "terraform init". # Manual edits may be lost in future updates. +provider "registry.terraform.io/hashicorp/local" { + version = "2.5.3" + hashes = [ + "h1:MCzg+hs1/ZQ32u56VzJMWP9ONRQPAAqAjuHuzbyshvI=", + "zh:284d4b5b572eacd456e605e94372f740f6de27b71b4e1fd49b63745d8ecd4927", + "zh:40d9dfc9c549e406b5aab73c023aa485633c1b6b730c933d7bcc2fa67fd1ae6e", + "zh:6243509bb208656eb9dc17d3c525c89acdd27f08def427a0dce22d5db90a4c8b", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:885d85869f927853b6fe330e235cd03c337ac3b933b0d9ae827ec32fa1fdcdbf", + "zh:bab66af51039bdfcccf85b25fe562cbba2f54f6b3812202f4873ade834ec201d", + "zh:c505ff1bf9442a889ac7dca3ac05a8ee6f852e0118dd9a61796a2f6ff4837f09", + "zh:d36c0b5770841ddb6eaf0499ba3de48e5d4fc99f4829b6ab66b0fab59b1aaf4f", + "zh:ddb6a407c7f3ec63efb4dad5f948b54f7f4434ee1a2607a49680d494b1776fe1", + "zh:e0dafdd4500bec23d3ff221e3a9b60621c5273e5df867bc59ef6b7e41f5c91f6", + "zh:ece8742fd2882a8fc9d6efd20e2590010d43db386b920b2a9c220cfecc18de47", + "zh:f4c6b3eb8f39105004cf720e202f04f57e3578441cfb76ca27611139bc116a82", + ] +} + provider "registry.terraform.io/hashicorp/null" { version = "3.2.4" hashes = [ diff --git a/terraform/cluster/talos/modules/machine/main.tf b/terraform/cluster/talos/modules/machine/main.tf index f8ab6323..9b5ff936 100644 --- a/terraform/cluster/talos/modules/machine/main.tf +++ b/terraform/cluster/talos/modules/machine/main.tf @@ -11,6 +11,9 @@ terraform { null = { source = "hashicorp/null" } + local = { + source = "hashicorp/local" + } } } @@ -78,6 +81,32 @@ resource "talos_machine_bootstrap" "bootstrap" { client_configuration = var.client_configuration } +#----------------------------------------------------------------------------------------------------------------------- +# Kubeconfig Generation +#----------------------------------------------------------------------------------------------------------------------- + +resource "talos_cluster_kubeconfig" "this" { + count = var.bootstrap ? 1 : 0 + depends_on = [talos_machine_bootstrap.bootstrap] + + client_configuration = var.client_configuration + node = var.node + endpoint = var.endpoint +} + +// Write kubeconfig to a local file when bootstrap is true +resource "local_sensitive_file" "kubeconfig" { + count = var.bootstrap && trim(var.kubeconfig_path, " ") != "" ? 1 : 0 + + content = talos_cluster_kubeconfig.this[0].kubeconfig_raw + filename = var.kubeconfig_path + file_permission = "0600" // Set file permissions to read/write for owner only + + lifecycle { + ignore_changes = [content] // Ignore changes to content to prevent unnecessary updates + } +} + #----------------------------------------------------------------------------------------------------------------------- # Node Health Check #----------------------------------------------------------------------------------------------------------------------- @@ -85,6 +114,9 @@ resource "talos_machine_bootstrap" "bootstrap" { locals { # Use hostname if available, otherwise fall back to node address node_name = var.hostname != null && var.hostname != "" ? var.hostname : var.node + + # Always use Talos API; during bootstrap also check Kubernetes API + health_check_command = var.bootstrap ? "windsor check node-health --nodes ${local.node_name} --timeout 5m --k8s-endpoint" : "windsor check node-health --nodes ${local.node_name} --timeout 5m" } resource "null_resource" "node_healthcheck" { @@ -94,13 +126,15 @@ resource "null_resource" "node_healthcheck" { depends_on = [ talos_machine_configuration_apply.this, - talos_machine_bootstrap.bootstrap + talos_machine_bootstrap.bootstrap, + local_sensitive_file.kubeconfig ] provisioner "local-exec" { - command = var.enable_health_check ? "windsor check node-health --nodes ${local.node_name} --timeout 5m" : "echo 'Health check disabled for testing'" + command = var.enable_health_check ? local.health_check_command : "echo 'Health check disabled'" environment = var.enable_health_check ? { TALOSCONFIG = var.talosconfig_path + KUBECONFIG = var.bootstrap ? var.kubeconfig_path : "" } : {} } } diff --git a/terraform/cluster/talos/modules/machine/output.tf b/terraform/cluster/talos/modules/machine/output.tf index 607b7052..3434f2e9 100644 --- a/terraform/cluster/talos/modules/machine/output.tf +++ b/terraform/cluster/talos/modules/machine/output.tf @@ -5,3 +5,9 @@ output "node" { output "endpoint" { value = var.endpoint } + +output "kubeconfig" { + description = "The generated kubeconfig when bootstrap is true" + value = var.bootstrap ? talos_cluster_kubeconfig.this[0].kubeconfig_raw : null + sensitive = true +} diff --git a/terraform/cluster/talos/modules/machine/test.tftest.hcl b/terraform/cluster/talos/modules/machine/test.tftest.hcl index bff71892..1ea93769 100644 --- a/terraform/cluster/talos/modules/machine/test.tftest.hcl +++ b/terraform/cluster/talos/modules/machine/test.tftest.hcl @@ -2,12 +2,17 @@ mock_provider "talos" { mock_resource "talos_machine_configuration" {} mock_resource "talos_machine_configuration_apply" {} mock_resource "talos_machine_bootstrap" {} + mock_resource "talos_cluster_kubeconfig" {} } mock_provider "null" { mock_resource "null_resource" {} } +mock_provider "local" { + mock_resource "local_sensitive_file" {} +} + variables { machine_type = "controlplane" endpoint = "dummy" @@ -56,6 +61,7 @@ variables { kubernetes_version = "dummy" talos_version = "1.10.1" talosconfig_path = "/tmp/dummy-talosconfig" + kubeconfig_path = "" enable_health_check = false } @@ -162,3 +168,114 @@ run "config_patches_includes_extra" { error_message = "Should include nameservers in extra patch" } } + +run "bootstrap_mode_generates_kubeconfig" { + variables { + bootstrap = true + kubeconfig_path = "/tmp/test-kubeconfig" + disk_selector = null + hostname = "test-node" + } + + assert { + condition = length(talos_cluster_kubeconfig.this) == 1 + error_message = "Should create kubeconfig resource when bootstrap is true" + } + + assert { + condition = length(local_sensitive_file.kubeconfig) == 1 + error_message = "Should create kubeconfig file when bootstrap is true and path is provided" + } + + assert { + condition = local_sensitive_file.kubeconfig[0].filename == "/tmp/test-kubeconfig" + error_message = "Should write kubeconfig to specified path" + } +} + +run "non_bootstrap_mode_no_kubeconfig" { + variables { + bootstrap = false + kubeconfig_path = "/tmp/test-kubeconfig" + disk_selector = null + hostname = "test-node" + } + + assert { + condition = length(talos_cluster_kubeconfig.this) == 0 + error_message = "Should not create kubeconfig resource when bootstrap is false" + } + + assert { + condition = length(local_sensitive_file.kubeconfig) == 0 + error_message = "Should not create kubeconfig file when bootstrap is false" + } +} + +run "bootstrap_mode_empty_kubeconfig_path" { + variables { + bootstrap = true + kubeconfig_path = "" + disk_selector = null + hostname = "test-node" + } + + assert { + condition = length(talos_cluster_kubeconfig.this) == 1 + error_message = "Should create kubeconfig resource when bootstrap is true" + } + + assert { + condition = length(local_sensitive_file.kubeconfig) == 0 + error_message = "Should not create kubeconfig file when path is empty" + } +} + +run "health_check_command_bootstrap_mode" { + variables { + bootstrap = true + hostname = "test-node" + disk_selector = null + } + + assert { + condition = strcontains(local.health_check_command, "--k8s-endpoint") + error_message = "Should include --k8s-endpoint flag during bootstrap" + } + + assert { + condition = strcontains(local.health_check_command, "test-node") + error_message = "Should include node name in health check command" + } +} + +run "health_check_command_non_bootstrap_mode" { + variables { + bootstrap = false + hostname = "test-node" + disk_selector = null + } + + assert { + condition = !strcontains(local.health_check_command, "--k8s-endpoint") + error_message = "Should not include --k8s-endpoint flag after bootstrap" + } + + assert { + condition = strcontains(local.health_check_command, "test-node") + error_message = "Should include node name in health check command" + } +} + +run "health_check_command_without_hostname" { + variables { + bootstrap = true + hostname = "" + disk_selector = null + } + + assert { + condition = strcontains(local.health_check_command, "dummy") + error_message = "Should use node address when hostname is empty" + } +} diff --git a/terraform/cluster/talos/modules/machine/variables.tf b/terraform/cluster/talos/modules/machine/variables.tf index 54ae088a..307509ab 100644 --- a/terraform/cluster/talos/modules/machine/variables.tf +++ b/terraform/cluster/talos/modules/machine/variables.tf @@ -123,3 +123,9 @@ variable "enable_health_check" { type = bool default = true } + +variable "kubeconfig_path" { + description = "Path where the kubeconfig file should be written when bootstrap is true." + type = string + default = "" +} diff --git a/terraform/cluster/talos/test.tftest.hcl b/terraform/cluster/talos/test.tftest.hcl index 3d5289c5..e8bfbf98 100644 --- a/terraform/cluster/talos/test.tftest.hcl +++ b/terraform/cluster/talos/test.tftest.hcl @@ -21,10 +21,7 @@ run "minimal_configuration" { error_message = "Talos config file should be generated" } - assert { - condition = length(local_sensitive_file.kubeconfig) == 1 - error_message = "Kubeconfig file should be generated" - } + assert { condition = module.controlplane_bootstrap.node == "192.168.1.10" @@ -208,10 +205,7 @@ run "no_config_files" { error_message = "No Talos config file should be generated without context path" } - assert { - condition = length(local_sensitive_file.kubeconfig) == 0 - error_message = "No Kubeconfig file should be generated without context path" - } + } # Verifies that all input validation rules are enforced simultaneously, ensuring that diff --git a/terraform/gitops/flux/README.md b/terraform/gitops/flux/README.md index 1b4b9f37..e6de5560 100644 --- a/terraform/gitops/flux/README.md +++ b/terraform/gitops/flux/README.md @@ -31,9 +31,9 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [flux\_helm\_version](#input\_flux\_helm\_version) | The version of Flux Helm chart to install | `string` | `"2.16.2"` | no | +| [flux\_helm\_version](#input\_flux\_helm\_version) | The version of Flux Helm chart to install | `string` | `"2.16.3"` | no | | [flux\_namespace](#input\_flux\_namespace) | The namespace in which Flux will be installed | `string` | `"system-gitops"` | no | -| [flux\_version](#input\_flux\_version) | The version of Flux to install | `string` | `"2.6.3"` | no | +| [flux\_version](#input\_flux\_version) | The version of Flux to install | `string` | `"2.6.4"` | no | | [git\_auth\_secret](#input\_git\_auth\_secret) | The name of the secret to store the git authentication details | `string` | `"flux-system"` | no | | [git\_password](#input\_git\_password) | The git password or PAT used to authenticte with the git provider | `string` | `""` | no | | [git\_username](#input\_git\_username) | The git user to use to authenticte with the git provider | `string` | `"git"` | no |