Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions modules/helm/k8s/0.2/facets.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
intent: helm
flavor: k8s
version: '0.2'
description: Adds Helm module
clouds:
- aws
- azure
- gcp
- kubernetes
iac:
validated_files:
- variables.tf
inputs:
prometheus_details:
type: '@outputs/prometheus'
optional: false
default:
resource_type: configuration
resource_name: prometheus
kubernetes_details:
type: '@outputs/kubernetes'
optional: false
default:
resource_type: kubernetes_cluster
resource_name: default
providers:
- kubernetes
- helm
- kubernetes-alpha
spec:
title: Helm
description: Specification of the Helm chart intent
type: object
properties:
helm:
title: Helm
description: Configuration for the Helm chart
type: object
properties:
chart:
type: string
title: Chart
description: Name of the Helm chart package
x-ui-placeholder: Enter name of the helm chart
pattern: ^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*[a-zA-Z0-9]$
x-ui-error-message: Invalid format. Only alphabets, numbers and hypens are
allowed
repository:
type: string
title: Repository
description: URL or relative path for the helm repository
x-ui-placeholder: Enter URL or relative path of the repository
pattern: '[a-zA-Z0-9:/._-]+'
x-ui-error-message: Invalid input format. The URL or relative path should
contain only valid regex `[a-zA-Z0-9:/._-]+`
version:
type: string
title: Version
description: Version for the helm chart
x-ui-placeholder: Enter the valid chart version
namespace:
type: string
title: Namespace
description: Namespace to deploy the Helm chart in
x-ui-placeholder: Enter namespace
pattern: ^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$
x-ui-error-message: Invalid namespace format. The namespace must be DNS-compliant,
containing only letters, numbers, and hyphens, and must not start or end
with a hyphen
Comment on lines +66 to +69
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Namespace regex allows uppercase and violates k8s DNS-1123 label rules.

Kubernetes namespaces must be lowercase alphanumeric plus hyphen, start/end with alphanumeric, max 63 chars. Update the pattern accordingly.

-          pattern: ^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$
-          x-ui-error-message: Invalid namespace format. The namespace must be DNS-compliant,
-            containing only letters, numbers, and hyphens, and must not start or end
-            with a hyphen
+          pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'
+          x-ui-error-message: Invalid namespace format. Must be DNS-1123 compliant: lowercase letters,
+            numbers, and hyphens; must start and end with an alphanumeric character.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In modules/helm/k8s/0.2/facets.yaml around lines 66 to 69, the current regex
allows uppercase letters and doesn't enforce Kubernetes DNS-1123 label rules;
replace the pattern with one that enforces lowercase letters, numbers and
hyphens only, ensures the name starts and ends with an alphanumeric character,
and limits length to 1–63 characters (e.g. use a pattern like
^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$), and adjust the x-ui-error-message to
mention lowercase and the 63-character limit.

wait:
type: boolean
title: Wait
description: Whether to wait for the Helm chart deployment to complete
repository_username:
type: string
title: Repository Username
description: Username to authenticate to private helm repository
x-ui-placeholder: Enter repository username
repository_password:
type: string
title: Repository Password
description: Password to authenticate to private helm repository
x-ui-placeholder: Enter repository password
x-ui-mask-content: true
required:
- chart
x-ui-order:
- repository
- chart
- version
- namespace
- repository_username
- repository_password
- wait
values:
type: object
title: Values
description: The values to be passed on to the chart in the form of a YAML object
x-ui-yaml-editor: true
x-ui-placeholder: Enter values in YAML format
required:
- helm
- values
sample:
$schema: https://facets-cloud.github.io/facets-schemas/schemas/helm/helm.schema.json
kind: helm
flavor: k8s
disabled: true
metadata: {}
version: '0.2'
spec:
helm:
repository: https://helm.datadoghq.com
chart: datadog
namespace: default
wait: false
values: null
Comment on lines +101 to +117
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Schema requires values but sample sets it to null; this will fail validation.

You mark values as required and typed object, yet the sample has values: null and main.tf explicitly supports null. Relax the schema or drop values from required.

Option A (allow nulls):

