Skip to content

Latest commit

Β 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ”΄ F5 BIG-IP LTM Policy Terraform Module

terraform-bigip-ltm-policy manages a single bigip_ltm_policy -- an LTM local traffic policy with its rule (condition/action) blocks rendered inline, since no separate provider resource exists for a policy rule -- against the F5Networks/bigip Terraform provider ~> 1.28, TMOS >= v12.1.1.

Terraform Provider Module Type Resources Posture


🧩 Overview

  • 🧭 Manages one bigip_ltm_policy.this -- an LTM local traffic policy that steers requests via ordered rule entries, each a condition/action pair evaluated against the policy's own strategy (e.g. first-match, best-match, all-match).
  • 🧡 Renders rule as a dynamic block over a map(object(...)) keyed by each rule's own name -- BIG-IP has no separate provider resource for a policy rule, so growing or shrinking the rule set never re-indexes another rule's plan entry (for_each, never count).
  • πŸ”€ Each rule carries its own nested condition and action lists (ordered, ANDed unless a condition's not/all flags say otherwise) -- both are 1:1 mirrors of the live bigip_ltm_policy provider schema, field-for-field.
  • πŸ”— Consumes pools by full-path name inside rule[*].action[*].pool from terraform-bigip-ltm-pool -- this module never creates a pool itself, only references one.
  • πŸ”’ Deliberately omits published_copy as a module variable -- it is deprecated on the live resource and the policy auto-publishes; surfacing it would only invite callers to depend on a no-op field.
  • 🏷️ Emits name (full path, the practical cross-reference key), id, and rule_names (diagnostics), consumed by terraform-bigip-ltm-virtual-server's policy_names list.
  • 🚫 Owns nothing downstream -- virtual servers that attach this policy live in the sibling terraform-bigip-ltm-virtual-server module, which consumes this module's name output by full path.

πŸ’‘ Why it matters: a local traffic policy externalizes request-steering logic (URI/host routing, header rewrites, redirects, pool selection) out of iRule TCL and into a condition/action table that Terraform can diff rule-by-rule. Keying each rule on its own name, rather than a list index, means adding rule #6 never touches the plan for rules #1-5.


❀️ Support this project

If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:

Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!


πŸ—ΊοΈ Where this fits

flowchart LR
 POOL["terraform-bigip-ltm-pool"]
 POLICY["terraform-bigip-ltm-policy (this module)"]
 VS["terraform-bigip-ltm-virtual-server"]

 POOL -->|"pool_name, full-path, referenced inside rule actions"| POLICY
 POLICY -->|"name, full-path, consumed as policy_names"| VS

 style POLICY fill:#E4002B,color:#ffffff
 style VS fill:#000000,color:#ffffff
 style POOL fill:#ECECEC,color:#000000
Loading

terraform-bigip-ltm-pool is this module's only formally tracked upstream input (per SCOPE.md's Consumes table) -- a rule's action.pool field takes a pool's full-path name output, and this module never creates that pool itself. terraform-bigip-ltm-virtual-server is the primary downstream consumer, wiring this module's name output into its own policy_names list. This module never creates or references a virtual server (see SCOPE.md "Out of scope / consumed by name").


🧬 What this builds

flowchart TD
 subgraph Identity["Identity"]
 NAME["var.name"]
 DESC["var.description"]
 STRAT["var.strategy"]
 end
 subgraph ProtocolControls["Protocol and Controls"]
 CTRL["var.controls"]
 REQ["var.requires"]
 end
 subgraph Rules["Rules, for_each"]
 RULE["rule, dynamic block, keyed by rule name"]
 COND["condition, dynamic, nested in rule"]
 ACT["action, dynamic, nested in rule"]
 end

 RES(["bigip_ltm_policy.this"]):::keystone

 NAME --> RES
 DESC --> RES
 STRAT --> RES
 CTRL --> RES
 REQ --> RES
 RULE -->|"for_each over var.rule"| RES
 COND -->|"nested per rule"| RULE
 ACT -->|"nested per rule"| RULE

 RES --> OUT_NAME["output: name"]
 RES --> OUT_ID["output: id"]
 RES --> OUT_RULES["output: rule_names"]

 classDef keystone fill:#000000,color:#ffffff,stroke:#000000,stroke-width:1px;
Loading

