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
}