Skip to content

microsoftexpert/tf-mod-helm-template

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

⎈ Helm Template Terraform Module

Renders a Helm chart's manifests locally for inspection, GitOps hand-off, or drift-comparison workflows, without creating a Helm release, wrapping hashicorp/helm ~> 3.2's helm_template data source.

Terraform Helm Provider Module Version Module Type Resources Posture


🧩 Overview

  • ⎈ Renders a chart's manifests locally, mirroring the helm template CLI command, without touching the cluster's Helm release history.
  • 🔒 Performs no cluster mutation, and in most rendering modes, no cluster read either — rendering proceeds from the chart's own metadata plus caller-supplied api_versions/ kube_version.
  • 🧬 Reuses tf-mod-helm-release's values-injection variable shapes (values_yaml, set_values, set_list_values, set_sensitive_values) and secret-shaped-name validation for consistency across this library.
  • 🚫 Never accepts cluster-authentication material; the calling root module owns the helm/kubernetes provider block.

💡 Why it matters: helm_template lets a GitOps pipeline or a reviewer see exactly what a chart would render before any helm_release apply executes chart hooks inside a live, regulated cluster — without granting that pipeline any cluster-mutating permission.


❤️ Support this project

If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:

Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!


🗺️ Where this fits

flowchart LR
 AKS["Azure AKS module\noutputs (optional, validate=true only)"]:::neutral
 KV["Key Vault / Managed Identity\nmodule outputs"]:::neutral
 PROV["Caller-owned provider config\n(helm + kubernetes blocks)"]:::neutral
 THIS["tf-mod-helm-template\n(data.helm_template.this)"]:::keystone
 OUT["Rendered manifests\n(GitOps hand-off / drift comparison)"]:::aks

 AKS -.->|"cluster read, only if validate = true"| PROV
 KV -->|"secret values for set_sensitive_values"| THIS
 PROV -->|"configures"| THIS
 THIS -->|"renders"| OUT

 classDef keystone fill:#0F1689,color:#fff
 classDef aks fill:#277A9F,color:#fff
 classDef neutral fill:#E5E7EB,color:#111
Loading

This provider has no sibling tf-mod-helm-* family tree above it. The AKS module's outputs feed this diagram's provider-configuration edge only as a dotted, conditional line — most rendering runs entirely offline, with cluster reachability required only when validate = true.


🧬 What this builds

flowchart LR
 subgraph INPUTS["Inputs"]
 ID["name, namespace, chart,\nrepository, chart_version"]:::neutral
 VAL["values_yaml, set_values,\nset_list_values, set_sensitive_values"]:::neutral
 RC["api_versions, kube_version,\nshow_only, include_crds, validate,..."]:::neutral
 end

 THIS["data.helm_template.this"]:::keystone

 subgraph OUTPUTS["Outputs"]
 O1["manifest, manifests\n(sensitive)"]:::neutral
 O2["notes, crds\n(sensitive)"]:::neutral
 O3["id"]:::neutral
 end

 ID -->|"identity + chart source"| THIS
 VAL -->|"values injection"| THIS
 RC -->|"rendering context"| THIS
 THIS -->|"emits"| O1
 THIS -->|"emits"| O2
 THIS -->|"emits"| O3

 classDef keystone fill:#0F1689,color:#fff
 classDef neutral fill:#E5E7EB,color:#111
Loading

Resource inventory: exactly one data source, data.helm_template.this — no resource is created by this module.


✅ Provider / Versions

Component Requirement
Terraform >= 1.12.0
hashicorp/helm provider ~> 3.2 (verified live against v3.2.0)
Provider block None — the calling root module configures provider "helm" {} / provider "kubernetes" {}; required even for offline rendering, since the provider itself still needs a valid configuration block to load

Schema notes that bite:

  • set, set_list, and set_sensitive are list-of-nested-objects in provider v3.x, assigned with = syntax — identical shape to tf-mod-helm-release, confirmed per the live provider schema against v3.2.0.
  • version is a reserved variable name inside Terraform module blocks; this module exposes it as chart_version.
  • manifest, manifests, notes, and crds are technically "Optional" in the live schema (Plugin Framework Optional+Computed attributes) but are always computed by the render — this module models them exclusively as outputs, never as inputs.
  • atomic, create_namespace, wait, and timeout do not exist on this module's variable surface by design — they have no meaning for a non-mutating render (see 🧠 Architecture Notes).

🔑 Required Kubernetes RBAC / Cluster Permissions

