Pre-defining count/for each values on initial run and they would have dependencies on subsequent runs

So I am running into an issue where I need one set of behavior on the initial run and a separate set of behavior on each subsequent run. That is because the subsequent behavior will define count for for each relies on a resource created on first apply and will error.

I need code that would work using GitHub as VCS, both Github Actions and Jenkins as CI/CD and both S3 and HCP as remote state.

Is this even possible? If not what would be the recommended way to go about this considering I’m working on a PoC using HCP + GitHub Actions but may be forced into Jenkins/S3.

This is my current setup that does what i want it to do when running locally.

data "external" "saml_app_id_from_state" {
  program = ["bash", "-c", <<-EOT
    STATE_FILE="${path.module}/terraform.tfstate"

    if [ -f "$STATE_FILE" ]; then
      APP_ID=$(jq -r '.resources[] | select(.type == "okta_app_saml" and .name == "saml_app") | .instances[0].attributes.id // "none"' "$STATE_FILE")
      
      if [ "$APP_ID" = "null" ] || [ -z "$APP_ID" ]; then
        echo '{"id": "none"}'
      else
        echo "{\"id\": \"$APP_ID\"}"
      fi
    else
      echo '{"id": "none"}'
    fi
  EOT
  ]
}

locals {
  saml_app_id = data.external.saml_app_id_from_state.result.id
  base_schema_url =  ["https://${var.environment.org_name}.${var.environment.base_url}/api/v1/meta/schemas/apps/${local.saml_app_id}",
  "https://${var.environment.org_name}.${var.environment.base_url}/api/v1/meta/schemas/apps/${local.saml_app_id}/default"]
}

data "http" "schema" {
  count = local.saml_app_id != "none" ? 2 : 0
  
  url = local.base_schema_url[count.index]
  method = "GET"
  request_headers = {
    Accept = "application/json"
    Authorization = "SSWS ${var.environment.api_token}"
  }
}

locals {
  schema_transformation_status = nonsensitive(try(data.http.schema[0],"Application does not exist" 
    ) != try(data.http.schema[1],"Application does not exist")|| var.base_schema == [{
      index       = "userName"
      master      = "PROFILE_MASTER"
      pattern     = tostring(null)
      permissions = "READ_ONLY"
      required    = true
      title       = "Username"
      type        = "string"
      user_type   = "default"
    }] ? "transformation complete or no transformation required" : "pre-transformation")
 

  base_schema = local.schema_transformation_status == "pre-transformation" ? [{
    index       = "userName"
    master      = "PROFILE_MASTER"
    pattern     = null
    permissions = "READ_ONLY"
    required    = true
    title       = "Username"
    type        = "string"
    user_type   = "default"
  }] : var.base_schema
}

Is there a reason you need to do that complicated bash script in the external data resource? In particular, since it seems like the resource you’re getting an attribute from is defined in some state (either the current one or a remote one), I would think you should be able to either directly reference the attribute (if it’s in the same state), or use one or more outputs from a terraform_remote_state data reference?

While it’s not totally clear what you’re trying to do here from a once-over of that snippet, I think if you have the correct implicit or explicit dependencies in your terraform code, you could possibly be able to do something that’s simpler and more readable.

the challenge is that the data I want to use is a dependency for a count or for-each resource and therefore errors on initial run. I was able to use this behavior. The whole problem and reason for this is SCIM apps in Okta run into inevitable state shift because SCIM can’t be enabled through code except for a select few apps.

locals {
  find_app_url =  "https://${var.environment.org_name}.${var.environment.base_url}/api/v1/apps?includeNonDeleted=false&q=${local.saml_label}"

}

data "http" "saml_app_list" {
  url = local.find_app_url
  method = "GET"
  request_headers = {
    Accept = "application/json"
    Authorization = "SSWS ${var.environment.api_token}"
  }
}

locals {
  saml_app_id = try(jsondecode(data.http.saml_app_list.response_body)[0].id, "none")
  base_schema_url =  "https://${var.environment.org_name}.${var.environment.base_url}/api/v1/meta/schemas/apps/${local.saml_app_id}/default"

}
data "http" "schema" {
  url = local.base_schema_url
  method = "GET"
  request_headers = {
    Accept = "application/json"
    Authorization = "SSWS ${var.environment.api_token}"
  }
}


data "external" "pre-condition" {
  program = ["bash", "-c", <<-EOT
    echo '{"running": "precondition"}'
  EOT
  ]

  lifecycle {
    # Check SAML app list API response
    precondition {
      condition = data.http.saml_app_list.status_code == 200
      error_message = "API request failed with status code: ${data.http.saml_app_list.status_code}. Error: ${data.http.saml_app_list.response_body}"
    }

    # Check SAML app ID
    precondition {
      condition = local.saml_app_id == "none" || local.saml_app_id == try(okta_app_saml.saml_app.id, "n/a")
      error_message = "An application with label '${local.saml_label}' already exists in Okta outside of Terraform. Either modify the label in your configuration or delete/rename the existing application in Okta."
    }

    # Check schema API response
    precondition {
      condition = data.http.schema.status_code == 200 || local.saml_app_id == "none"
      error_message = "Schema API request failed with status code: ${data.http.schema.status_code}. Error: ${data.http.schema.response_body}"
    }
  }
}

locals {
  schema_transformation_status = try(jsondecode(data.http.schema.response_body).definitions.base,"Application does not exist" 
    ) != {
    "id": "#base",
    "type": "object",
    "properties": {
      "userName": {
        "title": "Username",
        "type": "string",
        "required": true,
        "scope": "NONE",
        "maxLength": 100,
        "master": {
          "type": "PROFILE_MASTER"
        }
      }
    },
    "required": [
      "userName"
    ]
  } || var.base_schema == [{
      index       = "userName"
      master      = "PROFILE_MASTER"
      pattern     = tostring(null)
      permissions = "READ_ONLY"
      required    = true
      title       = "Username"
      type        = "string"
      user_type   = "default"
    }] ? "transformation complete or no transformation required" : "pre-transformation"
 

  base_schema = local.schema_transformation_status == "pre-transformation" ? [{
    index       = "userName"
    master      = "PROFILE_MASTER"
    pattern     = null
    permissions = "READ_ONLY"
    required    = true
    title       = "Username"
    type        = "string"
    user_type   = "default"
  }] : var.base_schema
}

resource "okta_app_user_base_schema_property" "properties" {
  count = length(local.base_schema)

  app_id      = okta_app_saml.saml_app.id
  index       = local.base_schema[count.index].index
  title       = local.base_schema[count.index].title
  type        = local.base_schema[count.index].type
  master      = local.base_schema[count.index].master
  pattern     = local.base_schema[count.index].pattern
  permissions = local.base_schema[count.index].permissions
  required    = local.base_schema[count.index].required
  user_type   = local.base_schema[count.index].user_type
}

Wonder if the experimental deferred actions feature in the upcoming 1.12 release would help at all with any of this?