Skip to content

ignore_changes not working in kubernetes_manifest #2410

@thecosmicfrog

Description

@thecosmicfrog

Terraform version, Kubernetes provider version and Kubernetes version

Terraform version: Terraform v1.5.5 on darwin_arm64
Kubernetes Provider version: v2.25.2
Kubernetes version: 1.28

Terraform configuration

locals {
  applications = {
    application-1 = {
      chart = "application-1"
    }
    application-2 = {
      chart = "application-2"
    }
    application-3 = {
      chart   = "application-3"
      version = 3.46.0
    }
  }
}

resource "kubernetes_manifest" "argocd_application" {
  for_each = local.applications

  manifest = {
    apiVersion = "argoproj.io/v1alpha1"
    kind       = "Application"

    metadata = {
      name      = each.key
      namespace = "kube-system"

      finalizers = [
        # Foreground cascading deletion.
        "resources-finalizer.argocd.argoproj.io"
      ]
    }

    spec = {
      project = "default"

      source = {
        repoURL        = "https://artifactory.example.com/artifactory/helm-all"
        chart          = each.value["chart"]
        targetRevision = try(var.charts[each.value["chart"]]["version"], null)

        helm = {
          valuesObject = {
            replicas = 1
          }

          parameters = concat([
            {
              name = "image.name"
              value = each.key
            }
          ],
            try(each.value["version"], null) != null ? [
              {
                name = "image.tag"
                value = each.value["version"]
              }
            ] : [],
          )
        }
      }

      destination = {
        server    = "https://kubernetes.default.svc"
        namespace = "mynamespace"
      }

      syncPolicy = {
        automated = {
          prune    = true
          selfHeal = true
        }
      }
    }
  }
}

Question

Hi all. I am using the kubernetes_manifest resource in order to install Argo CD Application objects (spec is defined here).

Initial object creation works without issue, and subsequent runs of terraform apply show no changes (as I would expect).

However, if the input value of manifest.spec.source.helm.valuesObject changes (say, the value of foo changes from bar to bar123), the subsequent run of terraform apply says that the entire kubernetes_manifest resource must be replaced, alongside a massive number of added and changed fields:

Terraform will perform the following actions:

  # kubernetes_manifest.argocd_application["application-3"] must be replaced
