env0/terraform-provider-env0

env0_project_policy: infinite TTL (Infinite) is silently dropped, falls back to org policy

Open

#1,110 opened on Jul 7, 2026

View on GitHub
 (0 comments) (0 reactions) (0 assignees)Go (14 forks)auto 404
buggood first issue

Repository metrics

Stars
 (39 stars)
PR merge metrics
 (PR metrics pending)

Description

Describe the bug

Setting default_ttl = "Infinite" and max_ttl = "Infinite" on env0_project_policy does not produce an infinite TTL. Environments silently inherit the organization TTL policy instead.

The provider converts "Infinite" to an empty string in the update payload:

// env0/resource_project_policy.go (Update)
if payload.DefaultTtl == INFINITE { payload.DefaultTtl = "" }
if payload.MaxTtl == INFINITE   { payload.MaxTtl = "" }

But both fields are tagged omitempty, so the empty string is dropped from the request body entirely (it is not serialized as JSON null):

// client/project_policy.go
type PolicyUpdatePayload struct {
    ...
    MaxTtl     string `json:"maxTtl,omitempty"`
    DefaultTtl string `json:"defaultTtl,omitempty"`
    ...
}

The PUT /policies API documents that infinite requires an explicit null ("Must be 6-h, 12-h, 1-d, 3-d, 1-w, 2-w, 1-M, inherit, or explicit null which means infinite"). Since omitempty yields an absent field rather than null, infinite is never set. This is most visible when both TTLs are Infinite, because the PUT then carries no TTL fields at all and the API applies the inherited org policy.

To Reproduce

  1. Have an organization TTL policy set (e.g. max_ttl = "4-w").
  2. Apply the following:
resource "env0_project_policy" "example" {
  project_id  = data.env0_project.example.id
  default_ttl = "Infinite"
  max_ttl     = "Infinite"
}
  1. Create an environment in that project - it gets a 4-week TTL and is auto-destroyed, instead of never being destroyed.

Expected behavior

Infinite should result in maxTtl: null / defaultTtl: null in the request body, so the API applies an infinite (never-destroy) TTL.

Provider Version

1.31.4 (also reproduces on 1.30.x)

Screenshots

N/A

Additional context

The unit test in resource_project_policy_test.go only covers max_ttl = "Infinite" alongside a concrete default_ttl = "7-h", and asserts the Go payload MaxTtl: "" at the mock boundary - it never verifies the serialized JSON nor the both-Infinite case, so the Infinite → "" → omitted round-trip passes the mock but breaks against the real API.

Suggested fix: make MaxTtl/DefaultTtl in PolicyUpdatePayload pointers (*string) and set them to nil for Infinite so they serialize as explicit null (or drop omitempty and send null). Add coverage for both fields set to Infinite.

Workaround: map Infinite to a large finite TTL (e.g. 120-M) in the caller's Terraform.

Contributor guide