Cluster information:
Kubernetes version: 1.28
Cloud being used: AWS
Installation method: Terraform
Hi folks. I’m encountering an issue with Terraform provider for Kubernetes where lifecycle.ignore_changes
does not appear to be working in the kubernetes_manifest
resource. I’ve filed a GitHub issue for same.
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
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!