Generate values for Helmfile from Terraform
I use Helmfile to manage third-party helm charts such as Karpenter for my EKS clusters since it’s simple and flexible to use across multiple environments. I used to use Terraform to manage third-party helm charts; however, I came across random weirdness when running a Terraform plan/apply and it also required myself to be on the VPN to run Terraform commands to talk to the EKS control plane API.
Below I’m going to show two ways to generate values from Terraform such as EKS cluster and IAM role details to be referenced in values files used by Helmfile. No need to hardcode the files!
If you have a monorepo with your Helmfiles and Terraform, it’s pretty simple to do this.
Most of these values are used for the Karpenter helm chart as you can see. In this example, it’s saving a values file in the helm directory outside the Terraform directory.
Terraform
1resource "local_file" "helmfile_values" {
2 filename = "../../../../helm/services/terraform/${local.env}_values.yaml"
3 content = yamlencode({
4 region = local.region
5 cluster_name = module.eks.name
6 "${local.env}_cluster_name" = module.eks.name
7 cluster_endpoint = module.eks.endpoint
8 "${local.env}_cluster_endpoint" = module.eks.endpoint
9 "${local.env}_cluster_cert" = module.eks.certificate
10 karpenter_interruption_queue = module.karpenter.sqs_queue_name
11 karpenter_iam_role = module.karpenter.iam_controller_role
12 karpenter_iam_instance_profile = module.karpenter.iam_instance_profile
13 lb_controller_iam_role = module.lb_controller.iam_role
14 })
15}
Here we’re simply referencing the generated values file that could be used for any of the helm charts in your helmfile. You can also add a reference to the values file under a specific helm chart if you didn’t want it global.
Helmfile
1environments:
2 default:
3 values:
4 - env:
5 name: "staging"
6 - ../../services/terraform/staging_values.yaml
Below is a more advanced method I currently use if your helmfiles are located in another repo.
Here we’re saving the generated values to AWS SSM and these will be pulled in by Helmfile with a hook during runtime.
Terraform
1resource "aws_ssm_parameter" "helmfile_values" {
2 name = "/eks/${local.env}/helmfile_values"
3 type = "SecureString"
4 value = jsonencode({
5 region = local.region
6 cluster_name = module.eks.name
7 "${local.env}_cluster_name" = module.eks.name
8 cluster_endpoint = module.eks.endpoint
9 "${local.env}_cluster_endpoint" = module.eks.endpoint
10 "${local.env}_cluster_cert" = module.eks.certificate
11 karpenter_interruption_queue = module.karpenter.sqs_queue_name
12 karpenter_iam_role = module.karpenter.iam_controller_role
13 karpenter_iam_instance_profile = module.karpenter.iam_instance_profile
14 lb_controller_iam_role = module.lb_controller.iam_role
15 })
16}
The main difference in this method is a bash script that runs first and generates the values file. I would advise setting the values files in .gitignore.
Helmfile
1hooks:
2 - events: ["prepare"]
3 showlogs: true
4 command: "bash"
5 args:
6 - "../../services/terraform/sync-ssm.sh"
7 - "staging"
8
9environments:
10 default:
11 values:
12 - env:
13 name: "staging"
14 - ../../services/terraform/staging_values.yaml
This bash script will fetch the SSM parameter and format it.
1#!/bin/bash
2# Sync helmfile values from AWS SSM Parameter Store
3# Usage: ./sync-ssm.sh <environment> [profile]
4
5set -eo pipefail
6
7SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
9# Parse arguments
10if [[ -z "$1" ]]; then
11 echo "Usage: $0 <environment> [profile]"
12 echo "Examples:"
13 echo " $0 staging - Sync staging environment"
14 exit 1
15fi
16
17ENV="$1"
18PROFILE="$2"
19OUTPUT_FILE="${SCRIPT_DIR}/${ENV}_values.yaml"
20
21echo "Syncing SSM values for ${ENV} using AWS profile ${PROFILE}..."
22
23if aws ssm get-parameter \
24 --name "/eks/${ENV}/helmfile_values" \
25 --query "Parameter.Value" \
26 --output text \
27 --with-decryption \
28 --profile "${PROFILE}" 2>/dev/null | \
29 jq -r 'to_entries | map("\"\(.key)\": \"\(.value)\"") | .[]' > "${OUTPUT_FILE}"; then
30 echo "✓ Synced to ${OUTPUT_FILE}"
31else
32 echo "✗ Failed to fetch ${ENV} (parameter may not exist)" >&2
33 exit 1
34fi
That’s it for avoiding the need to put some of those hardcoded values in your helmfiles.