-    values:
-      type: object
+    values:
+      type:
+      - object
+      - 'null'
@@
-  required:
-  - helm
-  - values
+  required:
+  - helm

Option B (if you want to require non-null values): update sample and main.tf to align.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
required:
- helm
- values
sample:
$schema: https://facets-cloud.github.io/facets-schemas/schemas/helm/helm.schema.json
kind: helm
flavor: k8s
disabled: true
metadata: {}
version: '0.2'
spec:
helm:
repository: https://helm.datadoghq.com
chart: datadog
namespace: default
wait: false
values: null
# In your JSON Schema definitions (e.g. under `properties:`)
values:
type:
- object
- 'null'
# In the same file, replace the `required` block:
required:
- helm
# (The rest of the sample stays unchanged:)
sample:
$schema: https://facets-cloud.github.io/facets-schemas/schemas/helm/helm.schema.json
kind: helm
flavor: k8s
disabled: true
metadata: {}
version: '0.2'
spec:
helm:
repository: https://helm.datadoghq.com
chart: datadog
namespace: default
wait: false
values: null
🤖 Prompt for AI Agents
In modules/helm/k8s/0.2/facets.yaml around lines 101 to 117, the schema marks
`values` as required and object-typed but the sample sets `values: null`,
causing validation failure; fix by either A) allowing nulls in the schema (make
the `values` type accept null, e.g., object OR null) or remove `values` from the
required list so null is permitted, or B) if `values` must be non-null, update
the sample to provide a valid object for `values` and adjust main.tf to disallow
nulls consistently.

33 changes: 33 additions & 0 deletions modules/helm/k8s/0.2/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
locals {

variables = lookup(var.instance.spec, "env", [])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont have this, variables, static_variables, dynamic_variables_raw etc

# filter out static variables
static_variables = toset([for x in local.variables : x if x["type"] == "static"])
# filter out static variables
dynamic_variables_raw = toset([for x in local.variables : x if x["type"] == "secret"])
# replace the value
dynamic_variables = toset([
for x in local.dynamic_variables_raw : {
name = x["name"],
default = lookup(var.environment.secrets, x["attribute"], "Invalid Secret Reference")
}
])
all_variables = setunion(local.static_variables, local.dynamic_variables)
Comment on lines +3 to +15
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard nested attribute access and avoid set unions across heterogeneous object shapes.

Directly accessing var.instance.spec and var.environment.secrets will error when absent. Also, setunion across objects with different shapes (static vs. dynamic) can fail type unification. Use try()/lookup() and keep lists unless you specifically need sets.

Apply this refactor to harden locals:

 locals {
-
-  variables = lookup(var.instance.spec, "env", [])
-  # filter out static variables
-  static_variables = toset([for x in local.variables : x if x["type"] == "static"])
-  # filter out static variables
-  dynamic_variables_raw = toset([for x in local.variables : x if x["type"] == "secret"])
-  # replace the value
-  dynamic_variables = toset([
-    for x in local.dynamic_variables_raw : {
-      name    = x["name"],
-      default = lookup(var.environment.secrets, x["attribute"], "Invalid Secret Reference")
-    }
-  ])
-  all_variables = setunion(local.static_variables, local.dynamic_variables)
+  spec      = try(var.instance.spec, {})
+  helm_spec = try(local.spec.helm, {})
+  variables = try(local.spec.env, [])
+
+  # Filter static variables
+  static_variables = [for x in local.variables : x if try(x.type, null) == "static"]
+
+  # Filter secret-backed variables
+  dynamic_variables_raw = [for x in local.variables : x if try(x.type, null) == "secret"]
+
+  # Resolve secret defaults safely (environment.secrets may be absent)
+  dynamic_variables = [
+    for x in local.dynamic_variables_raw : {
+      name    = x.name
+      default = lookup(lookup(var.environment, "secrets", {}), x.attribute, "Invalid Secret Reference")
+    }
+  ]
+
+  # Combine without forcing a set of mixed object shapes
+  all_variables = concat(local.static_variables, local.dynamic_variables)
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In modules/helm/k8s/0.2/main.tf around lines 3 to 15, the code directly indexes
nested attributes and performs setunion across objects with different shapes
which will break when var.instance.spec or var.environment.secrets are absent
and when static and dynamic objects differ; change to use lookup()/try() to
safely access var.instance.spec and var.environment.secrets with sensible
defaults, keep variables as lists (not sets) while you build/transform them to
maintain consistent object shapes, ensure both static and dynamic entries share
the same key set (e.g., name and value/default) or normalize them into a common
object shape, then concatenate lists (concat()) instead of setunion() or convert
to sets only after normalization if deduping is required, and provide default
fallback strings for missing secret lookups via lookup(var.environment.secrets,
attr, "<missing>").

}

resource "helm_release" "external_helm_charts" {
provider = "helm3"
chart = var.instance.spec["helm"]["chart"]
name = var.instance_name
namespace = lookup(var.instance.spec["helm"], "namespace", var.environment.namespace)
timeout = lookup(var.instance.spec["helm"], "timeout", 300)
create_namespace = true
wait = lookup(var.instance.spec["helm"], "wait", true)
repository = lookup(var.instance.spec["helm"], "repository", "")
version = lookup(var.instance.spec["helm"], "version", "")
recreate_pods = lookup(var.instance.spec["helm"], "recreate_pods", false)
repository_username = lookup(var.instance.spec["helm"], "repository_username", null)
repository_password = lookup(var.instance.spec["helm"], "repository_password", null)
values = lookup(var.instance.spec, "values", null) != null ? [yamlencode(lookup(var.instance.spec, "values", {}))] : null
cleanup_on_fail = true
}
Comment on lines +19 to +33
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Fix provider reference and make Helm spec access safe.

  • The provider meta-arg must reference a configured provider block (e.g., helm or helm.alias), not a string. Remove it here and let the root module pass the provider mapping, or switch to provider = helm.some_alias if you declare it.
  • Use the guarded locals to read Helm settings without crashing when keys are absent.
  • Optional: align default wait with the facet sample (false) to avoid surprises.
 resource "helm_release" "external_helm_charts" {
-  provider            = "helm3"
-  chart               = var.instance.spec["helm"]["chart"]
-  name                = var.instance_name
-  namespace           = lookup(var.instance.spec["helm"], "namespace", var.environment.namespace)
-  timeout             = lookup(var.instance.spec["helm"], "timeout", 300)
-  create_namespace    = true
-  wait                = lookup(var.instance.spec["helm"], "wait", true)
-  repository          = lookup(var.instance.spec["helm"], "repository", "")
-  version             = lookup(var.instance.spec["helm"], "version", "")
-  recreate_pods       = lookup(var.instance.spec["helm"], "recreate_pods", false)
-  repository_username = lookup(var.instance.spec["helm"], "repository_username", null)
-  repository_password = lookup(var.instance.spec["helm"], "repository_password", null)
-  values              = lookup(var.instance.spec, "values", null) != null ? [yamlencode(lookup(var.instance.spec, "values", {}))] : null
-  cleanup_on_fail     = true
+  # provider = helm           # (optional) let root module pass provider; uncomment and set alias if needed
+  chart               = local.helm_spec.chart
+  name                = var.instance_name
+  namespace           = lookup(local.helm_spec, "namespace", lookup(var.environment, "namespace", "default"))
+  timeout             = lookup(local.helm_spec, "timeout", 300)
+  create_namespace    = true
+  wait                = lookup(local.helm_spec, "wait", true)
+  repository          = lookup(local.helm_spec, "repository", "")
+  version             = lookup(local.helm_spec, "version", "")
+  recreate_pods       = lookup(local.helm_spec, "recreate_pods", false)
+  repository_username = lookup(local.helm_spec, "repository_username", null)
+  repository_password = lookup(local.helm_spec, "repository_password", null)
+  values              = try(local.spec.values, null) != null ? [yamlencode(local.spec.values)] : null
+  cleanup_on_fail     = true
 }
  • If you intend to keep an aliased provider (e.g., helm3), add provider "helm" { alias = "v3" } in the root and use provider = helm.v3 here, and adjust required_providers to helm = { source = "hashicorp/helm" } in versions.tf.
  • Confirm whether wait default should be false to match the facet sample (values.wait: false).

🏁 Script executed:

#!/bin/bash
set -e

# Inspect for locals block within the helm module
echo ">>> Checking for locals blocks in modules/helm/k8s/0.2/main.tf"
rg -n 'locals' -C3 modules/helm/k8s/0.2/main.tf || echo "No locals block found in this file"

# Search for any references to helm_spec throughout the repo
echo ">>> Searching for 'helm_spec' references across the repository"
rg -n 'helm_spec' -C3 . || echo "No occurrences of 'helm_spec' found"

Length of output: 544


Fix missing local.helm_spec definition and correct provider usage

The suggested diff references local.helm_spec, but no such local is defined in modules/helm/k8s/0.2/main.tf. To implement these changes safely:

• Add a locals block at the top of modules/helm/k8s/0.2/main.tf:

locals {
  # pull the Helm-specific settings, or use an empty object if absent
  helm_spec   = lookup(var.instance.spec, "helm", {})
  # pull any top-level values, or default to empty map
  spec_values = lookup(var.instance.spec, "values", {})
}

• In the helm_release.external_helm_charts resource (lines 19–33), replace all lookup(var.instance.spec["helm"], …) calls with lookup(local.helm_spec, …) and change the values argument to:

values = length(local.spec_values) > 0 ? [yamlencode(local.spec_values)] : null

• Remove the hard-coded provider = "helm3" meta-arg. Either:

  1. Omit the provider setting so the root module’s provider mapping applies, or
  2. Declare an aliased provider in the root (e.g. provider "helm" { alias = "v3" } and required_providers { helm = { source = "hashicorp/helm" } }) and reference it here as provider = helm.v3.
    • Decide on a wait default (true vs. false) to match your facet sample (values.wait = false vs. current default true).

Revised snippet for the resource:

 resource "helm_release" "external_helm_charts" {
-  provider            = "helm3"
-  chart               = var.instance.spec["helm"]["chart"]
+  # provider = helm.v3      # uncomment if using an aliased provider
+  chart               = local.helm_spec.chart
   name                = var.instance_name
   namespace           = lookup(local.helm_spec, "namespace", lookup(var.environment, "namespace", "default"))
   timeout             = lookup(local.helm_spec, "timeout", 300)
   create_namespace    = true
   wait                = lookup(local.helm_spec, "wait", true)
   repository          = lookup(local.helm_spec, "repository", "")
   version             = lookup(local.helm_spec, "version", "")
   recreate_pods       = lookup(local.helm_spec, "recreate_pods", false)
   repository_username = lookup(local.helm_spec, "repository_username", null)
   repository_password = lookup(local.helm_spec, "repository_password", null)
-  values              = lookup(var.instance.spec, "values", null) != null ? [yamlencode(lookup(var.instance.spec, "values", {}))] : null
+  values              = length(local.spec_values) > 0 ? [yamlencode(local.spec_values)] : null
   cleanup_on_fail     = true
 }
🤖 Prompt for AI Agents
In modules/helm/k8s/0.2/main.tf around lines 19 to 33, the code references
local.helm_spec which is not defined and hard-codes provider = "helm3"; fix by
adding a locals block at the top that defines helm_spec =
lookup(var.instance.spec, "helm", {}) and spec_values =
lookup(var.instance.spec, "values", {}), replace all
lookup(var.instance.spec["helm"], ...) calls in the
helm_release.external_helm_charts resource with lookup(local.helm_spec, ...),
change the values argument to use local.spec_values (e.g. values =
length(local.spec_values) > 0 ? [yamlencode(local.spec_values)] : null), and
remove the hard-coded provider meta-arg (or instead reference an aliased
provider declared in the root module such as provider = helm.v3) and ensure the
wait default is set consistently with your facet (choose true or false).

13 changes: 13 additions & 0 deletions modules/helm/k8s/0.2/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
locals {
output_attributes = {
release_name = helm_release.external_helm_charts.name
}
output_interfaces = {}
}

output "metadata" {
value = helm_release.external_helm_charts.metadata
}
Comment on lines +8 to +10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

helm_release doesn’t expose a metadata attribute; construct a metadata map instead.

The Helm provider’s helm_release resource exports fields like name, namespace, chart, version, status, etc., but not a single metadata map. Building your own avoids plan/apply failures.

-output "metadata" {
-  value = helm_release.external_helm_charts.metadata
-}
+output "metadata" {
+  value = {
+    name      = helm_release.external_helm_charts.name
+    namespace = helm_release.external_helm_charts.namespace
+    chart     = helm_release.external_helm_charts.chart
+    version   = helm_release.external_helm_charts.version
+  }
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
output "metadata" {
value = helm_release.external_helm_charts.metadata
}
output "metadata" {
value = {
name = helm_release.external_helm_charts.name
namespace = helm_release.external_helm_charts.namespace
chart = helm_release.external_helm_charts.chart
version = helm_release.external_helm_charts.version
}
}
🤖 Prompt for AI Agents
In modules/helm/k8s/0.2/outputs.tf around lines 8 to 10, the output references a
non-existent helm_release.external_helm_charts.metadata attribute; replace it by
constructing and returning a map of metadata using existing attributes (for
example name, namespace, chart, version, status, and any other exported fields
you need) — e.g., build a map literal that pulls
helm_release.external_helm_charts.name, .namespace, .chart, .version, .status,
etc., and output that instead so Terraform plan/apply won’t fail.

output "status" {
value = helm_release.external_helm_charts.status
}
21 changes: 21 additions & 0 deletions modules/helm/k8s/0.2/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
variable "instance" {
type = any
default = {}
}

variable "instance_name" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ask mcp to validate the variables.tf file

type = string
default = "test_instance"
}

variable "environment" {
type = any
default = {
namespace = "default"
}
}
Comment on lines +11 to +16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Default environment lacks secrets; downstream attribute access will crash.

main.tf reads var.environment.secrets. With the current default, var.environment.secrets is undefined and will trigger an evaluation error. Add a default empty map for secrets.

 variable "environment" {
   type = any
   default = {
     namespace = "default"
+    secrets   = {}
   }
 }
🤖 Prompt for AI Agents
In modules/helm/k8s/0.2/variables.tf around lines 11 to 16, the variable
"environment" default lacks a "secrets" key which causes var.environment.secrets
to be undefined downstream; update the default value to include a secrets
attribute (empty map) so the structure matches expectations (e.g., add secrets =
{} inside the default map) and ensure the variable type still accepts that
shape.


variable "inputs" {
type = any
default = {}
}
13 changes: 13 additions & 0 deletions modules/helm/k8s/0.2/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
terraform {
required_providers {
helm3 = {
source = "hashicorp/helm3"
}
Comment on lines +3 to +5
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use the official Helm provider source and local name; hashicorp/helm3 is invalid.

Terraform’s official Helm provider is hashicorp/helm, and the local name should match how you reference the provider elsewhere. Using helm3 here plus a string literal provider reference in resources will fail provider wiring at plan time.

Apply this diff to align with the official provider:

   required_providers {
-    helm3 = {
-      source  = "hashicorp/helm3"
-    }
+    helm = {
+      source  = "hashicorp/helm"
+    }
     kubernetes = {
       source = "hashicorp/kubernetes"
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
helm3 = {
source = "hashicorp/helm3"
}
required_providers {
helm = {
source = "hashicorp/helm"
}
kubernetes = {
source = "hashicorp/kubernetes"
}
}
🤖 Prompt for AI Agents
In modules/helm/k8s/0.2/versions.tf around lines 3 to 5, the provider source and
local name are incorrect: replace the invalid "hashicorp/helm3" and local name
"helm3" with the official provider source "hashicorp/helm" and a local name that
matches how the provider is referenced elsewhere in the module (e.g., "helm" if
resources refer to provider.helm); update any provider aliases/references in the
module to match the new local name so Terraform can wire the provider correctly
at plan time.

kubernetes = {
source = "hashicorp/kubernetes"
}
}
required_version = ">= 0.13"
}