-/+ resource "kubernetes_manifest" "argocd_application" {
      ~ manifest = {
          ~ spec       = {
              ~ source      = {
                  ~ helm           = {
                      ~ valuesObject = {
                          ~ foo                   = "bar" -> "bar123"
                            # (6 unchanged attributes hidden)
                        }
                        # (1 unchanged attribute hidden)
                    }
                    # (3 unchanged attributes hidden)
                }
                # (3 unchanged attributes hidden)
            }
            # (3 unchanged attributes hidden)
        }
      ~ object   = {
          ~ metadata   = {
              + annotations                = (known after apply)
              + creationTimestamp          = (known after apply)
              + deletionGracePeriodSeconds = (known after apply)
              + deletionTimestamp          = (known after apply)
              + generateName               = (known after apply)
              + generation                 = (known after apply)
              + labels                     = (known after apply)
              + managedFields              = (known after apply)
                name                       = "application-3"
              + ownerReferences            = (known after apply)
              + resourceVersion            = (known after apply)
              + selfLink                   = (known after apply)
              + uid                        = (known after apply)
                # (2 unchanged attributes hidden)
            }
          ~ operation  = {
              + info        = (known after apply)
              ~ initiatedBy = {
                  + automated = (known after apply)
                  + username  = (known after apply)
                }
              ~ retry       = {
                  ~ backoff = {
                      + duration    = (known after apply)
                      + factor      = (known after apply)
                      + maxDuration = (known after apply)
                    }
                  + limit   = (known after apply)
                }
              ~ sync        = {
                  + dryRun       = (known after apply)
                  + manifests    = (known after apply)
                  + prune        = (known after apply)
                  + resources    = (known after apply)
                  + revision     = (known after apply)
                  + revisions    = (known after apply)
                  ~ source       = {
                      + chart          = (known after apply)
                      ~ directory      = {
                          + exclude = (known after apply)
                          + include = (known after apply)
                          ~ jsonnet = {
                              + extVars = (known after apply)
                              + libs    = (known after apply)
                              + tlas    = (known after apply)
                            }
                          + recurse = (known after apply)
                        }
                      ~ helm           = {
                          + fileParameters          = (known after apply)
                          + ignoreMissingValueFiles = (known after apply)
                          + parameters              = (known after apply)
                          + passCredentials         = (known after apply)
                          + releaseName             = (known after apply)
                          + skipCrds                = (known after apply)
                          + valueFiles              = (known after apply)
                          + values                  = (known after apply)
                          + valuesObject            = (known after apply)
                          + version                 = (known after apply)
                        }
                      ~ kustomize      = {
                          + commonAnnotations         = (known after apply)
                          + commonAnnotationsEnvsubst = (known after apply)
                          + commonLabels              = (known after apply)
                          + forceCommonAnnotations    = (known after apply)
                          + forceCommonLabels         = (known after apply)
                          + images                    = (known after apply)
                          + namePrefix                = (known after apply)
                          + nameSuffix                = (known after apply)
                          + namespace                 = (known after apply)
                          + patches                   = (known after apply)
                          + replicas                  = (known after apply)
                          + version                   = (known after apply)
                        }
                      + path           = (known after apply)
                      ~ plugin         = {
                          + env        = (known after apply)
                          + name       = (known after apply)
                          + parameters = (known after apply)
                        }
                      + ref            = (known after apply)
                      + repoURL        = (known after apply)
                      + targetRevision = (known after apply)
                    }
                  + sources      = (known after apply)
                  + syncOptions  = (known after apply)
                  ~ syncStrategy = {
                      ~ apply = {
                          + force = (known after apply)
                        }
                      ~ hook  = {
                          + force = (known after apply)
                        }
                    }
                }
            }
          ~ spec       = {
              ~ destination          = {
                  + name      = (known after apply)
                    # (2 unchanged attributes hidden)
                }
              + ignoreDifferences    = (known after apply)
              + info                 = (known after apply)
              + revisionHistoryLimit = (known after apply)
              ~ source               = {
                  ~ directory      = {
                      + exclude = (known after apply)
                      + include = (known after apply)
                      ~ jsonnet = {
                          + extVars = (known after apply)
                          + libs    = (known after apply)
                          + tlas    = (known after apply)
                        }
                      + recurse = (known after apply)
                    }
                  ~ helm           = {
                      + fileParameters          = (known after apply)
                      + ignoreMissingValueFiles = (known after apply)
                      ~ parameters              = [
                          ~ {
                              + forceString = (known after apply)
                                name        = "image.name"
                                # (1 unchanged attribute hidden)
                            },
                          ~ {
                              + forceString = (known after apply)
                                name        = "image.tag"
                                # (1 unchanged attribute hidden)
                            },
                        ]
                      + passCredentials         = (known after apply)
                      + releaseName             = (known after apply)
                      + skipCrds                = (known after apply)
                      + valueFiles              = (known after apply)
                      + values                  = (known after apply)
                      ~ valuesObject            = {
                          + foo                            = "bar"
                            # (7 unchanged attributes hidden)
                        }
                      + version                 = (known after apply)
                    }
                  ~ kustomize      = {
                      + commonAnnotations         = (known after apply)
                      + commonAnnotationsEnvsubst = (known after apply)
                      + commonLabels              = (known after apply)
                      + forceCommonAnnotations    = (known after apply)
                      + forceCommonLabels         = (known after apply)
                      + images                    = (known after apply)
                      + namePrefix                = (known after apply)
                      + nameSuffix                = (known after apply)
                      + namespace                 = (known after apply)
                      + patches                   = (known after apply)
                      + replicas                  = (known after apply)
                      + version                   = (known after apply)
                    }
                  + path           = (known after apply)
                  ~ plugin         = {
                      + env        = (known after apply)
                      + name       = (known after apply)
                      + parameters = (known after apply)
                    }
                  + ref            = (known after apply)
                    # (3 unchanged attributes hidden)
                }
              + sources              = (known after apply)
              ~ syncPolicy           = {
                  ~ automated                = {
                      + allowEmpty = (known after apply)
                        # (2 unchanged attributes hidden)
                    }
                  ~ managedNamespaceMetadata = {
                      + annotations = (known after apply)
                      + labels      = (known after apply)
                    }
                  ~ retry                    = {
                      ~ backoff = {
                          + duration    = (known after apply)
                          + factor      = (known after apply)
                          + maxDuration = (known after apply)
                        }
                      + limit   = (known after apply)
                    }
                  + syncOptions              = (known after apply)
                }
                # (1 unchanged attribute hidden)
            }
            # (2 unchanged attributes hidden)
        }
    }

I understand that objects created by Terraform can change outside of its control, and it appears that Argo CD's controllers are doing that in this case, by creating default values for optional fields. As such, I added a few lifecycle.ignore_changes entries to instruct Terraform to disregard these:

resource "kubernetes_manifest" "argocd_application" {
  lifecycle {
    ignore_changes = [
      object.metadata.annotations,
      object.metadata.finalizers,
      object.metadata.labels,
      object.operation.initiatedBy.automated
    ]
  }

  [... rest of resource...]
}

However, this appears to have made no difference, as a terraform apply still gives me the same "must be replaced" output with all the same fields.

I feel like I may be missing something here, so I'm asking for assistance at this stage. Thanks! 🙂

Metadata

Metadata

Assignees

No one assigned

    Labels

    acknowledgedIssue has undergone initial review and is in our work queue.bugmanifest

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions