feat: enable rancher helm chart values

Signed-off-by: matttrach <matt.trachier@suse.com>
This commit is contained in:
matttrach 2025-07-08 10:50:16 -05:00
parent 3df73bf72b
commit ad853e2435
No known key found for this signature in database
GPG Key ID: E082F2592F87D4AE
26 changed files with 301 additions and 355 deletions

11
examples/three/README.md Normal file
View File

@ -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

View File

@ -24,8 +24,8 @@ provider "rancher2" {
terraform { terraform {
backend "s3" { backend "s3" {
# This needs to be set in the backend configs on the command line. # This needs to be set in the backend configs on the command line or somewhere that your identifier can be set.
# bucket = local.identifier # terraform init -reconfigure -backend-config="bucket=<identifier>"
# https://developer.hashicorp.com/terraform/language/backend/s3 # https://developer.hashicorp.com/terraform/language/backend/s3
# https://developer.hashicorp.com/terraform/language/backend#partial-configuration # https://developer.hashicorp.com/terraform/language/backend#partial-configuration
key = "tfstate" key = "tfstate"
@ -62,10 +62,25 @@ locals {
acme_server_url = "https://acme-v02.api.letsencrypt.org" acme_server_url = "https://acme-v02.api.letsencrypt.org"
owner = var.owner owner = var.owner
rke2_version = var.rke2_version 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 local_file_path = var.file_path
runner_ip = chomp(data.http.myip.response_body) # "runner" is the server running Terraform runner_ip = chomp(data.http.myip.response_body) # "runner" is the server running Terraform
rancher_version = var.rancher_version 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" os = "sle-micro-61"
} }
@ -116,7 +131,12 @@ module "rancher" {
} }
# rancher # rancher
cert_manager_version = local.cert_manager_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_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" { data "rancher2_cluster" "local" {

View File

@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1751180975, "lastModified": 1751852175,
"narHash": "sha256-BKk4yDiXr4LdF80OTVqYJ53Q74rOcA/82EClXug8xsY=", "narHash": "sha256-+MLlfTCCOvz4K6AcSPbaPiFM9MYi7fA2Wr1ibmRwIlM=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "a48741b083d4f36dd79abd9f760c84da6b4dc0e5", "rev": "2defa37146df235ef62f566cde69930a86f14df1",
"type": "github" "type": "github"
}, },
"original": { "original": {

16
main.tf
View File

@ -28,10 +28,15 @@ locals {
cni = var.cni cni = var.cni
node_configuration = var.node_configuration node_configuration = var.node_configuration
# rancher # rancher
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 cert_manager_version = var.cert_manager_version
rancher_version = var.rancher_version rancher_version = var.rancher_version
rancher_helm_repo = var.rancher_helm_repo
rancher_helm_channel = var.rancher_helm_channel
ip_family = "ipv4" ip_family = "ipv4"
# ingress_controller = "nginx" 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 bootstrap_rancher = var.bootstrap_rancher
install_cert_manager = var.install_cert_manager install_cert_manager = var.install_cert_manager
configure_cert_manager = var.configure_cert_manager configure_cert_manager = var.configure_cert_manager
@ -59,7 +64,6 @@ module "cluster" {
cni = local.cni cni = local.cni
node_configuration = local.node_configuration node_configuration = local.node_configuration
ip_family = local.ip_family ip_family = local.ip_family
# ingress_controller = local.ingress_controller
skip_cert_creation = local.skip_cert skip_cert_creation = local.skip_cert
} }
@ -72,8 +76,8 @@ module "install_cert_manager" {
project_domain = local.fqdn project_domain = local.fqdn
zone = local.zone zone = local.zone
zone_id = data.aws_route53_zone.zone.zone_id zone_id = data.aws_route53_zone.zone.zone_id
project_cert_name = module.cluster.cert.name project_cert_name = local.cert_name
project_cert_key_id = module.cluster.cert.key_id project_cert_key_id = local.cert_key
path = local.local_file_path path = local.local_file_path
cert_manager_version = local.cert_manager_version cert_manager_version = local.cert_manager_version
configure_cert_manager = local.configure_cert_manager configure_cert_manager = local.configure_cert_manager
@ -94,6 +98,10 @@ module "rancher_bootstrap" {
email = local.cert_manager_config.acme_email email = local.cert_manager_config.acme_email
acme_server_url = local.cert_manager_config.acme_server_url acme_server_url = local.cert_manager_config.acme_server_url
rancher_version = local.rancher_version 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 cert_manager_version = local.cert_manager_version
externalTLS = (local.configure_cert_manager ? false : true) 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
} }

View File

@ -25,11 +25,6 @@ locals {
var.file_path != "" ? (var.file_path == path.root ? "${path.root}/rke2" : var.file_path) : var.file_path != "" ? (var.file_path == path.root ? "${path.root}/rke2" : var.file_path) :
"${path.root}/rke2" "${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 install_method = var.install_method
download = (local.install_method == "tar" ? "download" : "skip") 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}" user_workfolder = strcontains(each.value.os, "cis") ? "/var/tmp" : "/home/${local.username}"
timeout = 10 timeout = 10
}))}" }))}"
server_domain_name = "${substr("${local.project_name}-${md5(each.key)}", 0, 25)}" server_add_domain = false
server_domain_zone = "${local.zone}"
install_use_strategy = "${local.install_method}" install_use_strategy = "${local.install_method}"
local_file_use_strategy = "${local.download}" local_file_use_strategy = "${local.download}"
local_file_path = "${each.value.deploy_path}/configs" 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 # 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 # I felt this was the best way to accomplish the goal without incurring additional dependencies
module "deploy_additional_nodes" { module "deploy_additional_nodes" {
source = "../deploy" source = "../deploy"
@ -271,8 +265,7 @@ module "deploy_additional_nodes" {
user_workfolder = strcontains(each.value.os, "cis") ? "/var/tmp" : "/home/${local.username}" user_workfolder = strcontains(each.value.os, "cis") ? "/var/tmp" : "/home/${local.username}"
timeout = 10 timeout = 10
}))}" }))}"
server_domain_name = "${substr("${local.project_name}-${md5(each.key)}", 0, 25)}" server_add_domain = false
server_domain_zone = "${local.zone}"
install_use_strategy = "${local.install_method}" install_use_strategy = "${local.install_method}"
local_file_use_strategy = "${local.download}" local_file_use_strategy = "${local.download}"
local_file_path = "${each.value.deploy_path}/configs" local_file_path = "${each.value.deploy_path}/configs"
@ -318,7 +311,7 @@ strcontains(each.value.type, "database") ? local.database_config :
EOT EOT
} }
resource "local_file" "kubeconfig" { resource "local_sensitive_file" "kubeconfig" {
depends_on = [ depends_on = [
module.deploy_initial_node, module.deploy_initial_node,
module.deploy_additional_nodes, module.deploy_additional_nodes,

View File

@ -28,7 +28,7 @@ variable "zone" {
# access # access
variable "key_name" { variable "key_name" {
type = string 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" { variable "key" {
type = string type = string
@ -112,10 +112,6 @@ variable "ip_family" {
type = string type = string
description = "The IP family to use. Must be 'ipv4', 'ipv6', or 'dualstack'." 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" { variable "skip_cert_creation" {
type = bool type = bool
description = "Skip the generation of a certificate, useful when configuring cert manager." description = "Skip the generation of a certificate, useful when configuring cert manager."

View File

@ -3,7 +3,6 @@ variable "cert_manager_version" {
description = <<-EOT description = <<-EOT
The version of cert manager to install. The version of cert manager to install.
EOT EOT
default = "v1.13.1"
} }
variable "cert_manager_configuration" { variable "cert_manager_configuration" {
type = object({ type = object({
@ -18,12 +17,6 @@ variable "cert_manager_configuration" {
https://cert-manager.io/docs/configuration/acme/dns01/route53/#ambient-credentials https://cert-manager.io/docs/configuration/acme/dns01/route53/#ambient-credentials
https://docs.aws.amazon.com/sdkref/latest/guide/environment-variables.html https://docs.aws.amazon.com/sdkref/latest/guide/environment-variables.html
EOT EOT
default = {
aws_region = ""
aws_session_token = ""
aws_access_key_id = ""
aws_secret_access_key = ""
}
sensitive = true sensitive = true
} }
variable "zone" { variable "zone" {

View File

@ -30,13 +30,12 @@ module "deploy_cert_manager" {
KUBECONFIG = "${abspath(local.path)}/kubeconfig" KUBECONFIG = "${abspath(local.path)}/kubeconfig"
} }
inputs = <<-EOT 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}" project_domain = "${local.rancher_domain}"
zone = "${local.zone}" zone = "${local.zone}"
zone_id = "${local.zone_id}" 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 = { cert_manager_configuration = {
aws_region = "${local.cert_manager_config.aws_region}" aws_region = "${local.cert_manager_config.aws_region}"
aws_session_token = "${local.cert_manager_config.aws_session_token}" aws_session_token = "${local.cert_manager_config.aws_session_token}"

View File

@ -3,19 +3,16 @@ variable "cert_manager_version" {
description = <<-EOT description = <<-EOT
The version of cert manager to install. The version of cert manager to install.
EOT EOT
default = "v1.13.1"
} }
variable "project_cert_key_id" { variable "project_cert_key_id" {
type = string type = string
description = <<-EOT description = <<-EOT
The key name to retrieve the project's cert's private key from AWS The key name to retrieve the project's cert's private key from AWS
EOT EOT
default = ""
} }
variable "project_cert_name" { variable "project_cert_name" {
type = string type = string
description = <<-EOT description = <<-EOT
The project's cert name The project's cert name
EOT EOT
default = ""
} }

View File

@ -45,7 +45,6 @@ variable "cert_manager_version" {
description = <<-EOT description = <<-EOT
The version of cert manager to install. The version of cert manager to install.
EOT EOT
default = "v1.13.1"
} }
variable "configure_cert_manager" { variable "configure_cert_manager" {
type = bool type = bool
@ -75,14 +74,3 @@ variable "cert_manager_configuration" {
} }
sensitive = true 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 = ""
# }

View File

@ -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}'

View File

@ -42,7 +42,7 @@ resource "terraform_data" "snapshot" {
] ]
} }
resource "local_file" "file" { resource "local_sensitive_file" "file" {
depends_on = [ depends_on = [
data.external.read_file, data.external.read_file,
terraform_data.recreate, terraform_data.recreate,

View File

@ -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"}'

View File

@ -1,7 +1,4 @@
# output "encoded_contents" {
# value = base64encode(filesystem_file_writer.file.contents)
# }
output "contents" { output "contents" {
value = local_file.file.content #filesystem_file_writer.file.contents value = local_sensitive_file.file.content
sensitive = true
} }

View File

@ -1,4 +1,4 @@
# !/bin/bash #!/bin/bash
set -e set -e
JSON_INPUT="$(jq -r '.')" JSON_INPUT="$(jq -r '.')"

View File

@ -16,6 +16,7 @@ variable "contents" {
The contents to persist, one of "contents" or "sourcefile" must be given. The contents to persist, one of "contents" or "sourcefile" must be given.
EOT EOT
default = "" default = ""
sensitive = true
} }
variable "sourcefile" { variable "sourcefile" {
type = string type = string
@ -23,4 +24,5 @@ variable "sourcefile" {
A file to persist, one of "contents" or "sourcefile" must be given. A file to persist, one of "contents" or "sourcefile" must be given.
EOT EOT
default = "" default = ""
sensitive = true
} }

View File

@ -9,11 +9,15 @@ locals {
email = var.email email = var.email
acme_server_url = var.acme_server_url acme_server_url = var.acme_server_url
rancher_version = replace(var.rancher_version, "v", "") # don't include the v 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 cert_manager_version = var.cert_manager_version
path = var.path path = var.path
externalTLS = var.externalTLS externalTLS = var.externalTLS
rancher_path = (local.externalTLS ? "${path.module}/rancher_externalTLS" : "${path.module}/rancher") rancher_path = (local.externalTLS ? "${path.module}/rancher_externalTLS" : "${path.module}/rancher")
deploy_path = "${local.path}/rancher_bootstrap" 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" { module "deploy_rancher" {
@ -30,10 +34,14 @@ module "deploy_rancher" {
} }
inputs = <<-EOT inputs = <<-EOT
project_domain = "${local.project_domain}" 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}" zone_id = "${local.zone_id}"
region = "${local.region}" region = "${local.region}"
email = "${local.email}" email = "${local.email}"
rancher_version = "${local.rancher_version}"
cert_manager_version = "${local.cert_manager_version}" cert_manager_version = "${local.cert_manager_version}"
acme_server_url = "${local.acme_server_url}" acme_server_url = "${local.acme_server_url}"
EOT EOT

View File

@ -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"

View File

@ -9,7 +9,7 @@ locals {
rancher_helm_channel = var.rancher_helm_channel rancher_helm_channel = var.rancher_helm_channel
rancher_version = replace(var.rancher_version, "v", "") # don't include the v rancher_version = replace(var.rancher_version, "v", "") # don't include the v
helm_chart_use_strategy = var.rancher_helm_chart_use_strategy 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 = { default_hc_values = {
"hostname" = local.rancher_domain "hostname" = local.rancher_domain
"replicas" = "1" "replicas" = "1"
@ -185,6 +185,7 @@ resource "helm_release" "rancher" {
for_each = local.helm_chart_values for_each = local.helm_chart_values
content { content {
name = set.key name = set.key
type = "string"
value = set.value value = set.value
} }
} }

View File

@ -11,6 +11,12 @@ variable "project_domain" {
error_message = "Must be a fully qualified domain name." 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" { variable "rancher_helm_repo" {
type = string type = string
description = <<-EOT description = <<-EOT
@ -38,27 +44,27 @@ variable "rancher_helm_chart_use_strategy" {
default = "default" default = "default"
} }
variable "rancher_helm_chart_values" { variable "rancher_helm_chart_values" {
type = map(any) type = string
description = <<-EOT 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". This will be ignored if the rancher_helm_chart_use_strategy argument is set to "default".
eg. eg.
{ {
"hostname" = "test.example.com" "hostname" : "test.example.com",
"replicas" = "1" "replicas" : "1",
"bootstrapPassword" = "password" "bootstrapPassword" : "password",
"ingress.enabled" = "true" "ingress.enabled" : "true",
"ingress.tls.source" = "letsEncrypt" "ingress.tls.source" : "letsEncrypt",
"tls" = "ingress" "tls" : "ingress",
"letsEncrypt.ingress.class" = "nginx" "letsEncrypt.ingress.class" : "nginx",
"letsEncrypt.environment" = "production" "letsEncrypt.environment" : "production",
"letsEncrypt.email" = "test@example.com" "letsEncrypt.email" : "test@example.com",
"certmanager.version" = "1.13.1" "certmanager.version" : "1.13.1",
"agentTLSMode" = "system-store" "agentTLSMode" : "system-store",
"ingress.extraAnnotations.cert-manager\\.io\\/issuer" = "rancher" "ingress.extraAnnotations.cert-manager.io/issuer" : "rancher"
} }
EOT EOT
default = {} default = "{}"
} }
variable "zone_id" { variable "zone_id" {
type = string type = string
@ -80,13 +86,6 @@ variable "email" {
The email to use when registering an account with Let's Encrypt. The email to use when registering an account with Let's Encrypt.
EOT EOT
} }
variable "rancher_version" {
type = string
description = <<-EOT
The version of rancher to install.
EOT
default = "2.11.2"
}
variable "cert_manager_version" { variable "cert_manager_version" {
type = string type = string
description = <<-EOT description = <<-EOT

View File

@ -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"

View File

@ -9,9 +9,9 @@ locals {
rancher_helm_channel = var.rancher_helm_channel rancher_helm_channel = var.rancher_helm_channel
rancher_version = replace(var.rancher_version, "v", "") # don't include the v rancher_version = replace(var.rancher_version, "v", "") # don't include the v
helm_chart_use_strategy = var.rancher_helm_chart_use_strategy 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 = { default_hc_values = {
"hostname" = local.rancher_domain "hostname" = local.rancher_domain # must be an fqdn
"replicas" = "1" "replicas" = "1"
"bootstrapPassword" = "admin" "bootstrapPassword" = "admin"
"ingress.enabled" = "true" "ingress.enabled" = "true"
@ -21,10 +21,10 @@ locals {
"agentTLSMode" = "system-store" "agentTLSMode" = "system-store"
} }
helm_chart_values = coalesce( # using coalesce like this essentially gives us a switch function 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.helm_chart_use_strategy == "default" ?
local.default_hc_values : null), 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.helm_chart_use_strategy == "provide" ?
local.rancher_helm_chart_values : null) local.rancher_helm_chart_values : null)
) # WARNING! Some config is necessary, if the result is an empty string the coalesce will fail ) # 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 for_each = local.helm_chart_values
content { content {
name = set.key name = set.key
type = "string"
value = set.value value = set.value
} }
} }
@ -149,39 +150,40 @@ resource "terraform_data" "get_public_cert_info" {
} }
} }
resource "terraform_data" "get_ping" { # this requires a let's encrypt certificate, which we would like to eventually not require
depends_on = [ # resource "terraform_data" "get_ping" {
random_password.password, # depends_on = [
time_sleep.settle_before_rancher, # random_password.password,
terraform_data.wait_for_nginx, # time_sleep.settle_before_rancher,
helm_release.rancher, # terraform_data.wait_for_nginx,
terraform_data.wait_for_rancher, # helm_release.rancher,
terraform_data.get_public_cert_info, # terraform_data.wait_for_rancher,
] # terraform_data.get_public_cert_info,
provisioner "local-exec" { # ]
command = <<-EOT # provisioner "local-exec" {
check_letsencrypt_ca() { # command = <<-EOT
# Try to verify a known Let's Encrypt certificate (you can use any valid one) # check_letsencrypt_ca() {
if openssl s_client -showcerts -connect letsencrypt.org:443 < /dev/null | openssl x509 -noout -issuer | grep -q "Let's Encrypt"; then # # Try to verify a known Let's Encrypt certificate (you can use any valid one)
return 0 # Success # if openssl s_client -showcerts -connect letsencrypt.org:443 < /dev/null | openssl x509 -noout -issuer | grep -q "Let's Encrypt"; then
else # return 0 # Success
return 1 # Failure # else
fi # return 1 # Failure
} # fi
echo "Checking Let's Encrypt CA" # }
if check_letsencrypt_ca; then # echo "Checking Let's Encrypt CA"
echo "Let's Encrypt CA is functioning correctly." # if check_letsencrypt_ca; then
else # echo "Let's Encrypt CA is functioning correctly."
echo "Error: Let's Encrypt CA is not being used for verification." # else
exit 1 # echo "Error: Let's Encrypt CA is not being used for verification."
fi # exit 1
echo "Checking Cert" # fi
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 Cert"
echo "Checking Curl" # echo | openssl s_client -showcerts -servername ${local.rancher_domain} -connect "${local.rancher_domain}:443" 2>/dev/null | openssl x509 -inform pem -noout -text || true
curl "https://${local.rancher_domain}/ping" # echo "Checking Curl"
EOT # curl "https://${local.rancher_domain}/ping"
} # EOT
} # }
# }
resource "rancher2_bootstrap" "admin" { resource "rancher2_bootstrap" "admin" {
depends_on = [ depends_on = [
@ -191,7 +193,7 @@ resource "rancher2_bootstrap" "admin" {
helm_release.rancher, helm_release.rancher,
terraform_data.wait_for_rancher, terraform_data.wait_for_rancher,
terraform_data.get_public_cert_info, terraform_data.get_public_cert_info,
terraform_data.get_ping, # terraform_data.get_ping,
] ]
password = random_password.password.result password = random_password.password.result
} }

View File

@ -45,21 +45,21 @@ variable "rancher_helm_chart_use_strategy" {
default = "default" default = "default"
} }
variable "rancher_helm_chart_values" { variable "rancher_helm_chart_values" {
type = map(any) type = string
description = <<-EOT 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". This will be ignored if the rancher_helm_chart_use_strategy argument is set to "default".
eg. eg.
{ {
"hostname" = local.rancher_domain "hostname" : "rancher.example.com",
"replicas" = "1" "replicas" : "1",
"bootstrapPassword" = "admin" "bootstrapPassword" : "admin",
"ingress.enabled" = "true" "ingress.enabled" : "true",
"ingress.tls.source" = "secret" "ingress.tls.source" : "secret",
"ingress.tls.secretName" = "tls-rancher-ingress" "ingress.tls.secretName" : "tls-rancher-ingress",
"privateCA" = "true" "privateCA" : "true",
"agentTLSMode" = "system-store" "agentTLSMode" : "system-store"
} }
EOT EOT
default = {} default = "{}"
} }

View File

@ -45,6 +45,21 @@ variable "rancher_version" {
EOT EOT
default = "2.11.2" 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" { variable "cert_manager_version" {
type = string type = string
description = <<-EOT description = <<-EOT
@ -66,3 +81,37 @@ variable "path" {
The local file path to stage files for the deployment. The local file path to stage files for the deployment.
EOT 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 = {}
}

View File

@ -20,16 +20,6 @@ output "admin_password" {
sensitive = true 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" { output "vpc" {
value = module.cluster.vpc value = module.cluster.vpc
} }

View File

@ -27,7 +27,6 @@ variable "domain" {
If left empty this will default to the project name. If left empty this will default to the project name.
eg. "test" in "test.example.com" eg. "test" in "test.example.com"
EOT EOT
default = ""
} }
variable "zone" { variable "zone" {
type = string type = string
@ -150,14 +149,45 @@ variable "cert_manager_version" {
description = <<-EOT description = <<-EOT
The version of cert-manager to install. The version of cert-manager to install.
EOT 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" { variable "rancher_version" {
type = string type = string
description = <<-EOT description = <<-EOT
The version of Rancher to install. The version of rancher to install.
EOT 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" { variable "bootstrap_rancher" {
type = bool type = bool
@ -209,27 +239,37 @@ variable "cert_manager_configuration" {
} }
sensitive = true sensitive = true
} }
# variable "install_cert_manager_backend" { variable "rancher_helm_chart_use_strategy" {
# type = string type = string
# description = <<-EOT description = <<-EOT
# Path to a .tfbackend file. The strategy to use for Rancher's Helm chart values.
# This allows the user to pass a backend file to the install_cert_manager submodule. Options include: "default", "merge", or "provide".
# The backend file will be added to the submodule's terraform run and will allow that module's state data to be saved remotely. Default will tell the module to use our suggested default configuration.
# 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. Merge will merge our default suggestions with your supplied configuration, anything you supply will override the default.
# See https://developer.hashicorp.com/terraform/language/backend#file for more information. Provide will ignore our default suggestions and use the configuration provided in the rancher_helm_chart_values argument.
# The default is to use a local state file. EOT
# EOT default = "default"
# default = "" validation {
# } condition = contains(["default", "merge", "provide"], var.rancher_helm_chart_use_strategy)
# variable "rancher_bootstrap_backend" { error_message = "Must be one of 'default', 'merge', or 'provide'."
# type = string }
# description = <<-EOT }
# Path to a .tfbackend file. variable "rancher_helm_chart_values" {
# This allows the user to pass a backend file to the rancher_bootstrap submodule. type = map(any)
# The backend file will be added to the submodule's terraform run and will allow that module's state data to be saved remotely. description = <<-EOT
# 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. A key/value map of Helm arguments to pass to the Rancher helm chart.
# See https://developer.hashicorp.com/terraform/language/backend#file for more information. This will be ignored if the rancher_helm_chart_use_strategy argument is set to "default".
# The default is to use a local state file. eg.
# EOT {
# default = "" "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 = {}
}