Resource inventory: exactly one resource, bigip_ltm_policy.this. There is no separate child resource -- rule, and the condition/action blocks nested inside each rule, are all rendered as dynamic blocks against a single keystone resource, not independent managed resources.

Resource Count Notes
bigip_ltm_policy.this 1 (single keystone) The policy object itself -- identity, strategy, protocol/controls, and every rule's condition/action pairs.

βœ… Provider / Versions

Requirement Value
Terraform >= 1.12.0
F5Networks/bigip provider ~> 1.28 (re-verify against the Terraform Registry before each new module wave)
Provider block None -- the caller's root module configures address/username/password/token_value
BIG-IP TMOS >= v12.1.1 (provider floor)

Schema notes that bite:

  • name is the full-path identity (/Partition/name) -- the live bigip_ltm_policy schema exposes only name, with no separate partition/full_path attribute (unlike bigip_ltm_ifile); this was verified against the live F5Networks/bigip v1.28.0 provider schema during authoring.
  • published_copy is deliberately not exposed as a module variable -- it is deprecated on the live resource and the policy auto-publishes on every apply; do not add it back without checking whether the provider has since changed that behavior.
  • requires (the protocol list, e.g. ["http"]) is effectively immutable in practice once rules referencing protocol-specific conditions/actions exist -- model a change to it as a destroy/recreate of the policy, not an in-place update.
  • controls and requires are both set(string) -- the exact accepted value set for each (e.g. "forwarding", "classification", "compression", "asm", "l7dos") is not independently confirmed against every TMOS version; verify against clouddocs.f5.com/the target device before relying on a specific control or protocol name in production.
  • condition and action are modeled as ordered lists, not maps -- unlike rule itself (keyed on the caller's own rule name), neither carries an independent natural unique key in the live schema, so list order is significant and reordering one is a full list rewrite rather than a per-element diff.
  • Field combinations inside condition/action are type/context dependent (e.g. a selector like http_uri = true paired with a match-type selector like starts_with = true and a values list) -- this module does not validate combination correctness at terraform validate time; consult clouddocs.f5.com and the provider's rendered docs for the valid combinations for a given condition/action, per the caveat already carried in variables.tf.
  • rule is for_each-keyed on the caller's own rule name (map key) -- adding or removing one rule never re-indexes another, but changing a rule's map key is a destroy/recreate of that one rule's plan entry, not an in-place rename inside the policy.

πŸ”‘ Required BIG-IP User Role / Partition Access

Manager role scoped to the target partition is sufficient; Administrator is not required for this application-layer LTM object. See SCOPE.md for the full cross-module contract this was derived from.


F5 BIG-IP Prerequisites

  • iControl REST enabled and reachable on the target device.
  • TMOS >= v12.1.1 (provider floor).
  • Target partition must already exist (this module does not create partitions).
  • Any pool referenced by full path inside a rule's action.pool must already exist on the device before this module runs -- this module suite's recommended authoring order builds pools before policies for exactly this reason.

πŸ“ Module Structure

File Role
providers.tf required_version >= 1.12.0, pinned F5Networks/bigip ~> 1.28 -- no provider {} block
variables.tf 1:1 bigip_ltm_policy argument schema, plus the rule map-of-object schema mirroring the provider's nested rule/condition/action blocks
main.tf Keystone bigip_ltm_policy.this; dynamic "rule" over var.rule, with nested dynamic "condition"/dynamic "action" per rule
outputs.tf name, id, rule_names
SCOPE.md Composite/standalone scope contract: in-scope/out-of-scope boundary, Consumes/Emits tables, required role, F5 prerequisites
README.md This document

βš™οΈ Quick Start

# Caller's root module configures the provider once -- never inside this module.
# provider "bigip" {
# address = var.bigip_address
# username = var.bigip_username
# password = var.bigip_password
# }

module "app_policy" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-policy.git?ref=v1.0.0"

  name = "/Common/app-routing-policy"
}

rule defaults to {} -- the smallest real call above produces a valid, published, but inert policy with no rules. Add rule entries to make it actually steer traffic (see the Example Library below).


πŸ”Œ Cross-Module Contract

Consumes:

Input Type Source module
rule[*].action[*].pool string (full-path pool name) terraform-bigip-ltm-pool

Emits:

Output Description Consumed by
name Full-path policy name (partition + name) terraform-bigip-ltm-virtual-server
id Provider-internal id (rarely consumed directly)
rule_names Names of the rules configured on this policy (keys of var.rule) -- diagnostics only, since rule has no separate provider resource to reference (diagnostics / reporting only)

