terraform-provider-kubectl icon indicating copy to clipboard operation
terraform-provider-kubectl copied to clipboard

The "count" value depends on resource attributes that cannot be determined until apply

Open eugeneromero opened this issue 4 years ago • 44 comments

Hello!

I have the following Terraform code inside of a module:

data "kubectl_path_documents" "esCustomResourcesManifests" {
  pattern = "modules/elasticsearch/all-in-one-1.3.0.yaml"
  disable_template = true
}

resource "kubectl_manifest" "esCustomResources" {
  count     = length(data.kubectl_path_documents.esCustomResourcesManifests.documents)
  yaml_body = element(data.kubectl_path_documents.esCustomResourcesManifests.documents, count.index)
}

When running terraform plan, I get:

Error: Invalid count argument

  on modules/elasticsearch/elasticSearch.tf line 24, in resource "kubectl_manifest" "esCustomResources":
  24:   count     = length(data.kubectl_path_documents.esCustomResourcesManifests.documents)

The "count" value depends on resource attributes that cannot be determined
until apply, so Terraform cannot predict how many instances will be created.
To work around this, use the -target argument to first apply only the
resources that the count depends on.

This does not seem to be the same as issue #58, since I am not pulling any external variables. Is there something obvious I am missing?

Versions: Terraform v0.13.5 provider registry.terraform.io/gavinbunney/kubectl v1.9.1

YAML file: https://download.elastic.co/downloads/eck/1.3.0/all-in-one.yaml

Thanks in advance for your help!

eugeneromero avatar Nov 16 '20 18:11 eugeneromero

Any updates on this issue ? Am facing similar problem, this happens only if it is used inside a module

sathish-kumar-narayanan avatar Jan 28 '21 09:01 sathish-kumar-narayanan

Hi @eugeneromero my guess is the pathing isn't quite right as you need to have the ${path.module}/ as a prefix for the pattern path (obviously fixing up the relative path to that file)... so try something like this:

data "kubectl_path_documents" "esCustomResourcesManifests" {
  pattern = "${path.module}/modules/elasticsearch/all-in-one-1.3.0.yaml"
  disable_template = true
}

resource "kubectl_manifest" "esCustomResources" {
  count     = length(data.kubectl_path_documents.esCustomResourcesManifests.documents)
  yaml_body = element(data.kubectl_path_documents.esCustomResourcesManifests.documents, count.index)
}

gavinbunney avatar Feb 08 '21 23:02 gavinbunney

Hi @gavinbunney hit the same issue:

data "kubectl_path_documents" "dashboard-manifests" {
  pattern = "${path.module}/yaml/dashboard.yaml"
}

resource "kubectl_manifest" "dashboard" {
  count     = length(data.kubectl_path_documents.dashboard-manifests.documents)
  yaml_body = element(data.kubectl_path_documents.dashboard-manifests.documents, count.index)
}

Error in resource "kubectl_manifest" "dashboard":

   6:   count     = length(data.kubectl_path_documents.dashboard-manifests.documents)

The "count" value depends on resource attributes that cannot be determined

sunsingerus avatar Feb 18 '21 13:02 sunsingerus

kubectl provider is the latest one, 1.10.0

sunsingerus avatar Feb 18 '21 13:02 sunsingerus

this happens only if it is used inside a module

@gavinbunney Confirm, in my case it does not work inside a module

sunsingerus avatar Feb 18 '21 13:02 sunsingerus

I think we hit the same issue with a for_each inside a module. I can confirm that it works outside a module but not inside.

kubectl 1.10.0
terraform 0.14.7
locals {
  apply = [for v in data.kubectl_file_documents.apply.documents : {           
    data : yamldecode(v)                                                         
    content : v                                                                  
    }                                                                            
  ] 
}
data "kubectl_file_documents" "apply" {                                                                           
  content = data.flux_install.main.content                                    
}                                                                                
                                                                                 
# Apply manifests on the cluster                                                 
resource "kubectl_manifest" "apply" {                                            
  for_each   = { for v in local.apply : lower(join("/", compact([v.data.apiVersion, v.data.kind, lookup(v.data.metadata, "namespace", ""), v.data.metadata.name]))) => v.content }
  depends_on = [kubernetes_namespace.fluxv2]                                     
  yaml_body  = each.value                                                        
} 
Error: Invalid for_each argument

  on .terraform/modules/addons/modules/scaleway/fluxv2.tf line 72, in resource "kubectl_manifest" "apply":
  72:   for_each   = { for v in local.apply : lower(join("/", compact([v.data.apiVersion, v.data.kind, lookup(v.data.metadata, "namespace", ""), v.data.metadata.name]))) => v.content }

