Search code examples
terraformterraform-provider-awsaws-backup

Terraform template file, malformed policy document (Backup policy)


I call a file to be rendered like so

resource "aws_organizations_policy" "backup_policy" {
  name        = "organization_backup_policy"
  description = "Organization wide backup policy"
  type        = "BACKUP_POLICY"
  content     = jsonencode(templatefile("${path.module}/policies/backup-policy.json.tftpl", {
    vault_name                = aws_backup_vault.central_backup_vault.name
    backup_operator_role_name = aws_iam_role.backup_operator.name
    backup_tag_key            = "Backup"
    backup_tag_value          = "true"
  }))
}

The template itself:

{
  "plans": {
    "BackupPlan00": {
      "regions": {
        "@@assign": [
          "eu-central-1"
        ]
      },
      "rules": {
        "BackupRule00": {
          "target_backup_vault_name": {
            "@@assign": ${vault_name}"
          }
        }
      },
      "backup_plan_tags": {
        "backup-plan-tag": {
          "tag_key": {
            "@@assign": "backup-plan-tag"
          },
          "tag_value": {
            "@@assign": "backup-plan-key"
          }
        }
      },
      "selections": {
        "tags": {
          "ResourceAssignment00": {
            "iam_role_arn": {
              "@@assign": "arn:aws:iam::$account:role/${backup_operator_role_name}"
            },
            "tag_key": {
              "@@assign": "${backup_tag_key}"
            },
            "tag_value": {
              "@@assign": [
                "${backup_tag_value}"
              ]
            }
          }
        }
      }
    }
  }
}

EDIT

Funny enough, when I have a plan JSON file such as backup.json

{
  "plans": {
    "BackupPlan00": {
      "regions": {
        "@@append": [
          "eu-central-1",
          "eu-west-3"
        ]
      },
      "rules": {
        "BackupRule00": {
          "target_backup_vault_name": {
            "@@assign": "CentralVault"
          }
        }
      },
      "backup_plan_tags": {
        "backup-plan-tag": {
          "tag_key": {
            "@@assign": "backup-plan-tag"
          },
          "tag_value": {
            "@@assign": "backup-plan-key"
          }
        }
      },
      "selections": {
        "tags": {
          "ResourceAssignment00": {
            "iam_role_arn": {
              "@@assign": "arn:aws:iam::$account:role/BackupOperator"
            },
            "tag_key": {
              "@@assign": "Backup"
            },
            "tag_value": {
              "@@assign": [
                "true"
              ]
            }
          }
        }
      }
    }
  }
}

With no variable interpolation or anythings, it still fails

resource "aws_organizations_policy" "backup_policy" {
  provider    = aws.primary_region
  name        = "organization_backup_policy"
  description = "Organization wide backup policy"
  type        = "BACKUP_POLICY"
  content     = jsonencode(templatefile("${path.module}/policies/backup.json", {}))
}
│ Error: updating Organizations Policy (p-89ql027feq): operation error Organizations: UpdatePolicy, https response error StatusCode: 400, RequestID: 2ed05a2d-b829-46fc-a18c-78105ae710a0, MalformedPolicyDocumentException: The provided policy document does not meet the requirements of the specified policy type.

What do I miss?


Solution

  • Using jsonencode with the result of templatefile is typically a mistake, because the result of templatefile is always a string and so applying jsonencode to that would produce a JSON-encoded version of that string.

    If your goal is to generate JSON from the template then the operations need to happen in the opposite order: the jsonencode call must happen inside the template, so that the template's rendered result is the desired JSON document.

    For example, you might write your template like this:

    ${jsonencode({
      plans = {
        "BackupPlan00" = {
          regions = {
            "@@assign" = [
              "eu-central-1",
            ]
          }
          rules = {
            "BackupRule00" = {
              target_backup_vault_name = {
                "@@assign" = vault_name
              }
            }
          }
          backup_plan_tags = {
            "backup-plan-tag" = {
              tag_key = {
                "@@assign" = "backup-plan-tag"
              }
              tag_value = {
                "@@assign" = "backup-plan-key"
              }
            }
          }
          selections = {
            tags = {
              "ResourceAssignment00" = {
                iam_role_arn = {
                  "@@assign" = "arn:aws:iam::$account:role/${backup_operator_role_name}"
                }
                tag_key = {
                  "@@assign" = backup_tag_key
                }
                tag_value = {
                  "@@assign" = [
                    backup_tag_value,
                  ]
                }
              }
            }
          }
        }
      }
    })}
    

    The entire body of the template is now just a call to the jsonencode function, so the template's rendered result will be a valid JSON document.

    You can assign the template result directly to the argument that expects JSON, without any further encoding:

      content = templatefile("${path.module}/policies/backup-policy.json.tftpl", {
        vault_name                = aws_backup_vault.central_backup_vault.name
        backup_operator_role_name = aws_iam_role.backup_operator.name
        backup_tag_key            = "Backup"
        backup_tag_value          = "true"
      })