πŸ“š Example Library

ℹ️ Every multi-field condition/action combination below follows the one pattern variables.tf itself documents with confidence (a boolean selector + a match-type selector + values, e.g. http_uri/starts_with/values for a URI-prefix match). Verify any combination against clouddocs.f5.com/the provider's rendered docs for your target TMOS version before relying on it in production -- this module does not validate condition/action semantics at terraform validate time.

1 Β· Minimal policy, no rules yet

The smallest real call -- the one required argument, no rules.

module "app_policy" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-policy.git?ref=v1.0.0"

  name = "/Common/app-routing-policy"
}

ℹ️ rule defaults to {}, controls/requires default to empty sets -- this produces a valid, published, but inert policy that matches nothing until rules are added.

2 Β· Policy with a description
module "app_policy" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-policy.git?ref=v1.0.0"

  name        = "/Common/app-routing-policy"
  description = "Order-processing app -- URI/host-based routing and maintenance redirects"
}
3 Β· Explicit match strategy
module "app_policy" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-policy.git?ref=v1.0.0"

  name     = "/Common/app-routing-policy"
  strategy = "/Common/first-match"
}

πŸ’‘ strategy takes a full-path strategy name -- /Common/first-match, /Common/best-match, and /Common/all-match are the commonly documented built-ins. Confirm the exact set available on your TMOS version before relying on a specific one (see variables.tf's own caveat).

4 Β· Policy-level controls
module "waf_aware_policy" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-policy.git?ref=v1.0.0"

  name     = "/Common/waf-aware-policy"
  controls = ["forwarding", "asm"]
}

⚠️ controls is a set(string) of per-request features this policy activates -- the exact accepted names are not independently confirmed against every TMOS version in this module; verify against clouddocs.f5.com before shipping a control name to production.

5 Β· Policy-level protocol requirement
module "http_only_policy" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-policy.git?ref=v1.0.0"

  name     = "/Common/http-only-policy"
  requires = ["http"]
}

πŸ”’ Per SCOPE.md's Provider gotchas, requires is effectively immutable in practice once rules exist -- treat a later change to this value as a deliberate destroy/recreate, not a routine edit.

6 Β· Single rule -- URI-prefix match forwarding to a pool

The canonical pattern variables.tf documents directly.

module "app_policy" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-policy.git?ref=v1.0.0"

  name = "/Common/app-routing-policy"

  rule = {
    "api-prefix" = {
      condition = [
        {
          http_uri    = true
          starts_with = true
          values      = ["/api"]
        }
      ]
      action = [
        {
          forward = true
          pool    = "/Common/api-pool"
        }
      ]
    }
  }
}

ℹ️ action.pool is consumed by full-path name from a sibling terraform-bigip-ltm-pool module's name output -- this module never creates the pool itself, it must already exist.

7 Β· Single rule -- exact host-header match to a different pool
module "app_policy" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-policy.git?ref=v1.0.0"

  name = "/Common/app-routing-policy"

  rule = {
    "host-app2" = {
      condition = [
        {
          http_host = true
          equals    = true
          values    = ["app2.example.com"]
        }
      ]
      action = [
        {
          forward = true
          pool    = "/Common/app2-pool"
        }
      ]
    }
  }
}
8 Β· Rule with multiple ANDed conditions
module "app_policy" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-policy.git?ref=v1.0.0"

  name = "/Common/app-routing-policy"

  rule = {
    "admin-post-only" = {
      condition = [
        {
          http_uri    = true
          starts_with = true
          values      = ["/admin"]
        },
        {
          http_method = true
          equals      = true
          values      = ["POST"]
        }
      ]
      action = [
        {
          forward = true
          pool    = "/Common/admin-pool"
        }
      ]
    }
  }
}

πŸ’‘ Multiple entries in a rule's condition list are ANDed together unless a condition's own not/all flags say otherwise -- both conditions above must match for the action to fire.

9 Β· Rule with a redirect action
module "maintenance_policy" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-policy.git?ref=v1.0.0"

  name = "/Common/maintenance-routing-policy"

  rule = {
    "maintenance-redirect" = {
      condition = [
        {
          http_uri = true
          equals   = true
          values   = ["/"]
        }
      ]
      action = [
        {
          redirect = true
          location = "https://maintenance.example.com/"
        }
      ]
    }
  }
}