The "for_each" value depends on resource attributes that cannot be determined
until apply, so Terraform cannot predict how many instances will be created.
To work around this, use the -target argument to first apply only the
resources that the for_each depends on.

Using -target to target the Kubernetes namespace solves the problem but we'd like to run only one apply for the whole state.

rguichard avatar Feb 23 '21 11:02 rguichard

There is a workaround is this not the same as https://github.com/gavinbunney/terraform-provider-kubectl/issues/58

Although not ideal

cloudoutloud avatar Mar 06 '21 12:03 cloudoutloud

I don't think this is the same. As mentioned in the original post, there are no variables being set here. I am hitting the same problem inside a module:

data "kubectl_path_documents" "manifests" {
  pattern = "${path.module}/yaml/k8s/*.yaml"
}

resource "kubectl_manifest" "vector-yaml" {
  count     = length(data.kubectl_path_documents.manifests.documents)
  yaml_body = element(data.kubectl_path_documents.manifests.documents, count.index)
}

gives:

Error: Invalid count argument

  on modules/vector/main.tf line 29, in resource "kubectl_manifest" "vector-yaml":
  29:   count     = length(data.kubectl_path_documents.manifests.documents)

The "count" value depends on resource attributes that cannot be determined
until apply, so Terraform cannot predict how many instances will be created.
To work around this, use the -target argument to first apply only the
resources that the count depends on.
% terraform version                                                                                               
Terraform v0.14.7
+ provider registry.terraform.io/gavinbunney/kubectl v1.10.0
+ provider registry.terraform.io/hashicorp/external v2.1.0
+ provider registry.terraform.io/hashicorp/google v3.58.0
+ provider registry.terraform.io/hashicorp/google-beta v3.58.0
+ provider registry.terraform.io/hashicorp/helm v2.0.2
+ provider registry.terraform.io/hashicorp/kubernetes v1.13.3
+ provider registry.terraform.io/hashicorp/kubernetes-alpha v0.2.1
+ provider registry.terraform.io/hashicorp/null v3.1.0
+ provider registry.terraform.io/hashicorp/random v3.1.0
+ provider registry.terraform.io/mongey/kafka v0.2.12

DanTulovsky avatar Mar 07 '21 19:03 DanTulovsky

I have the same issue and couldn't find any solution so far.

yongzhang avatar Mar 08 '21 06:03 yongzhang

Having the same issue but with kubect_file_documents

data "kubectl_file_documents" "manifests" {
  content = file("${path.module}/manifests/metrics_server.yaml")
}

resource "kubectl_manifest" "metrics_server" {
  count     = length(data.kubectl_file_documents.manifests.documents)
  yaml_body = element(data.kubectl_file_documents.manifests.documents, count.index)
}

Resulting in

 6:   count     = length(data.kubectl_file_documents.manifests.documents)

The "count" value depends on resource attributes that cannot be determined
until apply, so Terraform cannot predict how many instances will be created.
To work around this, use the -target argument to first apply only the
resources that the count depends on.

bakayolo avatar Mar 18 '21 10:03 bakayolo

same issue

  50:   count     = length(data.kubectl_path_documents.manifests.documents)

The "count" value depends on resource attributes that cannot be determined
until apply, so Terraform cannot predict how many instances will be created.
To work around this, use the -target argument to first apply only the
resources that the count depends on.

msevastian avatar Mar 26 '21 22:03 msevastian

I'm experiencing the same issue with the code found here: https://github.com/fluxcd/terraform-provider-flux/blob/main/examples/github/main.tf. Everything works fine but as soon as the code is put into a module planning fails with the following error:

local.sync will be known only after apply
 
The "for_each" value depends on resource attributes that cannot be
determined until apply, so Terraform cannot predict how many instances will
be created. To work around this, use the -target argument to first apply
only the resources that the for_each depends on.

arodus avatar Apr 26 '21 06:04 arodus

The same problem manifested itself here today. Yesterday and before it was working fine.

shyblower avatar Apr 28 '21 08:04 shyblower

I had the same issue with vars. You can't reference asg_name = aws_autoscaliing_group.foo.name.

Arsen-Uulu avatar May 06 '21 16:05 Arsen-Uulu

Having same issue, only inside a module. TF 0.15.3 and 1.10.0 provider

theothermike avatar May 21 '21 17:05 theothermike

FYI I have a hackey workaround.