helm_template performs no cluster mutation. In most rendering modes it performs no cluster read either:

  • No RBAC by default when api_versions/kube_version are supplied and validate = false (this module's default).
  • Read access to whatever the chart's templates inspect only if the chart calls cluster-lookup template functions (lookup, .Capabilities) and the caller has not supplied api_versions/kube_version — document per-chart; this module grants none by default.
  • Read access sufficient for OpenAPI schema validation if validate = true is set — still no mutation, but the identity must reach the Kubernetes API server.

Kubernetes & Helm Prerequisites

  • Kubernetes server version: 1.14.0 or higher (provider-wide floor, same as tf-mod-helm-release).
  • Chart repository/registry reachability: same as tf-mod-helm-release — outbound reachability to repository if set.
  • CRDs: rendering does not require pre-existing CRDs unless a chart's templates call lookup against a CRD's custom resources. include_crds/skip_crds control the chart's own CRD manifests in the output, independent of cluster state.
  • Namespace existence: not required — this module never creates or checks for a namespace.

📁 Module Structure

tf-mod-helm-template/
├── providers.tf # Terraform + provider version pin, no provider block
├── variables.tf # Chart source, values injection, rendering-context arguments
├── main.tf # data.helm_template.this — the module's only data source
├── outputs.tf # manifest/manifests first (sensitive), then notes/crds/id
├── README.md
├── SCOPE.md
└── examples/
 └── basic/
 └── main.tf

⚙️ Quick Start

provider "helm" {
  kubernetes = {
    config_path = "~/.kube/config"
  }
}

module "mariadb_preview" {
  source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"

  name       = "mariadb-instance"
  namespace  = "default"
  chart      = "mariadb"
  repository = "https://charts.helm.sh/stable"

  api_versions = ["v1", "apps/v1"]
}

output "mariadb_manifest" {
  value     = module.mariadb_preview.manifest
  sensitive = true
}

🔌 Cross-Module Contract

Consumes

Input Type Source module
AKS cluster API endpoint + credentials (only used if validate = true, or if omitted api_versions/kube_version fall back to live cluster inspection) provider configuration (caller-owned) Azure AKS module or az aks get-credentials
Key Vault secret values for set_sensitive_values variable input Azure Key Vault module outputs
Managed Identity client ID (workload identity annotations) variable input, via set_values/values_yaml Azure Managed Identity module outputs

Emits

Output Description Sensitive
manifest Concatenated rendered chart templates Yes
manifests Map of rendered templates keyed by template path Yes
notes Rendered NOTES.txt content Yes
crds Rendered CRD manifests from the chart Yes
id The data source's Terraform resource ID No

📚 Example Library

1 · Minimal HTTPS-repository render
module "redis_preview" {
  source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"

  name       = "redis-preview"
  namespace  = "caching"
  chart      = "redis"
  repository = "https://charts.bitnami.com/bitnami"
}

ℹ️ With no api_versions/kube_version supplied, charts that don't branch on .Capabilities/lookup render identically offline or online.

2 · OCI registry chart source
provider "helm" {
  kubernetes = {
    config_path = "~/.kube/config"
  }

  registries = [
    {
      url      = "oci://private.registry.casey.example"
      username = "REPLACE_ME"
      password = "REPLACE_ME"
    }
  ]
}

module "internal_chart_preview" {
  source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"

  name          = "internal-chart"
  namespace     = "platform"
  repository    = "oci://private.registry.casey.example/charts"
  chart         = "internal-chart"
  chart_version = "1.4.2"
}
3 · Local chart path
module "internal_service_preview" {
  source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"

  name      = "internal-service"
  namespace = "platform"
  chart     = "./charts/internal-service"
}
4 · Faking Capabilities for fully offline rendering
module "offline_preview" {
  source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"

  name      = "app-preview"
  namespace = "platform"
  chart     = "./charts/app"

  api_versions = ["v1", "apps/v1", "networking.k8s.io/v1"]
  kube_version = "1.29.0"
}

💡 Supplying api_versions/kube_version lets charts that branch on .Capabilities render without any cluster read at all. See 🧠 Architecture Notes for the fidelity limits of this approach.

5 · Raw values_yaml heredoc
module "grafana_preview" {
  source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"

  name       = "grafana"
  namespace  = "observability"
  chart      = "grafana"
  repository = "https://grafana.github.io/helm-charts"

  values_yaml = [
    <<-YAML
 persistence:
 enabled: true
 size: 10Gi
 YAML
  ]
}
6 · set_values for scalar overrides
module "mariadb_scaled_preview" {
  source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"

  name       = "mariadb-instance"
  namespace  = "default"
  chart      = "mariadb"
  repository = "https://charts.helm.sh/stable"

  set_values = [
    { name = "service.port", value = "13306" },
  ]
}
7 · set_sensitive_values for secret-shaped material
module "mariadb_with_secret_preview" {
  source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"

  name       = "mariadb-instance"
  namespace  = "default"
  chart      = "mariadb"
  repository = "https://charts.helm.sh/stable"

  set_sensitive_values = [
    { name = "rootUser.password", value = "<from-key-vault>" },
  ]
}

🔒 Even though this is a preview render, the secret-shaped-name rejection still applies to set_values — the value ends up in the (sensitive) rendered manifest output either way, and the validation keeps callers in the habit of using set_sensitive_values.

8 · show_only for targeted template rendering
module "mariadb_master_only" {
  source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"

  name       = "mariadb-instance"
  namespace  = "default"
  chart      = "mariadb"
  repository = "https://charts.helm.sh/stable"

  show_only = [
    "templates/master-statefulset.yaml",
    "templates/master-svc.yaml",
  ]
}
9 · CRD inclusion for pre-installation review
module "cert_manager_preview" {
  source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"

  name         = "cert-manager"
  namespace    = "cert-manager"
  chart        = "cert-manager"
  repository   = "https://charts.jetstack.io"
  include_crds = true
}

ℹ️ crds output additionally exposes the CRD manifests separately from the main manifest output, for pipelines that pre-install CRDs as their own cluster-scoped step.

10 · Live-cluster OpenAPI validation
module "validated_preview" {
  source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"

  name      = "internal-app"
  namespace = "platform"
  chart     = "./charts/internal-app"

  validate = true # Requires read access to the target cluster's API server — see SCOPE.md.
}
11 · Chart signature verification
module "verified_chart_preview" {
  source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"

  name       = "verified-chart"
  namespace  = "platform"
  chart      = "verified-chart"
  repository = "https://charts.example.com"

  verify  = true
  keyring = "/etc/casey/helm/pubring.gpg"
}
12 · is_upgrade context for upgrade-branching templates
module "app_upgrade_preview" {
  source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"

  name       = "internal-app"
  namespace  = "platform"
  chart      = "./charts/internal-app"
  is_upgrade = true
}
13 · Multi-environment for_each composition
locals {
  environments = {
    dev  = { namespace = "app-dev", chart_version = "1.2.0" }
    prod = { namespace = "app-prod", chart_version = "1.1.5" }
  }
}

module "app_preview_per_env" {
  source   = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"
  for_each = local.environments

  name          = "internal-app"
  namespace     = each.value.namespace
  chart         = "internal-app"
  repository    = "https://charts.example.com"
  chart_version = each.value.chart_version
}

💡 for_each over a keyed map, never count — consistent with tf-mod-helm-release.

14 · Drift-comparison against a live tf-mod-helm-release
module "app_release" {
  source = "git::https://github.com/microsoftexpert/tf-mod-helm-release.git?ref=v1.0.0"

  name       = "internal-app"
  namespace  = "platform"
  chart      = "internal-app"
  repository = "https://charts.example.com"
}

module "app_preview" {
  source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"

  name       = "internal-app"
  namespace  = "platform"
  chart      = "internal-app"
  repository = "https://charts.example.com"
}

# Compare module.app_release's applied state against module.app_preview.manifest in CI to detect
# drift between what Terraform would install and what is actually rendered from the same chart
# call, before promoting a change.
15 · 🏗️ End-to-end composition — AKS module → provider → rendered manifest
module "aks" {
  source = "git::https://github.com/microsoftexpert/tf-mod-azure-aks-cluster.git?ref=v1.0.0"
  #... AKS cluster inputs...
}

provider "helm" {
  kubernetes = {
    host                   = module.aks.cluster_endpoint
    cluster_ca_certificate = base64decode(module.aks.cluster_ca_certificate)
    exec = {
      api_version = "client.authentication.k8s.io/v1beta1"
      command     = "kubelogin"
      args        = ["get-token", "--login", "workloadidentity", "--server-id", module.aks.oidc_issuer_url]
    }
  }
}

module "platform_ingress_preview" {
  source = "git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0"

  name       = "nginx-ingress-controller"
  namespace  = "ingress"
  chart      = "nginx-ingress-controller"
  repository = "https://charts.bitnami.com/bitnami"

  validate = true # Uses the AKS provider config above to validate against the live cluster's OpenAPI schema.
}

output "ingress_manifest_preview" {
  value     = module.platform_ingress_preview.manifest
  sensitive = true
}

🏗️ This is the mandatory end-to-end shape: an Azure AKS module's outputs feed the caller's provider "helm" {} block, enabling validate = true to reach the live cluster's OpenAPI schema, while this module itself still creates no release and mutates nothing.


📥 Inputs

Grouped summary:

Group Variables
Identity name, namespace, description
Chart source chart, repository, repository_ca_file, repository_cert_file, repository_key_file, repository_username, repository_password, chart_version, devel, verify, keyring
Rendering context api_versions, kube_version, show_only, skip_tests, include_crds, skip_crds, validate, is_upgrade
Values injection values_yaml, set_values, set_list_values, set_sensitive_values
Other knobs dependency_update, pass_credentials, reset_values, reuse_values, disable_webhooks, disable_openapi_validation
Full object schemas
variable "set_values" {
  type = list(object({
    name  = string
    value = optional(string)
    type  = optional(string)
  }))
  default = []
}

variable "set_list_values" {
  type = list(object({
    name  = string
    value = list(string)
  }))
  default = []
}

variable "set_sensitive_values" {
  type = list(object({
    name  = string
    value = string
    type  = optional(string)
  }))
  default   = []
  sensitive = true
}

See variables.tf for the complete set, including every scalar's description heredoc and validation {} block.


🧾 Outputs

Output Description Sensitive
manifest Concatenated rendered chart templates Yes
manifests Map of rendered templates keyed by template path Yes
notes Rendered NOTES.txt content Yes
crds Rendered CRD manifests from the chart Yes
id The data source's Terraform resource ID No

🧠 Architecture Notes

  • v2 → v3 syntax migration. set, set_list, and set_sensitive are list-of-nested-objects in provider v3.x, identical in shape to tf-mod-helm-release. A v2-era example will fail terraform validate against this module's ~> 3.2 pin.
  • Fidelity caveat. helm_template's rendering can differ from what helm_release will actually install if the chart's templates branch on live-cluster lookups (.Capabilities, lookup against cluster objects) that this data source cannot fully evaluate without the same cluster context helm_release has at apply time. Supplying api_versions/kube_version narrows but does not eliminate this gap.
  • No chart-hooks caveat needed. Unlike tf-mod-helm-release, there is no apply step at all for a data source — chart hooks never execute for helm_template, so this module has none of tf-mod-helm-release's "hooks execute outside Terraform's visibility" concern.
  • atomic, create_namespace, wait, timeout intentionally absent. These lifecycle/ mutation arguments have no meaning for a non-mutating render and are not modeled as variables.
  • upgrade_install does not exist on this data source at all — it is a helm_release-only concept; a caller expecting parity with tf-mod-helm-release here will not find it.

🧱 Design Principles

Default value Upstream provider default What it prevents
namespace required, no default defaults to "default" Incorrect .Release.Namespace template context and namespace fields in rendered manifests
validate false false Requiring live cluster access for a tool positioned as an offline preview
verify + keyring pairing keyring required when verify = true no pairing enforced Unsigned/unverifiable charts silently rendering as if verified
Secret-shaped set_values/set_list_values names rejected by validation {} not enforced Secrets landing in plan output and CI logs in plaintext, and normalizing the habit of using set_sensitive_values
set_sensitive_values entire variable sensitive = true set_sensitive masked at the argument level Secret values echoing in plan/apply output
manifest/manifests/notes/crds outputs sensitive = true unconditionally not marked sensitive by the provider Rendered manifests (which may contain injected secrets) printing in terraform output/CI logs
dependency_update false false Uncontrolled network calls to chart-dependency repositories during rendering

🚀 Runbook

cd tf-mod-helm-template
terraform init -backend=false
terraform validate
terraform fmt -check

Pin the module source with an immutable tag, never a branch:

git::https://github.com/microsoftexpert/tf-mod-helm-template.git?ref=v1.0.0

Even though this module performs no cluster mutation, terraform plan/apply are still run by a human, from CI, after code review — never by this authoring process.


🧪 Testing

terraform init -backend=false / validate / fmt -check prove schema and syntax correctness, including this module's validation {} blocks (secret-shaped name rejection, verify/keyring pairing, type enum checks). They do not prove rendering fidelity against a live cluster's actual .Capabilities — that requires either supplying real api_versions/kube_version values sourced from the target cluster, or running with validate = true against a live connection.


💬 Example Output

$ terraform output

manifest = <sensitive>
manifests = <sensitive>
notes = <sensitive>
crds = <sensitive>
id = "mariadb-instance"

🔍 Troubleshooting

Symptom Cause Fix
Rendered manifest looks different from what tf-mod-helm-release actually installs The chart's templates branch on live-cluster .Capabilities/lookup calls this data source cannot fully evaluate Supply api_versions/kube_version matching the target cluster, or set validate = true against a live connection for higher fidelity
terraform validate fails on a set { name =... value =... } block v2.x-era HCL block syntax, incompatible with this module's ~> 3.2 pin Rewrite as set_values = [{ name = "...", value = "..." }]
terraform validate fails with "set_values contains a secret-shaped name" A values path like rootUser.password was passed through set_values Move the entry to set_sensitive_values
manifest/manifests output is empty show_only was set to a template path that does not exist in the chart Confirm the exact template path via helm show chart <chart> or the chart's own template directory listing
A caller expected upgrade_install/atomic/wait variables here Those are helm_release-only concepts Use tf-mod-helm-release for actual installs; this module only renders

🔗 Related Docs


💙 "Infrastructure as Code should be standardized, consistent, and secure."

About

No description or website provided.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages