diff --git a/examples/three/README.md b/examples/three/README.md new file mode 100644 index 0000000..5885b53 --- /dev/null +++ b/examples/three/README.md @@ -0,0 +1,11 @@ +# Three + +This module was developed working closely with specific customer feedback. + +## Goals + +- three node HA Rancher cluster where each node has all Kubernetes roles +- the ability to specify a helm repo for the Rancher install (specifically the prime repo) +- the ability to specify custom values for Rancher helm chart +- the ability to use a remote backend, updating the infrastructure using a CI tool + diff --git a/examples/three/main.tf b/examples/three/main.tf index 65eda74..a36ee91 100644 --- a/examples/three/main.tf +++ b/examples/three/main.tf @@ -24,8 +24,8 @@ provider "rancher2" { terraform { backend "s3" { - # This needs to be set in the backend configs on the command line. - # bucket = local.identifier + # This needs to be set in the backend configs on the command line or somewhere that your identifier can be set. + # terraform init -reconfigure -backend-config="bucket=" # https://developer.hashicorp.com/terraform/language/backend/s3 # https://developer.hashicorp.com/terraform/language/backend#partial-configuration key = "tfstate" @@ -62,10 +62,25 @@ locals { acme_server_url = "https://acme-v02.api.letsencrypt.org" owner = var.owner rke2_version = var.rke2_version + rancher_helm_repo = "https://releases.rancher.com/server-charts" + rancher_helm_channel = "stable" + helm_chart_strategy = "provide" + # These options use the Let's Encrypt cert that the module generates for you when you deploy the VPC and Domain. + # WARNING! "hostname" must be an fqdn + helm_chart_values = { + "hostname" = "${local.domain}.${local.zone}" + "replicas" = "2" + "bootstrapPassword" = "admin" + "ingress.enabled" = "true" + "ingress.tls.source" = "secret" + "ingress.tls.secretName" = "tls-rancher-ingress" + "privateCA" = "true" + "agentTLSMode" = "system-store" + } local_file_path = var.file_path runner_ip = chomp(data.http.myip.response_body) # "runner" is the server running Terraform rancher_version = var.rancher_version - cert_manager_version = "1.16.3" # "1.13.1" + cert_manager_version = "1.18.1" #"1.16.3" os = "sle-micro-61" } @@ -115,8 +130,13 @@ module "rancher" { } } # rancher - cert_manager_version = local.cert_manager_version - rancher_version = local.rancher_version + cert_manager_version = local.cert_manager_version + configure_cert_manager = false # use the cert generated at the project level + rancher_version = local.rancher_version + rancher_helm_repo = local.rancher_helm_repo + rancher_helm_channel = local.rancher_helm_channel + rancher_helm_chart_use_strategy = local.helm_chart_strategy + rancher_helm_chart_values = local.helm_chart_values } data "rancher2_cluster" "local" { diff --git a/flake.lock b/flake.lock index 69bc1d0..babb333 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1751180975, - "narHash": "sha256-BKk4yDiXr4LdF80OTVqYJ53Q74rOcA/82EClXug8xsY=", + "lastModified": 1751852175, + "narHash": "sha256-+MLlfTCCOvz4K6AcSPbaPiFM9MYi7fA2Wr1ibmRwIlM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a48741b083d4f36dd79abd9f760c84da6b4dc0e5", + "rev": "2defa37146df235ef62f566cde69930a86f14df1", "type": "github" }, "original": { diff --git a/main.tf b/main.tf index ad79e45..92a5689 100644 --- a/main.tf +++ b/main.tf @@ -28,14 +28,19 @@ locals { cni = var.cni node_configuration = var.node_configuration # rancher - cert_manager_version = var.cert_manager_version - rancher_version = var.rancher_version - ip_family = "ipv4" - # ingress_controller = "nginx" - bootstrap_rancher = var.bootstrap_rancher - install_cert_manager = var.install_cert_manager - configure_cert_manager = var.configure_cert_manager - cert_manager_config = var.cert_manager_configuration + cert_name = (var.tls_cert_name != "" ? var.tls_cert_name : module.cluster.cert.name) + cert_key = (var.tls_cert_key != "" ? var.tls_cert_key : module.cluster.cert.key_id) + cert_manager_version = var.cert_manager_version + rancher_version = var.rancher_version + rancher_helm_repo = var.rancher_helm_repo + rancher_helm_channel = var.rancher_helm_channel + ip_family = "ipv4" + rancher_helm_chart_values = var.rancher_helm_chart_values + rancher_helm_chart_use_strategy = var.rancher_helm_chart_use_strategy + bootstrap_rancher = var.bootstrap_rancher + install_cert_manager = var.install_cert_manager + configure_cert_manager = var.configure_cert_manager + cert_manager_config = var.cert_manager_configuration } data "aws_route53_zone" "zone" { @@ -59,7 +64,6 @@ module "cluster" { cni = local.cni node_configuration = local.node_configuration ip_family = local.ip_family - # ingress_controller = local.ingress_controller skip_cert_creation = local.skip_cert } @@ -72,8 +76,8 @@ module "install_cert_manager" { project_domain = local.fqdn zone = local.zone zone_id = data.aws_route53_zone.zone.zone_id - project_cert_name = module.cluster.cert.name - project_cert_key_id = module.cluster.cert.key_id + project_cert_name = local.cert_name + project_cert_key_id = local.cert_key path = local.local_file_path cert_manager_version = local.cert_manager_version configure_cert_manager = local.configure_cert_manager @@ -85,15 +89,19 @@ module "rancher_bootstrap" { module.cluster, module.install_cert_manager, ] - count = (local.bootstrap_rancher ? 1 : 0) - source = "./modules/rancher_bootstrap" - path = local.local_file_path - project_domain = local.fqdn - zone_id = data.aws_route53_zone.zone.zone_id - region = local.cert_manager_config.aws_region - email = local.cert_manager_config.acme_email - acme_server_url = local.cert_manager_config.acme_server_url - rancher_version = local.rancher_version - cert_manager_version = local.cert_manager_version - externalTLS = (local.configure_cert_manager ? false : true) + count = (local.bootstrap_rancher ? 1 : 0) + source = "./modules/rancher_bootstrap" + path = local.local_file_path + project_domain = local.fqdn + zone_id = data.aws_route53_zone.zone.zone_id + region = local.cert_manager_config.aws_region + email = local.cert_manager_config.acme_email + acme_server_url = local.cert_manager_config.acme_server_url + rancher_version = local.rancher_version + rancher_helm_repo = local.rancher_helm_repo + rancher_helm_channel = local.rancher_helm_channel + cert_manager_version = local.cert_manager_version + externalTLS = (local.configure_cert_manager ? false : true) + rancher_helm_chart_values = local.rancher_helm_chart_values + rancher_helm_chart_use_strategy = local.rancher_helm_chart_use_strategy } diff --git a/modules/cluster/main.tf b/modules/cluster/main.tf index 6582d64..9fc9a10 100644 --- a/modules/cluster/main.tf +++ b/modules/cluster/main.tf @@ -25,11 +25,6 @@ locals { var.file_path != "" ? (var.file_path == path.root ? "${path.root}/rke2" : var.file_path) : "${path.root}/rke2" ) - # # tflint-ignore: terraform_unused_declarations - # local_file_path_validate = (can(regex( - # "^\\.", - # local.local_file_path - # )) ? false : one([local.local_file_path, "local_file_path_must_be_relative"])) # used like this we can validate local variables install_method = var.install_method download = (local.install_method == "tar" ? "download" : "skip") @@ -182,8 +177,7 @@ module "deploy_initial_node" { user_workfolder = strcontains(each.value.os, "cis") ? "/var/tmp" : "/home/${local.username}" timeout = 10 }))}" - server_domain_name = "${substr("${local.project_name}-${md5(each.key)}", 0, 25)}" - server_domain_zone = "${local.zone}" + server_add_domain = false install_use_strategy = "${local.install_method}" local_file_use_strategy = "${local.download}" local_file_path = "${each.value.deploy_path}/configs" @@ -227,7 +221,7 @@ strcontains(each.value.type, "database") ? local.database_config : } # There are many ways to orchestrate Terraform configurations with the goal of breaking it down -# In this example I am using Terraform resources to orchestrate Terraform +# In this module I am using Terraform resources to orchestrate Terraform # I felt this was the best way to accomplish the goal without incurring additional dependencies module "deploy_additional_nodes" { source = "../deploy" @@ -271,8 +265,7 @@ module "deploy_additional_nodes" { user_workfolder = strcontains(each.value.os, "cis") ? "/var/tmp" : "/home/${local.username}" timeout = 10 }))}" - server_domain_name = "${substr("${local.project_name}-${md5(each.key)}", 0, 25)}" - server_domain_zone = "${local.zone}" + server_add_domain = false install_use_strategy = "${local.install_method}" local_file_use_strategy = "${local.download}" local_file_path = "${each.value.deploy_path}/configs" @@ -318,7 +311,7 @@ strcontains(each.value.type, "database") ? local.database_config : EOT } -resource "local_file" "kubeconfig" { +resource "local_sensitive_file" "kubeconfig" { depends_on = [ module.deploy_initial_node, module.deploy_additional_nodes, diff --git a/modules/cluster/variables.tf b/modules/cluster/variables.tf index c74dccb..521bff7 100644 --- a/modules/cluster/variables.tf +++ b/modules/cluster/variables.tf @@ -28,7 +28,7 @@ variable "zone" { # access variable "key_name" { type = string - description = "The name of an ssh key that already exists in AWS of that you want to create." + description = "The name of an ssh key that already exists in AWS." } variable "key" { type = string @@ -112,10 +112,6 @@ variable "ip_family" { type = string description = "The IP family to use. Must be 'ipv4', 'ipv6', or 'dualstack'." } -# variable "ingress_controller" { -# type = string -# description = "The ingress controller to use. Must be 'nginx' or 'traefik'. Currently only supports 'nginx'." -# } variable "skip_cert_creation" { type = bool description = "Skip the generation of a certificate, useful when configuring cert manager." diff --git a/modules/install_cert_manager/configured/variables.tf b/modules/install_cert_manager/configured/variables.tf index 4cd3566..2aac592 100644 --- a/modules/install_cert_manager/configured/variables.tf +++ b/modules/install_cert_manager/configured/variables.tf @@ -3,7 +3,6 @@ variable "cert_manager_version" { description = <<-EOT The version of cert manager to install. EOT - default = "v1.13.1" } variable "cert_manager_configuration" { type = object({ @@ -18,13 +17,7 @@ variable "cert_manager_configuration" { https://cert-manager.io/docs/configuration/acme/dns01/route53/#ambient-credentials https://docs.aws.amazon.com/sdkref/latest/guide/environment-variables.html EOT - default = { - aws_region = "" - aws_session_token = "" - aws_access_key_id = "" - aws_secret_access_key = "" - } - sensitive = true + sensitive = true } variable "zone" { type = string diff --git a/modules/install_cert_manager/main.tf b/modules/install_cert_manager/main.tf index 27a5a0c..1d1ec1c 100644 --- a/modules/install_cert_manager/main.tf +++ b/modules/install_cert_manager/main.tf @@ -30,13 +30,12 @@ module "deploy_cert_manager" { KUBECONFIG = "${abspath(local.path)}/kubeconfig" } inputs = <<-EOT + cert_manager_version = "${local.cert_manager_version}" + project_cert_name = "${local.project_cert_name}" + project_cert_key_id = "${local.project_cert_key_id}" project_domain = "${local.rancher_domain}" zone = "${local.zone}" zone_id = "${local.zone_id}" - project_cert_name = "${local.project_cert_name}" - project_cert_key_id = "${local.project_cert_key_id}" - cert_manager_version = "${local.cert_manager_version}" - configure_cert_manager = "${local.configure_cert_manager}" cert_manager_configuration = { aws_region = "${local.cert_manager_config.aws_region}" aws_session_token = "${local.cert_manager_config.aws_session_token}" diff --git a/modules/install_cert_manager/unconfigured/variables.tf b/modules/install_cert_manager/unconfigured/variables.tf index 689bf10..4cf8eed 100644 --- a/modules/install_cert_manager/unconfigured/variables.tf +++ b/modules/install_cert_manager/unconfigured/variables.tf @@ -3,19 +3,16 @@ variable "cert_manager_version" { description = <<-EOT The version of cert manager to install. EOT - default = "v1.13.1" } variable "project_cert_key_id" { type = string description = <<-EOT The key name to retrieve the project's cert's private key from AWS EOT - default = "" } variable "project_cert_name" { type = string description = <<-EOT The project's cert name EOT - default = "" } diff --git a/modules/install_cert_manager/variables.tf b/modules/install_cert_manager/variables.tf index 097dd91..f093fb0 100644 --- a/modules/install_cert_manager/variables.tf +++ b/modules/install_cert_manager/variables.tf @@ -45,7 +45,6 @@ variable "cert_manager_version" { description = <<-EOT The version of cert manager to install. EOT - default = "v1.13.1" } variable "configure_cert_manager" { type = bool @@ -75,14 +74,3 @@ variable "cert_manager_configuration" { } sensitive = true } -# variable "backend_file" { -# type = string -# description = <<-EOT -# Path to a .tfbackend file. -# This allows the user to pass a backend file. -# The backend file will be added to the terraform run and will allow state data to be saved remotely. -# Please note that this is a separate state file, and this backend should be independent of the main module's state and any other submodules' states. -# See https://developer.hashicorp.com/terraform/language/backend#file for more information. -# EOT -# default = "" -# } diff --git a/modules/persist_file/archive.sh b/modules/persist_file/archive.sh deleted file mode 100755 index 25aa818..0000000 --- a/modules/persist_file/archive.sh +++ /dev/null @@ -1,25 +0,0 @@ -# !/bin/bash -set -e - - -# This script can compress text into smaller text or decompress that text back to its original form -# this requires: xz, openssl, jq, bash, and core linux utils (echo, redirection, pipe) -JSON_INPUT="$(jq -r '.')" -COMPRESS="$(jq -r '.compress' <<<"$JSON_INPUT")" -DECOMPRESS="$(jq -r '.decompress' <<<"$JSON_INPUT")" -DATA="$(jq -r '.contents' <<<"$JSON_INPUT")" - -if [ -n "$COMPRESS" ] && [ "null" != "$COMPRESS" ]; then - ENCODED_OUTPUT="$(printf "%s" "$DATA" | xz -c -9 -e -T0 | openssl base64 -A -)" -fi - -if [ -n "$DECOMPRESS" ] && [ "null" != "$DECOMPRESS" ]; then - ENCODED_OUTPUT="$(echo -n "$DATA" | openssl base64 -d -A | xz -dc | openssl base64 -A -)" -fi - -if [ -z "$ENCODED_OUTPUT" ]; then - echo "output is empty" >&2 - exit 1 -fi - -jq -n --arg data "$ENCODED_OUTPUT" '{"data": $data}' diff --git a/modules/persist_file/main.tf b/modules/persist_file/main.tf index d9b2199..5eda740 100644 --- a/modules/persist_file/main.tf +++ b/modules/persist_file/main.tf @@ -42,7 +42,7 @@ resource "terraform_data" "snapshot" { ] } -resource "local_file" "file" { +resource "local_sensitive_file" "file" { depends_on = [ data.external.read_file, terraform_data.recreate, diff --git a/modules/persist_file/make_holders.sh b/modules/persist_file/make_holders.sh deleted file mode 100755 index 3de24dd..0000000 --- a/modules/persist_file/make_holders.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash -set -e -INPUTS="$(jq -r '.')" -FILENAMEFILE="$(jq -r '.filename_file' <<<"$INPUTS")" -NEWFILE="$(jq -r '.filename' <<<"$INPUTS")" - -if [ -z "$FILENAMEFILE" ]; then - echo "filename_file required" >&2 - exit 1 -fi - -if [ -z "$NEWFILE" ]; then - echo "filename required" >&2 - exit 1 -fi - -install -d "$(dirname "$FILENAMEFILE")" -touch "$FILENAMEFILE" - -# grep returns 1 if the pattern isn't found, so we need to ignore the "failure" here -NEW="$(grep -l "$NEWFILE" "$FILENAMEFILE" || true)" - -if [ -z "$NEW" ]; then - echo "$NEWFILE" >> "$FILENAMEFILE" -fi - -while read -r FILEPATH; do - if [ -z "$FILEPATH" ]; then continue; fi # ignore empty lines - DIRECTORY="$(dirname "$FILEPATH")" - install -d "$DIRECTORY" - touch "$FILEPATH" -done < "$FILENAMEFILE" - -jq -n '{"outcome": "success"}' diff --git a/modules/persist_file/outputs.tf b/modules/persist_file/outputs.tf index 0c697ae..e5f5bcf 100644 --- a/modules/persist_file/outputs.tf +++ b/modules/persist_file/outputs.tf @@ -1,7 +1,4 @@ -# output "encoded_contents" { -# value = base64encode(filesystem_file_writer.file.contents) -# } - output "contents" { - value = local_file.file.content #filesystem_file_writer.file.contents + value = local_sensitive_file.file.content + sensitive = true } diff --git a/modules/persist_file/read_file.sh b/modules/persist_file/read_file.sh index a647f53..cd24a5f 100755 --- a/modules/persist_file/read_file.sh +++ b/modules/persist_file/read_file.sh @@ -1,4 +1,4 @@ -# !/bin/bash +#!/bin/bash set -e JSON_INPUT="$(jq -r '.')" diff --git a/modules/persist_file/variables.tf b/modules/persist_file/variables.tf index 02da55e..d9914e1 100644 --- a/modules/persist_file/variables.tf +++ b/modules/persist_file/variables.tf @@ -16,6 +16,7 @@ variable "contents" { The contents to persist, one of "contents" or "sourcefile" must be given. EOT default = "" + sensitive = true } variable "sourcefile" { type = string @@ -23,4 +24,5 @@ variable "sourcefile" { A file to persist, one of "contents" or "sourcefile" must be given. EOT default = "" + sensitive = true } diff --git a/modules/rancher_bootstrap/main.tf b/modules/rancher_bootstrap/main.tf index 8e968bb..4b89258 100644 --- a/modules/rancher_bootstrap/main.tf +++ b/modules/rancher_bootstrap/main.tf @@ -3,17 +3,21 @@ # I felt this was the best way to accomplish the goal without incurring additional dependencies locals { - project_domain = var.project_domain - zone_id = var.zone_id - region = var.region - email = var.email - acme_server_url = var.acme_server_url - rancher_version = replace(var.rancher_version, "v", "") # don't include the v - cert_manager_version = var.cert_manager_version - path = var.path - externalTLS = var.externalTLS - rancher_path = (local.externalTLS ? "${path.module}/rancher_externalTLS" : "${path.module}/rancher") - deploy_path = "${local.path}/rancher_bootstrap" + project_domain = var.project_domain + zone_id = var.zone_id + region = var.region + email = var.email + acme_server_url = var.acme_server_url + rancher_version = replace(var.rancher_version, "v", "") # don't include the v + rancher_helm_repo = var.rancher_helm_repo + rancher_helm_channel = var.rancher_helm_channel + cert_manager_version = var.cert_manager_version + path = var.path + externalTLS = var.externalTLS + rancher_path = (local.externalTLS ? "${path.module}/rancher_externalTLS" : "${path.module}/rancher") + deploy_path = "${local.path}/rancher_bootstrap" + rancher_helm_chart_values = var.rancher_helm_chart_values + rancher_helm_chart_use_strategy = var.rancher_helm_chart_use_strategy } module "deploy_rancher" { @@ -29,12 +33,16 @@ module "deploy_rancher" { KUBE_CONFIG_PATH = "${local.path}/kubeconfig" } inputs = <<-EOT - project_domain = "${local.project_domain}" - zone_id = "${local.zone_id}" - region = "${local.region}" - email = "${local.email}" - rancher_version = "${local.rancher_version}" - cert_manager_version = "${local.cert_manager_version}" - acme_server_url = "${local.acme_server_url}" + project_domain = "${local.project_domain}" + rancher_version = "${local.rancher_version}" + rancher_helm_repo = "${local.rancher_helm_repo}" + rancher_helm_channel = "${local.rancher_helm_channel}" + rancher_helm_chart_use_strategy = "${local.rancher_helm_chart_use_strategy}" + rancher_helm_chart_values = "${base64encode(jsonencode(local.rancher_helm_chart_values))}" + zone_id = "${local.zone_id}" + region = "${local.region}" + email = "${local.email}" + cert_manager_version = "${local.cert_manager_version}" + acme_server_url = "${local.acme_server_url}" EOT } diff --git a/modules/rancher_bootstrap/rancher/build_chart.sh b/modules/rancher_bootstrap/rancher/build_chart.sh deleted file mode 100755 index 60a23d2..0000000 --- a/modules/rancher_bootstrap/rancher/build_chart.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash - -set -x - -RANCHER_VERSION="$1" -if [ -z "$RANCHER_VERSION" ]; then echo "you must send the Rancher version"; exit 1; fi -if [ "${RANCHER_VERSION:0:1}" != "v" ]; then RANCHER_VERSION="v$RANCHER_VERSION"; fi - -git clone https://github.com/rancher/rancher.git -cd rancher || exit 1 -git checkout "$RANCHER_VERSION" -cd .. || exit 1 -mv rancher/chart ./chart -mv rancher/build.yaml ./config.yaml -rm -rf rancher -mv chart rancher - -VERSION="$RANCHER_VERSION" -CHART_VERSION="$(echo "$RANCHER_VERSION" | tr -d 'v')" -CONFIG="config.yaml" - -CATTLE_DEFAULT_SHELL_VERSION=$(yq -r -e '.defaultShellVersion' "$CONFIG") - -sed -i -e "s/%VERSION%/$CHART_VERSION/g" rancher/Chart.yaml -sed -i -e "s/%APP_VERSION%/$VERSION/g" rancher/Chart.yaml - -post_delete_base="$CATTLE_DEFAULT_SHELL_VERSION" -post_delete_image_name=$(echo "$post_delete_base" | tr -d '"' | cut -d ":" -f 1) || true; -post_delete_image_tag=$(echo "$post_delete_base" | tr -d '"' | cut -d ":" -f 2) || true; -if [[ ! $post_delete_image_name =~ ^rancher\/.+ ]]; then - echo "The image name [$post_delete_image_name] is invalid. Its prefix should be rancher/" - exit 1 -fi -if [[ ! $post_delete_image_tag =~ ^v.+ ]]; then - echo "The image tag [$post_delete_image_tag] is invalid. It should start with the letter v" - exit 1 -fi -# image name has slashes in it and image tag has at symbols in it -sed -i -e "s@%POST_DELETE_IMAGE_NAME%@$post_delete_image_name@g" rancher/values.yaml -sed -i -e "s/%POST_DELETE_IMAGE_TAG%/$post_delete_image_tag/g" rancher/values.yaml - -helm lint "$(pwd)/rancher" - -helm package "$(pwd)/rancher" diff --git a/modules/rancher_bootstrap/rancher/main.tf b/modules/rancher_bootstrap/rancher/main.tf index c4f2c9a..b306aac 100644 --- a/modules/rancher_bootstrap/rancher/main.tf +++ b/modules/rancher_bootstrap/rancher/main.tf @@ -9,7 +9,7 @@ locals { rancher_helm_channel = var.rancher_helm_channel rancher_version = replace(var.rancher_version, "v", "") # don't include the v helm_chart_use_strategy = var.rancher_helm_chart_use_strategy - rancher_helm_chart_values = var.rancher_helm_chart_values + rancher_helm_chart_values = jsondecode(base64decode(var.rancher_helm_chart_values)) default_hc_values = { "hostname" = local.rancher_domain "replicas" = "1" @@ -185,6 +185,7 @@ resource "helm_release" "rancher" { for_each = local.helm_chart_values content { name = set.key + type = "string" value = set.value } } diff --git a/modules/rancher_bootstrap/rancher/variables.tf b/modules/rancher_bootstrap/rancher/variables.tf index 6670339..caadbc3 100644 --- a/modules/rancher_bootstrap/rancher/variables.tf +++ b/modules/rancher_bootstrap/rancher/variables.tf @@ -11,6 +11,12 @@ variable "project_domain" { error_message = "Must be a fully qualified domain name." } } +variable "rancher_version" { + type = string + description = <<-EOT + The version of rancher to install. + EOT +} variable "rancher_helm_repo" { type = string description = <<-EOT @@ -38,27 +44,27 @@ variable "rancher_helm_chart_use_strategy" { default = "default" } variable "rancher_helm_chart_values" { - type = map(any) + type = string description = <<-EOT - A key/value map of Helm arguments to pass to the Rancher helm chart. + A base64 encoded, json encoded key/value map of Helm arguments to pass to the Rancher helm chart. This will be ignored if the rancher_helm_chart_use_strategy argument is set to "default". eg. { - "hostname" = "test.example.com" - "replicas" = "1" - "bootstrapPassword" = "password" - "ingress.enabled" = "true" - "ingress.tls.source" = "letsEncrypt" - "tls" = "ingress" - "letsEncrypt.ingress.class" = "nginx" - "letsEncrypt.environment" = "production" - "letsEncrypt.email" = "test@example.com" - "certmanager.version" = "1.13.1" - "agentTLSMode" = "system-store" - "ingress.extraAnnotations.cert-manager\\.io\\/issuer" = "rancher" + "hostname" : "test.example.com", + "replicas" : "1", + "bootstrapPassword" : "password", + "ingress.enabled" : "true", + "ingress.tls.source" : "letsEncrypt", + "tls" : "ingress", + "letsEncrypt.ingress.class" : "nginx", + "letsEncrypt.environment" : "production", + "letsEncrypt.email" : "test@example.com", + "certmanager.version" : "1.13.1", + "agentTLSMode" : "system-store", + "ingress.extraAnnotations.cert-manager.io/issuer" : "rancher" } EOT - default = {} + default = "{}" } variable "zone_id" { type = string @@ -80,13 +86,6 @@ variable "email" { The email to use when registering an account with Let's Encrypt. EOT } -variable "rancher_version" { - type = string - description = <<-EOT - The version of rancher to install. - EOT - default = "2.11.2" -} variable "cert_manager_version" { type = string description = <<-EOT diff --git a/modules/rancher_bootstrap/rancher_externalTLS/build_chart.sh b/modules/rancher_bootstrap/rancher_externalTLS/build_chart.sh deleted file mode 100755 index 60a23d2..0000000 --- a/modules/rancher_bootstrap/rancher_externalTLS/build_chart.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash - -set -x - -RANCHER_VERSION="$1" -if [ -z "$RANCHER_VERSION" ]; then echo "you must send the Rancher version"; exit 1; fi -if [ "${RANCHER_VERSION:0:1}" != "v" ]; then RANCHER_VERSION="v$RANCHER_VERSION"; fi - -git clone https://github.com/rancher/rancher.git -cd rancher || exit 1 -git checkout "$RANCHER_VERSION" -cd .. || exit 1 -mv rancher/chart ./chart -mv rancher/build.yaml ./config.yaml -rm -rf rancher -mv chart rancher - -VERSION="$RANCHER_VERSION" -CHART_VERSION="$(echo "$RANCHER_VERSION" | tr -d 'v')" -CONFIG="config.yaml" - -CATTLE_DEFAULT_SHELL_VERSION=$(yq -r -e '.defaultShellVersion' "$CONFIG") - -sed -i -e "s/%VERSION%/$CHART_VERSION/g" rancher/Chart.yaml -sed -i -e "s/%APP_VERSION%/$VERSION/g" rancher/Chart.yaml - -post_delete_base="$CATTLE_DEFAULT_SHELL_VERSION" -post_delete_image_name=$(echo "$post_delete_base" | tr -d '"' | cut -d ":" -f 1) || true; -post_delete_image_tag=$(echo "$post_delete_base" | tr -d '"' | cut -d ":" -f 2) || true; -if [[ ! $post_delete_image_name =~ ^rancher\/.+ ]]; then - echo "The image name [$post_delete_image_name] is invalid. Its prefix should be rancher/" - exit 1 -fi -if [[ ! $post_delete_image_tag =~ ^v.+ ]]; then - echo "The image tag [$post_delete_image_tag] is invalid. It should start with the letter v" - exit 1 -fi -# image name has slashes in it and image tag has at symbols in it -sed -i -e "s@%POST_DELETE_IMAGE_NAME%@$post_delete_image_name@g" rancher/values.yaml -sed -i -e "s/%POST_DELETE_IMAGE_TAG%/$post_delete_image_tag/g" rancher/values.yaml - -helm lint "$(pwd)/rancher" - -helm package "$(pwd)/rancher" diff --git a/modules/rancher_bootstrap/rancher_externalTLS/main.tf b/modules/rancher_bootstrap/rancher_externalTLS/main.tf index 694a49a..81bfc6c 100644 --- a/modules/rancher_bootstrap/rancher_externalTLS/main.tf +++ b/modules/rancher_bootstrap/rancher_externalTLS/main.tf @@ -9,9 +9,9 @@ locals { rancher_helm_channel = var.rancher_helm_channel rancher_version = replace(var.rancher_version, "v", "") # don't include the v helm_chart_use_strategy = var.rancher_helm_chart_use_strategy - rancher_helm_chart_values = var.rancher_helm_chart_values + rancher_helm_chart_values = jsondecode(base64decode(var.rancher_helm_chart_values)) default_hc_values = { - "hostname" = local.rancher_domain + "hostname" = local.rancher_domain # must be an fqdn "replicas" = "1" "bootstrapPassword" = "admin" "ingress.enabled" = "true" @@ -21,10 +21,10 @@ locals { "agentTLSMode" = "system-store" } helm_chart_values = coalesce( # using coalesce like this essentially gives us a switch function - (local.helm_chart_use_strategy == "merge" ? - merge(local.default_hc_values, local.rancher_helm_chart_values) : null), (local.helm_chart_use_strategy == "default" ? local.default_hc_values : null), + (local.helm_chart_use_strategy == "merge" ? + merge(local.default_hc_values, local.rancher_helm_chart_values) : null), (local.helm_chart_use_strategy == "provide" ? local.rancher_helm_chart_values : null) ) # WARNING! Some config is necessary, if the result is an empty string the coalesce will fail @@ -98,6 +98,7 @@ resource "helm_release" "rancher" { for_each = local.helm_chart_values content { name = set.key + type = "string" value = set.value } } @@ -149,39 +150,40 @@ resource "terraform_data" "get_public_cert_info" { } } -resource "terraform_data" "get_ping" { - depends_on = [ - random_password.password, - time_sleep.settle_before_rancher, - terraform_data.wait_for_nginx, - helm_release.rancher, - terraform_data.wait_for_rancher, - terraform_data.get_public_cert_info, - ] - provisioner "local-exec" { - command = <<-EOT - check_letsencrypt_ca() { - # Try to verify a known Let's Encrypt certificate (you can use any valid one) - if openssl s_client -showcerts -connect letsencrypt.org:443 < /dev/null | openssl x509 -noout -issuer | grep -q "Let's Encrypt"; then - return 0 # Success - else - return 1 # Failure - fi - } - echo "Checking Let's Encrypt CA" - if check_letsencrypt_ca; then - echo "Let's Encrypt CA is functioning correctly." - else - echo "Error: Let's Encrypt CA is not being used for verification." - exit 1 - fi - echo "Checking Cert" - echo | openssl s_client -showcerts -servername ${local.rancher_domain} -connect "${local.rancher_domain}:443" 2>/dev/null | openssl x509 -inform pem -noout -text || true - echo "Checking Curl" - curl "https://${local.rancher_domain}/ping" - EOT - } -} +# this requires a let's encrypt certificate, which we would like to eventually not require +# resource "terraform_data" "get_ping" { +# depends_on = [ +# random_password.password, +# time_sleep.settle_before_rancher, +# terraform_data.wait_for_nginx, +# helm_release.rancher, +# terraform_data.wait_for_rancher, +# terraform_data.get_public_cert_info, +# ] +# provisioner "local-exec" { +# command = <<-EOT +# check_letsencrypt_ca() { +# # Try to verify a known Let's Encrypt certificate (you can use any valid one) +# if openssl s_client -showcerts -connect letsencrypt.org:443 < /dev/null | openssl x509 -noout -issuer | grep -q "Let's Encrypt"; then +# return 0 # Success +# else +# return 1 # Failure +# fi +# } +# echo "Checking Let's Encrypt CA" +# if check_letsencrypt_ca; then +# echo "Let's Encrypt CA is functioning correctly." +# else +# echo "Error: Let's Encrypt CA is not being used for verification." +# exit 1 +# fi +# echo "Checking Cert" +# echo | openssl s_client -showcerts -servername ${local.rancher_domain} -connect "${local.rancher_domain}:443" 2>/dev/null | openssl x509 -inform pem -noout -text || true +# echo "Checking Curl" +# curl "https://${local.rancher_domain}/ping" +# EOT +# } +# } resource "rancher2_bootstrap" "admin" { depends_on = [ @@ -191,7 +193,7 @@ resource "rancher2_bootstrap" "admin" { helm_release.rancher, terraform_data.wait_for_rancher, terraform_data.get_public_cert_info, - terraform_data.get_ping, + # terraform_data.get_ping, ] password = random_password.password.result } diff --git a/modules/rancher_bootstrap/rancher_externalTLS/variables.tf b/modules/rancher_bootstrap/rancher_externalTLS/variables.tf index 47c7fba..8dc5d07 100644 --- a/modules/rancher_bootstrap/rancher_externalTLS/variables.tf +++ b/modules/rancher_bootstrap/rancher_externalTLS/variables.tf @@ -45,21 +45,21 @@ variable "rancher_helm_chart_use_strategy" { default = "default" } variable "rancher_helm_chart_values" { - type = map(any) + type = string description = <<-EOT - A key/value map of Helm arguments to pass to the Rancher helm chart. + A base64 encoded, json encoded key/value map of Helm arguments to pass to the Rancher helm chart. This will be ignored if the rancher_helm_chart_use_strategy argument is set to "default". eg. { - "hostname" = local.rancher_domain - "replicas" = "1" - "bootstrapPassword" = "admin" - "ingress.enabled" = "true" - "ingress.tls.source" = "secret" - "ingress.tls.secretName" = "tls-rancher-ingress" - "privateCA" = "true" - "agentTLSMode" = "system-store" + "hostname" : "rancher.example.com", + "replicas" : "1", + "bootstrapPassword" : "admin", + "ingress.enabled" : "true", + "ingress.tls.source" : "secret", + "ingress.tls.secretName" : "tls-rancher-ingress", + "privateCA" : "true", + "agentTLSMode" : "system-store" } EOT - default = {} + default = "{}" } diff --git a/modules/rancher_bootstrap/variables.tf b/modules/rancher_bootstrap/variables.tf index 9866481..251488e 100644 --- a/modules/rancher_bootstrap/variables.tf +++ b/modules/rancher_bootstrap/variables.tf @@ -45,6 +45,21 @@ variable "rancher_version" { EOT default = "2.11.2" } +variable "rancher_helm_repo" { + type = string + description = <<-EOT + The Helm repository to retrieve charts from. + EOT + default = "https://releases.rancher.com/server-charts" +} +variable "rancher_helm_channel" { + type = string + description = <<-EOT + The Helm repository channel retrieve charts from. + Can be "latest" or "stable", defaults to "stable". + EOT + default = "stable" +} variable "cert_manager_version" { type = string description = <<-EOT @@ -66,3 +81,37 @@ variable "path" { The local file path to stage files for the deployment. EOT } +variable "rancher_helm_chart_use_strategy" { + type = string + description = <<-EOT + The strategy to use for Rancher's Helm chart values. + Options include: "default", "merge", or "provide". + Default will tell the module to use our suggested default configuration. + Merge will merge our default suggestions with your supplied configuration, anything you supply will override the default. + Provide will ignore our default suggestions and use the configuration provided in the rancher_helm_chart_values argument. + EOT + default = "default" + validation { + condition = contains(["default", "merge", "provide"], var.rancher_helm_chart_use_strategy) + error_message = "Must be one of 'default', 'merge', or 'provide'." + } +} +variable "rancher_helm_chart_values" { + type = map(any) + description = <<-EOT + A key/value map of Helm arguments to pass to the Rancher helm chart. + This will be ignored if the rancher_helm_chart_use_strategy argument is set to "default". + eg. + { + "hostname" = local.rancher_domain + "replicas" = "1" + "bootstrapPassword" = "admin" + "ingress.enabled" = "true" + "ingress.tls.source" = "secret" + "ingress.tls.secretName" = "tls-rancher-ingress" + "privateCA" = "true" + "agentTLSMode" = "system-store" + } + EOT + default = {} +} diff --git a/outputs.tf b/outputs.tf index fa000ce..2a79283 100644 --- a/outputs.tf +++ b/outputs.tf @@ -20,16 +20,6 @@ output "admin_password" { sensitive = true } -# output "additional_node_states" { -# value = module.cluster.additional_node_states -# sensitive = true -# } - -# output "rancher_bootstrap_state" { -# value = module.rancher_bootstrap[0].rancher_bootstrap_state -# sensitive = true -# } - output "vpc" { value = module.cluster.vpc } diff --git a/variables.tf b/variables.tf index 920911a..e088d08 100644 --- a/variables.tf +++ b/variables.tf @@ -27,7 +27,6 @@ variable "domain" { If left empty this will default to the project name. eg. "test" in "test.example.com" EOT - default = "" } variable "zone" { type = string @@ -150,14 +149,45 @@ variable "cert_manager_version" { description = <<-EOT The version of cert-manager to install. EOT - default = "v1.18.1" # "v1.13.1" + default = "v1.18.1" # "v1.13.1" # "1.16.3" +} +variable "tls_cert_name" { + type = string + description = <<-EOT + The name of an AWS IAM Server Certificate where the public cert is stored. + This is only used when supplying your own TLS certificate. + EOT + default = "" +} +variable "tls_cert_key" { + type = string + description = <<-EOT + The name of an AWS SecretsManager Secret where the private key is stored. + This is only used when supplying your own TLS certificate. + EOT + default = "" } variable "rancher_version" { type = string description = <<-EOT - The version of Rancher to install. + The version of rancher to install. EOT - default = "2.9.1" + default = "2.11.2" +} +variable "rancher_helm_repo" { + type = string + description = <<-EOT + The Helm repository to retrieve charts from. + EOT + default = "https://releases.rancher.com/server-charts" +} +variable "rancher_helm_channel" { + type = string + description = <<-EOT + The Helm repository channel retrieve charts from. + Can be "latest" or "stable", defaults to "stable". + EOT + default = "stable" } variable "bootstrap_rancher" { type = bool @@ -209,27 +239,37 @@ variable "cert_manager_configuration" { } sensitive = true } -# variable "install_cert_manager_backend" { -# type = string -# description = <<-EOT -# Path to a .tfbackend file. -# This allows the user to pass a backend file to the install_cert_manager submodule. -# The backend file will be added to the submodule's terraform run and will allow that module's state data to be saved remotely. -# Please note that this is a separate state file, and this backend should be independent of the main module's state and any other submodules' states. -# See https://developer.hashicorp.com/terraform/language/backend#file for more information. -# The default is to use a local state file. -# EOT -# default = "" -# } -# variable "rancher_bootstrap_backend" { -# type = string -# description = <<-EOT -# Path to a .tfbackend file. -# This allows the user to pass a backend file to the rancher_bootstrap submodule. -# The backend file will be added to the submodule's terraform run and will allow that module's state data to be saved remotely. -# Please note that this is a separate state file, and this backend should be independent of the main module's state and any other submodules' states. -# See https://developer.hashicorp.com/terraform/language/backend#file for more information. -# The default is to use a local state file. -# EOT -# default = "" -# } +variable "rancher_helm_chart_use_strategy" { + type = string + description = <<-EOT + The strategy to use for Rancher's Helm chart values. + Options include: "default", "merge", or "provide". + Default will tell the module to use our suggested default configuration. + Merge will merge our default suggestions with your supplied configuration, anything you supply will override the default. + Provide will ignore our default suggestions and use the configuration provided in the rancher_helm_chart_values argument. + EOT + default = "default" + validation { + condition = contains(["default", "merge", "provide"], var.rancher_helm_chart_use_strategy) + error_message = "Must be one of 'default', 'merge', or 'provide'." + } +} +variable "rancher_helm_chart_values" { + type = map(any) + description = <<-EOT + A key/value map of Helm arguments to pass to the Rancher helm chart. + This will be ignored if the rancher_helm_chart_use_strategy argument is set to "default". + eg. + { + "hostname" = local.rancher_domain + "replicas" = "1" + "bootstrapPassword" = "admin" + "ingress.enabled" = "true" + "ingress.tls.source" = "secret" + "ingress.tls.secretName" = "tls-rancher-ingress" + "privateCA" = "true" + "agentTLSMode" = "system-store" + } + EOT + default = {} +}