Basically, I create a submodule that is its own state, that all it does it use the flux provider to generate the Manifest YAML files, and generate some terraform source code that I use in the main flux module to install. This means since I am generating terraform source, the list of manifests is well known and wont throw this error. here's some snippets (repeat same code for sync). This ought to be workable for any manifests loaded with kubectl_file_documents

# Flux
data "flux_install" "main" {
  target_path    = local.target_path
  network_policy = false
}

data "kubectl_file_documents" "install" {
  content = data.flux_install.main.content
}

# Convert documents list to include parsed yaml data
locals {
  install = [for v in data.kubectl_file_documents.install.documents : {
    data : yamldecode(v)
    content : v
    }
  ]
  install_filenames = [for v in local.install : format("%s.yaml", replace(lower(join("/", compact([v.data.apiVersion, v.data.kind, lookup(v.data.metadata, "namespace", ""), v.data.metadata.name]))), "/", "-"))]
}

# generate the install manifest files
resource "local_file" "install" {
  for_each             = { for v in local.install : lower(join("/", compact([v.data.apiVersion, v.data.kind, lookup(v.data.metadata, "namespace", ""), v.data.metadata.name]))) => v.content }
  filename             = "${path.module}/../manifests/${var.environment}/${replace(each.key, "/", "-")}.yaml"
  content              = each.value
  file_permission      = "0644"
  directory_permission = "0755"
}

// generate TF that holds the list of manifests to work around https://github.com/gavinbunney/terraform-provider-kubectl/issues/61
resource "local_file" "install-tfcode" {
  filename             = "${path.module}/../flux_install_manifests-${var.environment}.tf"
  content              = <<TF
locals {
  flux_install_manifests_${var.environment} = [
    "${join("\",\n\t\"", local.install_filenames)}"
  ]
}
TF
  file_permission      = "0644"
  directory_permission = "0755"
}

then in the main flux module i can reference these YAML and locals

resource "kubectl_manifest" "install" {
  for_each   = var.environment == "development" ? toset(local.flux_install_manifests_development) : var.environment == "staging" ? toset(local.flux_install_manifests_staging) : var.environment == "production" ? toset(local.flux_install_manifests_production) : toset(local.flux_install_manifests_development)
  depends_on = [kubernetes_namespace.flux_system]
  yaml_body  = file("${path.module}/manifests/${var.environment}/${each.value}")
}

This will also serve as a nice way to upgrade the flux manifests when there is a new provider - just re-generate these files, and run terraform apply in the actual state

theothermike avatar May 21 '21 19:05 theothermike

Exact same problem here. Was working ysterday and now it fails with:

 The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the count depends on.

My code:

data "kubectl_path_documents" "kafka-cluster-manifests" {
  pattern = "${path.module}/kafka-cluster/*.yaml"
  disable_template = true
}
resource "kubectl_manifest" "kafka-cluster" {
  count     = length(data.kubectl_path_documents.kafka-cluster-manifests.documents)
  yaml_body = element(data.kubectl_path_documents.kafka-cluster-manifests.documents, count.index)
  wait = true
}

TF version:

francois@Francoiss-Mac-mini evs-ipdvia-aws % terraform version
Terraform v0.15.5
on darwin_arm64
+ provider registry.terraform.io/gavinbunney/kubectl v1.11.1
+ provider registry.terraform.io/hashicorp/aws v3.44.0
+ provider registry.terraform.io/hashicorp/cloudinit v2.2.0
+ provider registry.terraform.io/hashicorp/helm v2.1.2
+ provider registry.terraform.io/hashicorp/kubernetes v1.13.4
+ provider registry.terraform.io/hashicorp/local v2.1.0
+ provider registry.terraform.io/hashicorp/random v3.1.0
+ provider registry.terraform.io/terraform-aws-modules/http v2.4.1

vfrans avatar Jun 07 '21 04:06 vfrans

  • exact same behaviour when I swap kubectl_path_documents to kubectl_filename_list

@gavinbunney are we doing something wrong? It seems to me like I really took the configration straight out of the documentation.

On top of that, it worked but now, with a clean state, terraform does not work because of that count...

vfrans avatar Jun 07 '21 04:06 vfrans

Same issue here... Only happens if inside a module

I think I will put a file called "yaml_count.txt" and read it with tonumber(file("${module.path}/.../yaml_count.txt")) and then count all the individual yaml files and put the number there :) (until this is fixed!)

jjhidalgar avatar Jun 30 '21 15:06 jjhidalgar

Hi, I get the same error if I do terraform apply with no existing cluster. If I have a cluster then I don't get this error. It would be nice if terraform knows this to run with no existing cluster.

data "kubectl_path_documents" "controller_manifests" {
pattern = "${path.module}/files/controller_deploy.yaml"
vars = {
eks_cluster_name = var.eks_cluster_name
}
}
#Install the ALB controller
resource "kubectl_manifest" "install_controller_manifests" {
wait = true
count = length(data.kubectl_path_documents.controller_manifests.documents)
yaml_body = element(data.kubectl_path_documents.controller_manifests.documents, count.index)
depends_on = [kubectl_path_documents.controller_manifests]
}

when I run terraform apply first time, it throws the following error. The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the count depends on.

hsalluri259 avatar Jul 27 '21 21:07 hsalluri259

Also having the same issue as others are experiencing using the same method that's indicated in the docs.

data "kubectl_file_documents" "certmanager_crds" {
  content = file("${path.module}/kubectl/certmanager-crds.yaml")
}

resource "kubectl_manifest" "certmanager-crds" {
  count      = length(data.kubectl_file_documents.certmanager_crds.documents)
  yaml_body  = element(data.kubectl_file_documents.certmanager_crds.documents, count.index)
  depends_on = [kubectl_manifest.rancher-import]
}
Error: Invalid count argument
│ 
│   on ../../Terraform.Modules.Azure.K8s-Base/src/main.tf line 179, in resource "kubectl_manifest" "certmanager-crds":
│  179:   count      = length(data.kubectl_file_documents.certmanager_crds.documents)
│ 
│ The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work
│ around this, use the -target argument to first apply only the resources that the count depends on.
❯ terraform version
Terraform v1.0.1
on darwin_amd64
+ provider registry.terraform.io/cloudflare/cloudflare v2.9.0
+ provider registry.terraform.io/gavinbunney/kubectl v1.11.3
+ provider registry.terraform.io/hashicorp/azuread v2.0.1
+ provider registry.terraform.io/hashicorp/google v3.81.0
+ provider registry.terraform.io/hashicorp/google-beta v3.81.0
+ provider registry.terraform.io/hashicorp/helm v2.0.3
+ provider registry.terraform.io/hashicorp/http v2.1.0
+ provider registry.terraform.io/hashicorp/kubernetes v2.0.3
+ provider registry.terraform.io/hashicorp/null v3.1.0
+ provider registry.terraform.io/hashicorp/random v3.1.0
+ provider registry.terraform.io/hashicorp/vault v2.23.0
+ provider registry.terraform.io/rancher/rancher2 v1.11.0

You can run terraform state show on the data resource and it returns the expected data so why would the plan say the attributes can't be determined until apply?

❯ terraform state show module.k8s-base.kubectl_manifest.certmanager-crds
# module.k8s-base.kubectl_manifest.certmanager-crds:
resource "kubectl_manifest" "certmanager-crds" {
    api_version             = "apiextensions.k8s.io/v1beta1"
    force_new               = false
    id                      = "/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/certificaterequests.cert-manager.io"
    kind                    = "CustomResourceDefinition"
...

g0atcheez avatar Sep 03 '21 15:09 g0atcheez

I'm having the same problem here inside a module. I can confirm that the hack provided here does not work either inside a module - I have not tested it outside of the module.

I even tested with a slimmed down version of a single yaml document with the following configuration and it still doesn't work...

data "kubectl_path_documents" "secrets_test_hack" {
  pattern = "${path.module}/k8s/secrets-test/00-namespace.yaml"
}

resource "kubectl_manifest" "secrets_test" {
  count     = length(data.kubectl_path_documents.secrets_test_hack.documents)
  yaml_body = element(data.kubectl_path_documents.secrets_test_hack.documents, count.index)
}
╷
│ Error: Invalid count argument
│ 
│   on modules/secrets-csi-test-app/main.tf line 28, in resource "kubectl_manifest" "secrets_test":
│   28:   count     = length(data.kubectl_path_documents.secrets_test_hack.documents)
│ 
│ The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to
│ first apply only the resources that the count depends on.
╵

calexandre avatar Sep 07 '21 11:09 calexandre

I managed to build a hacky workaround that might work for most of you guys... Basically I replaced the count source using other terraform's built-in functions to fetch the document count...

Here's an example (taken from by previous comment:

data "kubectl_path_documents" "secrets_test" {
  pattern = "${path.module}/k8s/secrets-test/*.yaml"
}

resource "kubectl_manifest" "secrets_test" {
  count = length(
    flatten(
      toset([
        for f in fileset(".", data.kubectl_path_documents.secrets_test.pattern) : split("\n---\n", file(f))
        ]
      )
    )
  )
  yaml_body = element(data.kubectl_path_documents.secrets_test.documents, count.index)
}

What is happening here:

  • I calculate all the possible documents in the same path that the data block is using using a fileset function
  • I wrap that result in a for loop and to support the yaml's multi-document feature, I'm also splitting each file with \n---\n to ensure that I only grab the correct ones.
  • In the end I must flatten the set because in case if multi-documents, there will be arrays inside arrays.

I know this is not an elegant solution and there might be other issues that I missed, but it's better than using -target to get around the issue...

I tested locally with multiple scenarios and for now this workaround works for me.

calexandre avatar Sep 07 '21 12:09 calexandre

@calexandre I tested with terraform 1.0.5 and kubectl 1.11.3 on a fresh project and I don't reproduce this module issue.

data "kubectl_path_documents" "nginx_manifests" {
  pattern = "${path.module}/templates/deploy_nginx.yaml"
  vars = {
    domainName = var.domain_name
    namespace = var.name_space
  }
}

resource "kubectl_manifest" "deploy_nginx" {
  count     = length(data.kubectl_path_documents.nginx_manifests.documents)
  yaml_body = element(data.kubectl_path_documents.nginx_manifests.documents, count.index)
}
module "deploy_nginx_test1" {
  source = "./modules/nginx_deploy_module"
  domain_name = "test1"
  name_space = "spike-tf-test1"
}

Works in both creation and update.

jodem avatar Sep 07 '21 13:09 jodem

@jodem it doesn't happen always, that's why some folks are reporting the issue.. I've used this module for at least one year, and it never happened to me, until today...

calexandre avatar Sep 07 '21 15:09 calexandre

Ok @calexandre thanks for the clarification, it's a bit scary. Thanks also for providing an elegant hack to mitigate the issue.

jodem avatar Sep 08 '21 07:09 jodem

Ok @calexandre thanks for the clarification, it's a bit scary. Thanks also for providing an elegant hack to mitigate the issue.

Thanks! I don't know about the "elegant" part ;) But the actual code, shouldn't be too different I guess...

calexandre avatar Sep 08 '21 08:09 calexandre

Yep, just started hitting this with no changes 😞. How odd.

iJebus avatar Nov 23 '21 06:11 iJebus

Hit this bug today. Very annoying. :(

sidh avatar Jan 17 '22 09:01 sidh

This is very strange, if I put this in a file on its own:

resource "kubectl_manifest" "pixie-viziers" {
  for_each  = data.kubectl_file_documents.pixie-viziers.manifests
  yaml_body = each.value
}

resource "kubectl_manifest" "pixie-crd" {
  for_each  = data.kubectl_file_documents.pixie-crd.manifests
  yaml_body = each.value
}

data "kubectl_file_documents" "pixie-viziers" {
  content = data.http.pixie-viziers.body
}

data "kubectl_file_documents" "pixie-crd" {
  content = data.http.pixie-crd.body
}

data "http" "pixie-viziers" {
  url = "https://raw.githubusercontent.com/pixie-labs/pixie/main/k8s/operator/crd/base/px.dev_viziers.yaml"
}

data "http" "pixie-crd" {
  url = "https://raw.githubusercontent.com/pixie-labs/pixie/main/k8s/operator/helm/crds/olm_crd.yaml"
}

provider "kubectl" {
  host                   = "host"
  cluster_ca_certificate = "cert"
  token                  = "token"
  load_config_file       = false
}

terraform {
  required_version = ">= 1.0.4"

  required_providers {
    kubectl = {
      source  = "gavinbunney/kubectl"
      version = ">= 1.13.1"
    }
    http = {
      source  = "hashicorp/http"
      version = "2.1.0"
    }
  }
}

it works but if I use that same code in a module, it fails with:

╷
│ Error: Invalid for_each argument
│
│   on .terraform/modules/eks/eks-init/newrelic.tf line 29, in resource "kubectl_manifest" "pixie-viziers":
│   29:   for_each  = data.kubectl_file_documents.pixie-viziers.manifests
│     ├────────────────
│     │ data.kubectl_file_documents.pixie-viziers.manifests is a map of string, known only after apply
│
│ The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the for_each depends on.
╵
╷
│ Error: Invalid for_each argument
│
│   on .terraform/modules/eks/eks-init/newrelic.tf line 34, in resource "kubectl_manifest" "pixie-crd":
│   34:   for_each  = data.kubectl_file_documents.pixie-crd.manifests
│     ├────────────────
│     │ data.kubectl_file_documents.pixie-crd.manifests is a map of string, known only after apply
│
│ The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the for_each depends on.
╵

luisdavim avatar Jan 18 '22 17:01 luisdavim