⚠️ Verify redirect/location field naming against clouddocs.f5.com/the provider's rendered docs for your TMOS version -- as with every condition/action combination in this library, this module does not validate it at plan time.

10 Β· Rule inserting an HTTP request header
module "app_policy" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-policy.git?ref=v1.0.0"

  name = "/Common/app-routing-policy"

  rule = {
    "tag-app-version" = {
      condition = [
        {
          http_uri    = true
          starts_with = true
          values      = ["/api"]
        }
      ]
      action = [
        {
          http_header = true
          insert      = true
          tm_name     = "X-App-Version"
          value       = "2.0"
        }
      ]
    }
  }
}

⚠️ Header-insert field naming (tm_name/value vs. an alternate combination) should be confirmed against clouddocs.f5.com for your TMOS version before production use -- this is one of the less-certain combinations this module's schema permits without validating semantics.

11 Β· Multiple named rules via for_each
module "app_policy" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-policy.git?ref=v1.0.0"

  name = "/Common/app-routing-policy"

  rule = {
    "api-prefix" = {
      condition = [
        { http_uri = true, starts_with = true, values = ["/api"] }
      ]
      action = [
        { forward = true, pool = "/Common/api-pool" }
      ]
    }
    "static-prefix" = {
      condition = [
        { http_uri = true, starts_with = true, values = ["/static"] }
      ]
      action = [
        { forward = true, pool = "/Common/static-pool" }
      ]
    }
  }
}

πŸ’‘ Keying rule on each rule's own name means removing "static-prefix" later never forces Terraform to touch "api-prefix"'s state.

12 Β· Non-/Common partition policy
module "tenant_a_policy" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-policy.git?ref=v1.0.0"

  name = "/Tenant-A/app-routing-policy"

  rule = {
    "api-prefix" = {
      condition = [
        { http_uri = true, starts_with = true, values = ["/api"] }
      ]
      action = [
        { forward = true, pool = "/Tenant-A/api-pool" }
      ]
    }
  }
}

πŸ’‘ Partition is folded into name's full-path convention (/Tenant-A/...), never a separate partition variable -- per this module suite's design decision log, this module invents no implicit partition default.

13 Β· Multiple policies via root-level for_each

This module models exactly one policy per call, so a caller creating several wraps the module invocation itself at the root, keyed on a stable identifier -- never count.

locals {
  policies = {
    "app-a" = { name = "/Common/app-a-routing-policy" }
    "app-b" = { name = "/Common/app-b-routing-policy" }
  }
}

module "app_policies" {
  source   = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-policy.git?ref=v1.0.0"
  for_each = local.policies

  name = each.value.name
}

πŸ’‘ Keying on each.key means removing app-b later never forces Terraform to touch app-a's state.

14 Β· πŸ—οΈ End-to-end composition

A pool feeding this policy's rule action, feeding a virtual server -- the full chain this module participates in.

module "api_pool" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-pool.git?ref=v1.0.0"

  name     = "/Common/api-pool"
  monitors = ["/Common/http_monitor"]

  members = {
    "10.0.1.10:8080" = {
      node             = "10.0.1.10:8080"
      connection_limit = 0
    }
  }
}

module "app_policy" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-policy.git?ref=v1.0.0"

  name = "/Common/app-routing-policy"

  rule = {
    "api-prefix" = {
      condition = [
        {
          http_uri    = true
          starts_with = true
          values      = ["/api"]
        }
      ]
      action = [
        {
          forward = true
          pool    = module.api_pool.name
        }
      ]
    }
  }
}

module "app_virtual_server" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-virtual-server.git?ref=v1.0.0"

  name         = "/Common/app-vs"
  destination  = "10.0.1.100:443"
  policy_names = [module.app_policy.name]
  #...pool/profile/persistence wiring per terraform-bigip-ltm-virtual-server's own README
}

πŸ—οΈ This module's contribution to the chain is deliberately narrow: it owns exactly the policy record and its rules' condition/action pairs, nothing upstream (the pool the rule forwards to) and nothing downstream (the virtual server that attaches it). Ordering matters -- the pool before the policy, the policy before the virtual server -- and Terraform's implicit dependency graph (via module.X.name references) enforces it automatically.


πŸ“₯ Inputs

Summary:

