PrefectHQ / terraform-provider-prefect

Terraform Provider for Prefect Cloud
https://registry.terraform.io/providers/PrefectHQ/prefect/latest/docs
Apache License 2.0
32 stars 16 forks source link

Imported `prefect_work_pool`'s `base_job_template` is overwritten on initial `plan/apply` when it's the same #259

Open kevingrismore opened 2 days ago

kevingrismore commented 2 days ago

Community Note

Terraform Version

Terraform v1.9.5
on darwin_arm64
+ provider registry.terraform.io/prefecthq/prefect v2.2.2

Affected Resource(s)

Terraform Configuration Files

resource "prefect_work_pool" "k8s-demo" {
  name              = "k8s-demo"
  type              = "kubernetes"
  workspace_id      = "<workspace-id>"
  paused            = false
  base_job_template = file("./k8s-demo-template.json")
}

Debug Output

Panic Output

Expected Behavior

When importing an existing work pool with terraform import prefect_work_pool.k8s-demo <workspace-id>,k8s-demo, if my work pool resource in the .tf above and the contents of the base template JSON in the referenced file are the same, plan should detect no changes and apply should do nothing.

Actual Behavior

terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # prefect_work_pool.k8s-demo will be updated in-place
  ~ resource "prefect_work_pool" "k8s-demo" {
      + base_job_template = jsonencode(
            {
              + job_configuration = {
                  + cluster_config            = "{{ cluster_config }}"
                  + command                   = "{{ command }}"
                  + env                       = "{{ env }}"
                  + job_manifest              = {
                      + apiVersion = "batch/v1"
                      + kind       = "Job"
                      + metadata   = {
                          + generateName = "{{ name }}-"
                          + labels       = "{{ labels }}"
                          + namespace    = "{{ namespace }}"
                        }
                      + spec       = {
                          + backoffLimit            = 0
                          + template                = {
                              + spec = {
                                  + completions        = 1
                                  + containers         = [
                                      + {
                                          + args            = "{{ command }}"
                                          + env             = "{{ env }}"
                                          + image           = "{{ image }}"
                                          + imagePullPolicy = "{{ image_pull_policy }}"
                                          + name            = "prefect-job"
                                          + resources       = "{{ resources }}"
                                        },
                                    ]
                                  + nodeSelector       = "{{ node_selector }}"
                                  + parallelism        = 1
                                  + restartPolicy      = "Never"
                                  + serviceAccountName = "{{ service_account_name }}"
                                }
                            }
                          + ttlSecondsAfterFinished = "{{ finished_job_ttl }}"
                        }
                    }
                  + job_watch_timeout_seconds = "{{ job_watch_timeout_seconds }}"
                  + labels                    = "{{ labels }}"
                  + name                      = "{{ name }}"
                  + namespace                 = "{{ namespace }}"
                  + pod_watch_timeout_seconds = "{{ pod_watch_timeout_seconds }}"
                  + stream_output             = "{{ stream_output }}"
                }
              + variables         = {
                  + definitions = {
                      + KubernetesClusterConfig = {
                          + block_schema_references = {}
                          + block_type_slug         = "kubernetes-cluster-config"
                          + description             = <<-EOT
                                Stores configuration for interaction with Kubernetes clusters.

                                See `from_file` for creation.
                            EOT
                          + properties              = {
                              + config       = {
                                  + description = "The entire contents of a kubectl config file."
                                  + title       = "Config"
                                  + type        = "object"
                                }
                              + context_name = {
                                  + description = "The name of the kubectl context to use."
                                  + title       = "Context Name"
                                  + type        = "string"
                                }
                            }
                          + required                = [
                              + "config",
                              + "context_name",
                            ]
                          + secret_fields           = []
                          + title                   = "KubernetesClusterConfig"
                          + type                    = "object"
                        }
                    }
                  + description = <<-EOT
                        Default variables for the Kubernetes worker.

                        The schema for this class is used to populate the `variables` section of the default
                        base job template.
                    EOT
                  + properties  = {
                      + base_name                 = {
                          + description = "Base Name given to infrastructure created by a worker. {flow_name}-{deployment_name}"
                          + title       = "Base Name"
                          + type        = "string"
                        }
                      + cluster_config            = {
                          + allOf       = [
                              + {
                                  + "$ref" = "#/definitions/KubernetesClusterConfig"
                                },
                            ]
                          + description = "The Kubernetes cluster config to use for job creation."
                          + title       = "Cluster Config"
                        }
                      + command                   = {
                          + description = "The command to use when starting a flow run. In most cases, this should be left blank and the command will be automatically generated by the worker."
                          + title       = "Command"
                          + type        = "string"
                        }
                      + env                       = {
                          + additionalProperties = {
                              + type = "string"
                            }
                          + description          = "Environment variables to set when starting a flow run."
                          + title                = "Environment Variables"
                          + type                 = "object"
                        }
                      + finished_job_ttl          = {
                          + description = "The number of seconds to retain jobs after completion. If set, finished jobs will be cleaned up by Kubernetes after the given delay. If not set, jobs will be retained indefinitely."
                          + title       = "Finished Job TTL"
                          + type        = "integer"
                        }
                      + image                     = {
                          + description = "The image reference of a container image to use for created jobs. If not set, the latest Prefect image will be used."
                          + example     = "docker.io/prefecthq/prefect:2-latest"
                          + title       = "Image"
                          + type        = "string"
                        }
                      + image_pull_policy         = {
                          + default     = "IfNotPresent"
                          + description = "The Kubernetes image pull policy to use for job containers."
                          + enum        = [
                              + "IfNotPresent",
                              + "Always",
                              + "Never",
                            ]
                          + title       = "Image Pull Policy"
                          + type        = "string"
                        }
                      + job_watch_timeout_seconds = {
                          + description = "Number of seconds to wait for each event emitted by a job before timing out. If not set, the worker will wait for each event indefinitely."
                          + title       = "Job Watch Timeout Seconds"
                          + type        = "integer"
                        }
                      + labels                    = {
                          + additionalProperties = {
                              + type = "string"
                            }
                          + description          = "Labels applied to infrastructure created by a worker."
                          + title                = "Labels"
                          + type                 = "object"
                        }
                      + name                      = {
                          + description = "Name given to infrastructure created by a worker."
                          + title       = "Name"
                          + type        = "string"
                        }
                      + namespace                 = {
                          + default     = "default"
                          + description = "The Kubernetes namespace to create jobs within."
                          + title       = "Namespace"
                          + type        = "string"
                        }
                      + node_selector             = {
                          + description = "a selector which must be true for the pod to fit on a node. Selector which must match a node's labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/"
                          + title       = "Node Selector"
                          + type        = "object"
                        }
                      + pod_watch_timeout_seconds = {
                          + default     = 60
                          + description = "Number of seconds to watch for pod creation before timing out."
                          + title       = "Pod Watch Timeout Seconds"
                          + type        = "integer"
                        }
                      + resources                 = {
                          + title = "Resources"
                          + type  = "object"
                        }
                      + service_account_name      = {
                          + description = "The Kubernetes service account to use for job creation."
                          + title       = "Service Account Name"
                          + type        = "string"
                        }
                      + stream_output             = {
                          + default     = true
                          + description = "If set, output will be streamed from the job to local standard output."
                          + title       = "Stream Output"
                          + type        = "boolean"
                        }
                    }
                  + type        = "object"
                }
            }
        )
        id                = "757d7c42-2f21-4138-b499-c1b924159d30"
        name              = "k8s-demo"
      ~ updated           = "2024-08-27T17:03:36Z" -> (known after apply)
        # (6 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

Steps to Reproduce

  1. Add prefect_work_pool "k8s-demo" resource with matching definition and reference to base job template to .tf
  2. terraform import prefect_work_pool.k8s-demo <workspace-id>,k8s-demo
  3. terraform plan

Important Factoids

References

mitchnielsen commented 2 days ago

Hmm, will need to dig into this further but this does seem related to https://github.com/PrefectHQ/terraform-provider-prefect/pull/249. The JSON object gets rearranged a bit between what a user defines and what's actually stored in Prefect, causing Terraform to think the object is different. That PR helps account for the difference, but this might be an edge case.