Variable Type Default Required
name string -- βœ…
description string null --
strategy string null --
controls set(string) [] --
requires set(string) [] --
rule map(object(...)) {} --
Full object schema -- var.rule
variable "rule" {
  type = map(object({
    description = optional(string)

    condition = optional(list(object({
      address                 = optional(bool)
      all                     = optional(bool)
      app_service             = optional(string)
      browser_type            = optional(bool)
      browser_version         = optional(bool)
      case_insensitive        = optional(bool)
      case_sensitive          = optional(bool)
      cipher                  = optional(bool)
      cipher_bits             = optional(bool)
      client_accepted         = optional(bool)
      client_ssl              = optional(bool)
      code                    = optional(bool)
      common_name             = optional(bool)
      contains                = optional(bool)
      continent               = optional(bool)
      country_code            = optional(bool)
      country_name            = optional(bool)
      cpu_usage               = optional(bool)
      datagroup               = optional(string)
      device_make             = optional(bool)
      device_model            = optional(bool)
      domain                  = optional(bool)
      ends_with               = optional(bool)
      equals                  = optional(bool)
      exists                  = optional(bool)
      expiry                  = optional(bool)
      extension               = optional(bool)
      external                = optional(bool)
      geoip                   = optional(bool)
      greater                 = optional(bool)
      greater_or_equal        = optional(bool)
      host                    = optional(bool)
      http_basic_auth         = optional(bool)
      http_cookie             = optional(bool)
      http_header             = optional(bool)
      http_host               = optional(bool)
      http_method             = optional(bool)
      http_referer            = optional(bool)
      http_set_cookie         = optional(bool)
      http_status             = optional(bool)
      http_uri                = optional(bool)
      http_user_agent         = optional(bool)
      http_version            = optional(bool)
      index                   = optional(number)
      internal                = optional(bool)
      isp                     = optional(bool)
      last_15secs             = optional(bool)
      last_1min               = optional(bool)
      last_5mins              = optional(bool)
      less                    = optional(bool)
      less_or_equal           = optional(bool)
      local                   = optional(bool)
      major                   = optional(bool)
      matches                 = optional(bool)
      minor                   = optional(bool)
      missing                 = optional(bool)
      mss                     = optional(bool)
      not                     = optional(bool)
      org                     = optional(bool)
      password                = optional(bool)
      path                    = optional(bool)
      path_segment            = optional(bool)
      port                    = optional(bool)
      present                 = optional(bool)
      protocol                = optional(bool)
      query_parameter         = optional(bool)
      query_string            = optional(bool)
      region_code             = optional(bool)
      region_name             = optional(bool)
      remote                  = optional(bool)
      request                 = optional(bool)
      response                = optional(bool)
      route_domain            = optional(bool)
      rtt                     = optional(bool)
      scheme                  = optional(bool)
      server_name             = optional(bool)
      ssl_cert                = optional(bool)
      ssl_client_hello        = optional(bool)
      ssl_extension           = optional(bool)
      ssl_server_handshake    = optional(bool)
      ssl_server_hello        = optional(bool)
      starts_with             = optional(bool)
      tcp                     = optional(bool)
      text                    = optional(bool)
      tm_name                 = optional(string)
      unnamed_query_parameter = optional(bool)
      user_agent_token        = optional(bool)
      username                = optional(bool)
      value                   = optional(bool)
      values                  = optional(list(string))
      version                 = optional(bool)
      vlan                    = optional(bool)
      vlan_id                 = optional(bool)
    })), [])

    action = optional(list(object({
      app_service          = optional(string)
      application          = optional(string)
      asm                  = optional(bool)
      avr                  = optional(bool)
      cache                = optional(bool)
      carp                 = optional(bool)
      category             = optional(string)
      classify             = optional(bool)
      clone_pool           = optional(string)
      code                 = optional(number)
      compress             = optional(bool)
      connection           = optional(bool)
      content              = optional(string)
      cookie_hash          = optional(bool)
      cookie_insert        = optional(bool)
      cookie_passive       = optional(bool)
      cookie_rewrite       = optional(bool)
      decompress           = optional(bool)
      defer                = optional(bool)
      destination_address  = optional(bool)
      disable              = optional(bool)
      domain               = optional(string)
      enable               = optional(bool)
      expiry               = optional(string)
      expiry_secs          = optional(number)
      expression           = optional(string)
      extension            = optional(string)
      facility             = optional(string)
      forward              = optional(bool)
      from_profile         = optional(string)
      hash                 = optional(bool)
      host                 = optional(string)
      http                 = optional(bool)
      http_basic_auth      = optional(bool)
      http_cookie          = optional(bool)
      http_header          = optional(bool)
      http_host            = optional(bool)
      http_referer         = optional(bool)
      http_reply           = optional(bool)
      http_set_cookie      = optional(bool)
      http_uri             = optional(bool)
      ifile                = optional(string)
      insert               = optional(bool)
      internal_virtual     = optional(string)
      ip_address           = optional(string)
      key                  = optional(string)
      l7dos                = optional(bool)
      length               = optional(number)
      location             = optional(string)
      log                  = optional(bool)
      ltm_policy           = optional(bool)
      member               = optional(string)
      message              = optional(string)
      netmask              = optional(string)
      nexthop              = optional(string)
      node                 = optional(string)
      offset               = optional(number)
      path                 = optional(string)
      pem                  = optional(bool)
      persist              = optional(bool)
      pin                  = optional(bool)
      policy               = optional(string)
      pool                 = optional(string)
      port                 = optional(number)
      priority             = optional(string)
      profile              = optional(string)
      protocol             = optional(string)
      query_string         = optional(string)
      rateclass            = optional(string)
      redirect             = optional(bool)
      remove               = optional(bool)
      replace              = optional(bool)
      request              = optional(bool)
      request_adapt        = optional(bool)
      reset                = optional(bool)
      response             = optional(bool)
      response_adapt       = optional(bool)
      scheme               = optional(string)
      script               = optional(string)
      select               = optional(bool)
      server_ssl           = optional(bool)
      set_variable         = optional(bool)
      shutdown             = optional(bool)
      snat                 = optional(string)
      snatpool             = optional(string)
      source_address       = optional(bool)
      ssl_client_hello     = optional(bool)
      ssl_server_handshake = optional(bool)
      ssl_server_hello     = optional(bool)
      ssl_session_id       = optional(bool)
      status               = optional(number)
      tcl                  = optional(bool)
      tcp_nagle            = optional(bool)
      text                 = optional(string)
      timeout              = optional(number)
      tm_name              = optional(string)
      uie                  = optional(bool)
      universal            = optional(bool)
      value                = optional(string)
      virtual              = optional(string)
      vlan                 = optional(string)
      vlan_id              = optional(number)
      wam                  = optional(bool)
      write                = optional(bool)
    })), [])
  }))
  default = {}
}
Field Type Notes
description string Optional per-rule description.
condition list(object(...)) ANDed match criteria (unless a condition's own not/all flags say otherwise); no independent unique key, so it is a list, not a map.
action list(object(...)) Steering behavior applied when the rule matches; same list-not-map reasoning as condition.

Both condition and action objects are direct 1:1 mirrors of the live bigip_ltm_policy provider schema -- consult clouddocs.f5.com and the provider's rendered docs for the specific field combinations valid for a given condition/action "type."


🧾 Outputs

Output Description Sensitive
name Full-path LTM policy name (partition + name) -- primary cross-reference key No
id Provider-internal id of the LTM policy No
rule_names Names of the rules configured on this policy (keys of var.rule) -- diagnostics only, since rule has no separate provider resource to reference No

🧠 Architecture Notes

  • rule is for_each-keyed on the caller's own rule name, never count. main.tf iterates var.rule (a map(object(...))) and renders one dynamic "rule" block per entry, with name = rule.key -- adding or removing one rule never re-indexes another rule's plan entry.
  • No separate provider resource for a rule. Unlike terraform-bigip-ltm-pool's pool-member attachments, rule/condition/action are all nested repeating blocks inside bigip_ltm_policy itself, per SCOPE.md's Design intent -- there is exactly one managed resource in this module.
  • condition and action are ordered lists, not maps. Neither has an independent natural unique key in the live schema (unlike rule, which is keyed on its own name), so both are modeled as lists that mirror the provider's own repeating-block order.
  • try(x, null) guards every optional field -- the policy-level description/strategy, and every one of the ~90 condition fields and ~90 action fields -- so an absent value never renders a spurious empty string or false into the API payload.
  • Pools are consumed, never created, here. rule[*].action[*].pool is a plain string full path from terraform-bigip-ltm-pool; this module does not validate that the referenced pool exists -- this module suite's recommended authoring order (pools before policies) is a human/pipeline concern, not something this module's type system enforces.
  • published_copy is structurally absent, not merely defaulted. It is not declared in variables.tf at all, per SCOPE.md's Provider gotchas -- callers cannot accidentally wire a value into a deprecated, auto-publishing field.
  • Full-path identity is partition-specific. name in the form /Partition/name is the practical cross-reference key terraform-bigip-ltm-virtual-server consumes.

🧱 Design Principles

Concern This module's default Opt-out (caller must type extra)
Partition scope No implicit default is invented -- name must carry the full path (/Partition/name) explicitly Caller always supplies the full path in name
Rule set rule defaults to {} -- a policy with no rules is valid but inert (matches nothing, steers nothing) Caller adds rule entries explicitly to make the policy do anything
Protocol / controls controls and requires both default to an empty set -- no feature activation or protocol requirement is silently assumed Caller explicitly lists the controls/protocols this policy actually needs
Deprecated fields published_copy is not exposed as a module variable at all -- callers cannot depend on a no-op, deprecated field N/A -- structural, not a toggle
Secrets Not applicable -- bigip_ltm_policy carries no secret-shaped attribute N/A

πŸš€ Runbook

cd C:\GitHubCode\newf5modules\bigip\terraform-bigip-ltm-policy
terraform init -backend=false
terraform validate
terraform fmt -check
Remove-Item -Recurse -Force.terraform,.terraform.lock.hcl -ErrorAction SilentlyContinue

Pin consuming module calls to ?ref=v1.0.0 (or the current tagged release) rather than an unpinned branch reference.


πŸ§ͺ Testing

This module's local proof gate is plan-only, schema-level validation:

Command Proves Does NOT prove
terraform init -backend=false Provider requirement resolves, no backend needed for a child module Reachability of any real BIG-IP device
terraform validate Types, required arguments, and the rule/condition/action object shapes are internally consistent That a given condition/action field combination is semantically valid for its "type," or that a referenced pool actually exists by full path
terraform fmt -check Canonical HCL formatting Anything about runtime behavior

Only a real terraform plan/apply against an authenticated bigip provider instance confirms the target partition exists, that a rule's condition/action combination is accepted by the device, and that a referenced pool resolves by full path. That step is a root-module/pipeline concern, never something this authoring process exercises.


πŸ’¬ Example Output

module.app_policy.bigip_ltm_policy.this: Creating...
module.app_policy.bigip_ltm_policy.this: Creation complete after 1s [id=/Common/app-routing-policy]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

id = "/Common/app-routing-policy"
name = "/Common/app-routing-policy"
rule_names = [
 "api-prefix",
]

πŸ” Troubleshooting

Symptom Cause Fix
terraform validate fails with Unsupported argument inside a condition/action block A field name was misspelled or doesn't exist in this module's fixed object schema Check the field name against the Full object schema in πŸ“₯ Inputs, or the live provider docs
BIG-IP rejects the policy at apply with a generic API error A condition/action field combination is syntactically valid Terraform but not semantically valid for its "type" on the target TMOS version This module does not validate combination correctness -- confirm the combination against clouddocs.f5.com
Plan shows a full destroy/recreate of the whole policy after changing requires requires is effectively immutable in practice once rules exist (see SCOPE.md Provider gotchas) Expected -- confirm the change was deliberate; this is not a module bug
Plan shows one rule fully replaced instead of updated That rule's for_each key (its map name) changed Expected -- rule identity is its map key; rename it deliberately and expect a replace, not an in-place rename
Virtual server fails to attach this policy policy_names on the virtual server doesn't match this module's name output exactly, or the two live in different partitions Wire policy_names = [module.app_policy.name] directly rather than retyping the full path
Policy applies cleanly but traffic never reaches the expected pool The pool referenced in rule[*].action[*].pool doesn't yet exist by that exact full path on the device Confirm the sibling terraform-bigip-ltm-pool module applied successfully first, and that partitions match
connection refused on first apply against a cloud-deployed BIG-IP Management interface reachable only on a non-443 port (common on single-NIC AWS/Azure/GCP deployments) Set the provider's port argument (env BIGIP_PORT) at the root module -- not a concern of this module

πŸ”— Related Docs

Releases

Packages

Contributors

Languages