Full Script to Protect DOB using Policy Management REST APIs

Full script for protecting DOB data.

The following code snippet contains the contents of the deploy-dob-policy.sh shell script. This script enables the creation and deployment of a policy to protect DOB data using the Policy Management REST APIs.

#!/usr/bin/env bash
###############################################################################
# Script Name  : dob_policy.sh
# Description  : End-to-end automation script for creating and deploying a
#                Date of Birth (DOB) protection policy using the
#                Protegrity Policy Information Management (PIM) REST API.
#
#
# ─────────────────────────────────────────────────────────────────────────────
# IMPORTANT NOTES
# ─────────────────────────────────────────────────────────────────────────────
#
# 1. WORKBENCH REQUIREMENT:
#    The Policy Management REST APIs will work only after you have installed
#    the Protegrity Workbench. Attempting to use these APIs before the
#    Workbench is installed will result in errors.
#
# 2. USER PERMISSIONS:
#    The user account used to authenticate against these APIs must have the
#    appropriate Protegrity role assigned:
#      - Security Officer : Required for write access (create, update, delete)
#      - Security Viewer  : Required for read-only access (get, list)
#    For more information about the roles and permissions required, refer to
#    the section "Managing Roles" in the Protegrity documentation.
#
# 3. API VERSION:
#    The Policy Management API uses version v2.
#    All endpoints in this script are prefixed with /pty/v2/pim/
#    Requests to older API versions will not be supported.
#
# ─────────────────────────────────────────────────────────────────────────────
# PREREQUISITES
# ─────────────────────────────────────────────────────────────────────────────
#   - Protegrity Workbench must be installed and running
#   - kubectl configured and connected to your Kubernetes cluster
#   - curl installed on the machine running this script
#   - Access to the Protegrity API Gateway
#   - A user account with Security Officer permissions
#
# ─────────────────────────────────────────────────────────────────────────────
# USAGE
# ─────────────────────────────────────────────────────────────────────────────
#   chmod +x deploy-dob-policy.sh
#   ./deploy-dob-policy.sh
#
# ─────────────────────────────────────────────────────────────────────────────
# WORKFLOW
# ─────────────────────────────────────────────────────────────────────────────
#   Step 1    - Initialize Policy Management
#   Step 2    - Prepare Data Element (DOB DateTime Token)
#   Step 3    - Create Member Source
#   Step 3.1  - Test Member Source Connectivity
#   Step 4    - Create Role
#   Step 5    - Assign Member Source to Role
#   Step 5.1  - Sync Role Membership
#   Step 6    - Create Policy Shell
#   Step 7    - Define Policy Rule (bind Role + Data Element + Permissions)
#   Step 8    - Create Datastore
#   Step 9    - Deploy Policy to Datastore
#   Step 10   - Confirm Deployment
#
# ─────────────────────────────────────────────────────────────────────────────
# SECURITY NOTES
# ─────────────────────────────────────────────────────────────────────────────
#   - If any API call returns HTTP 401 (Unauthorized), the script will
#     automatically attempt to re-generate the JWT token and retry the
#     request once before failing.
#   - If any API call indicates that a resource already exists, the script
#     will exit immediately with an error. Delete the conflicting resource
#     first, or update the name variables in SECTION 1 before re-running.
#
# ─────────────────────────────────────────────────────────────────────────────
# EXIT CODES
# ─────────────────────────────────────────────────────────────────────────────
#   0 - Success
#   1 - Script error (set -e will trigger on any failed command)
###############################################################################

set -euo pipefail

###############################################################################
# SECTION 1: USER-CONFIGURABLE VARIABLES
# ─────────────────────────────────────
# Modify the variables below to match your environment before running
# this script. All other values are derived automatically.
#
# NOTE: The user specified by ADMIN_USER must have the Security Officer
#       permission to perform write operations via the Policy Management API.
#       For read-only operations, the Security Viewer permission is sufficient.
#       For more information, refer to the "Managing Roles" section in the
#       Protegrity documentation.
###############################################################################

# --- Protegrity Admin Credentials ---
# WARNING: For production use, consider sourcing these values from a secrets
#          manager (e.g., HashiCorp Vault, Kubernetes Secrets, AWS SSM).
ADMIN_USER="workbench"
ADMIN_PASS="Admin123!"

# --- Data Element ---
DE_NAME="de_dob_token"
DE_DESC="Tokenize Date of Birth"
DE_TOKENIZER="SLT_8_DATETIME"     # DateTime tokenizer for date/time fields

# --- Role ---
ROLE_NAME="dob_protect_role"
ROLE_DESC="Role having access to protect DOB"
ROLE_MODE="MANUAL"                # Options: MANUAL | SEMIAUTOMATIC | AUTOMATIC

# --- Member Source ---
SOURCE_NAME="test-file"
SOURCE_USER_FILE="exampleusers.txt"
SOURCE_GROUP_FILE="examplegroups.txt"

# --- Role Member ---
MEMBER_NAME="exampleuser1"
MEMBER_TYPE="USER"                # Options: USER | GROUP

# --- Policy ---
POLICY_NAME="dob-policy"
POLICY_DESC="Protect Date of Birth with tokenization"

# --- Policy Rule Permissions ---
RULE_PROTECT=true                 # Allow protect operation
RULE_REPROTECT=false              # Allow re-protect operation
RULE_UNPROTECT=true               # Allow unprotect operation
RULE_NO_ACCESS_OP="NULL_VALUE"    # Behavior for no-access: NULL_VALUE | EXCEPTION

# --- Datastore ---
DS_NAME="ds_protect_dob"
DS_DESC="Datastore to demonstrate DOB protection"
DS_DEFAULT=true                   # Set as the default datastore: true | false

# --- Token Retry Settings ---
# On receiving HTTP 401 Unauthorized, the script will refresh the JWT token
# and retry the failed request. MAX_TOKEN_RETRIES controls how many refresh
# attempts are made before the script aborts.
MAX_TOKEN_RETRIES=1               # Number of times to retry generating a token on 401

###############################################################################
# SECTION 2: HELPER FUNCTIONS
# ────────────────────────────
# Internal utility functions used throughout the script.
# Do not modify unless necessary.
###############################################################################

# Prints a formatted primary section header to stdout
log() {
  printf "\n%s\n" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  printf "  %s\n" "$*"
  printf "%s\n" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
}

# Prints a formatted subsection header to stdout (indented, lighter style)
log_sub() {
  printf "\n%s\n" "  ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄"
  printf "    %s\n" "$*"
  printf "%s\n" "  ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄"
}

# Prints an error message to stderr and exits with code 1
# Usage: die <message>
die() {
  printf "\n  [ERROR] %s\n" "$*" >&2
  exit 1
}

# Attempts to extract a UID from a JSON API response.
# Handles both string UIDs ("uid":"1") and integer UIDs ("uid":1).
# Exits with an error if extraction fails — never prompts interactively.
# Usage: extract_uid <json_response> <resource_label>
extract_uid() {
  local response="$1"
  local label="$2"
  local uid
  # Match string-quoted UIDs: "uid":"<value>"
  uid=$(echo "$response" | grep -o '"uid":"[^"]*"' | head -1 | sed 's/"uid":"//;s/"//' || true)
  # Fallback: match integer UIDs: "uid":<number>
  if [[ -z "${uid:-}" ]]; then
    uid=$(echo "$response" | grep -o '"uid":[0-9]*' | head -1 | grep -o '[0-9]*' || true)
  fi
  if [[ -z "${uid:-}" ]]; then
    die "Failed to extract UID for '${label}'. API response was: ${response}"
  fi
  echo "$uid"
}

# Generates a new JWT authentication token using the configured admin
# credentials (ADMIN_USER / ADMIN_PASS). Stores the result in the global
# TOKEN variable.
#
# NOTE: The user must have the Security Officer permission for write access
#       or the Security Viewer permission for read-only access to the
#       Policy Management API (v2). For more information, refer to
#       the "Managing Roles" section in the Protegrity documentation.
#
# Usage: generate_token
generate_token() {
  echo "  Generating JWT authentication token..."
  TOKEN=$(curl -k -s "https://${GW_HOST}/api/v1/auth/login/token" \
    -X POST \
    -H 'Content-Type: application/x-www-form-urlencoded' \
    -d "loginname=${ADMIN_USER}" \
    -d "password=${ADMIN_PASS}" \
    -D - -o /dev/null 2>&1 \
    | grep -i 'pty_access_jwt_token:' \
    | sed 's/pty_access_jwt_token: //' \
    | tr -d '\r')

  if [[ -z "${TOKEN:-}" ]]; then
    die "Failed to retrieve JWT token. Please verify the following:
         - The Protegrity Workbench is installed and running.
         - The API Gateway host (${GW_HOST}) is reachable.
         - The credentials for user '${ADMIN_USER}' are correct.
         - The user '${ADMIN_USER}' has the Security Officer or Security Viewer
           permission assigned. Refer to 'Managing Roles' in the Protegrity
           documentation for more information."
  fi

  echo "  Token acquired successfully."
}

# Executes a curl API call and automatically retries with a refreshed JWT
# token if a 401 Unauthorized response is received.
#
# All Policy Management API calls in this script target the v2 API version:
#   https://<gateway>/pty/v2/pim/...
#
# On HTTP 401, the token is refreshed (up to MAX_TOKEN_RETRIES times) and
# the request is retried. This can occur when a token expires mid-run.
# On any other non-2xx response, a warning is logged but execution continues.
#
# Usage: api_call <curl_args...>
api_call() {
  local retries=0
  local http_status
  local response_body
  local tmp_file
  tmp_file=$(mktemp)

  while true; do
    # Execute the curl call, capturing body and HTTP status separately
    http_status=$(curl -k -s -o "$tmp_file" -w "%{http_code}" \
      -H "Authorization: Bearer ${TOKEN}" \
      "$@")
    response_body=$(cat "$tmp_file")

    # Handle 401 Unauthorized:
    # This typically means the JWT token has expired or is invalid.
    # The script will attempt to refresh the token and retry the request.
    # Ensure the user has the correct permissions (Security Officer / Viewer).
    # Refer to "Managing Roles" in the Protegrity documentation.
    if [[ "$http_status" == "401" ]]; then
      if [[ "$retries" -lt "$MAX_TOKEN_RETRIES" ]]; then
        echo "  [Warning] Received HTTP 401 Unauthorized." >&2
        echo "            Refreshing JWT token and retrying (attempt $((retries + 1)) of ${MAX_TOKEN_RETRIES})..." >&2
        generate_token
        retries=$((retries + 1))
        continue
      else
        rm -f "$tmp_file"
        die "Received HTTP 401 Unauthorized after ${MAX_TOKEN_RETRIES} token refresh attempt(s).
         Please verify that user '${ADMIN_USER}' has the required permissions:
           - Security Officer : for write access
           - Security Viewer  : for read-only access
         Refer to 'Managing Roles' in the Protegrity documentation."
      fi
    fi

    # Fail on "already exists" (HTTP 400/409) — resource must be removed first
    if echo "$response_body" | grep -qi "already exist"; then
      rm -f "$tmp_file"
      die "Resource already exists (HTTP ${http_status}). The script cannot continue.
         Response : ${response_body}
         Action   : Delete or rename the existing resource before re-running,
                    or update the name variables at the top of this script."
    fi

    # Log other non-2xx responses (excluding 401 already handled above)
    if [[ "$http_status" != 2* ]]; then
      echo "  [Warning] Received HTTP ${http_status}. Response: ${response_body}" >&2
    fi

    rm -f "$tmp_file"
    echo "$response_body"
    break
  done
}

###############################################################################
# SECTION 3: ENVIRONMENT SETUP
# ─────────────────────────────
# Retrieves the API Gateway host address and generates a JWT authentication
# token required for all subsequent API calls.
#
# NOTE: The Policy Management REST APIs will work only after the Protegrity
#       Workbench has been installed. All API calls target version v2:
#         https://<gateway>/pty/v2/pim/
###############################################################################

log "Environment Setup: Retrieving API Gateway Host"
export GW_HOST
GW_HOST="$(kubectl get gateway pty-main -n api-gateway \
  -o jsonpath='{.status.addresses[0].value}')"
echo "  API Gateway Host : ${GW_HOST}"
echo "  API Version      : v2  (/pty/v2/pim/)"

log "Environment Setup: Generating JWT Authentication Token"
generate_token

###############################################################################
# SECTION 4: WORKFLOW EXECUTION
# ──────────────────────────────
# Executes each step of the DOB policy creation workflow in sequence.
# UIDs returned by each step are captured and reused in subsequent steps.
#
# NOTE: All write operations (POST) require the Security Officer permission.
#       The read operation in Step 12 (GET) requires at minimum the
#       Security Viewer permission.
###############################################################################

# ─────────────────────────────────────────────────────────────────────────────
# STEP 1: Initialize Policy Management
# ─────────────────────────────────────────────────────────────────────────────
# Initializes the PIM system. This step only needs to be performed once
# per environment setup.
#
# Requirement : Protegrity Workbench must be installed before running this step.
# Permission  : Security Officer
# API Version : v2  — POST /pty/v2/pim/init
# ─────────────────────────────────────────────────────────────────────────────
log "Step 1: Initialize Policy Management"
INIT_RESPONSE=$(api_call \
  -H "accept: application/json" \
  -X POST "https://${GW_HOST}/pty/v2/pim/init")
if [[ -z "${INIT_RESPONSE}" ]]; then
  echo "  Status: OK (empty response — PIM already initialized or no content returned)"
else
  echo "  Response: ${INIT_RESPONSE}"
fi
echo ""

# ─────────────────────────────────────────────────────────────────────────────
# STEP 2: Prepare Data Element
# ─────────────────────────────────────────────────────────────────────────────
# Prepares the DOB DateTime Data Element that defines what data is protected
# and how it is tokenized. The SLT_8_DATETIME tokenizer handles date/time
# field tokenization.
#
# Permission  : Security Officer
# API Version : v2  — POST /pty/v2/pim/dataelements
# ─────────────────────────────────────────────────────────────────────────────
log "Step 2: Prepare Data Element — ${DE_NAME}"
DE_RESPONSE=$(api_call \
  -H "accept: application/json" \
  -H "Content-Type: application/json" \
  -X POST "https://${GW_HOST}/pty/v2/pim/dataelements" \
  -d '{
    "name": "'"${DE_NAME}"'",
    "description": "'"${DE_DESC}"'",
    "dateTimeToken": {
      "tokenizer": "'"${DE_TOKENIZER}"'"
    }
  }')
echo "  Response: ${DE_RESPONSE}"
DE_UID=$(extract_uid "$DE_RESPONSE" "$DE_NAME")
echo "  Data Element UID: ${DE_UID}"

# ─────────────────────────────────────────────────────────────────────────────
# STEP 3: Create Member Source
# ─────────────────────────────────────────────────────────────────────────────
# Creates a Member Source that defines where user and group identities are
# sourced from (in this example, a flat file). Member Sources are used to
# populate roles with real enterprise identities.
#
# Permission  : Security Officer
# API Version : v2  — POST /pty/v2/pim/sources
# ─────────────────────────────────────────────────────────────────────────────
log "Step 3: Create Member Source — ${SOURCE_NAME}"
SOURCE_RESPONSE=$(api_call \
  -H "accept: application/json" \
  -H "Content-Type: application/json" \
  -X POST "https://${GW_HOST}/pty/v2/pim/sources" \
  -d '{
    "name": "'"${SOURCE_NAME}"'",
    "type": "FILE",
    "connection": {
      "userFile": "'"${SOURCE_USER_FILE}"'",
      "groupFile": "'"${SOURCE_GROUP_FILE}"'"
    }
  }')
echo "  Response: ${SOURCE_RESPONSE}"
SOURCE_UID=$(extract_uid "$SOURCE_RESPONSE" "$SOURCE_NAME")
echo "  Source UID: ${SOURCE_UID}"

# ─────────────────────────────────────────────────────────────────────────────
# STEP 3.1: Test Member Source Connectivity
# ─────────────────────────────────────────────────────────────────────────────
# Validates that the Member Source is reachable and correctly configured.
# All connectivity checks (connection, authentication, groups, users) must
# pass before proceeding.
#
# Permission  : Security Officer
# API Version : v2  — POST /pty/v2/pim/sources/{id}/test
# ─────────────────────────────────────────────────────────────────────────────
log "Step 3.1: Test Member Source Connectivity — UID: ${SOURCE_UID}"
api_call \
  -H "accept: application/json" \
  -H "Content-Type: application/json" \
  -X POST "https://${GW_HOST}/pty/v2/pim/sources/${SOURCE_UID}/test"
echo ""

# ─────────────────────────────────────────────────────────────────────────────
# STEP 4: Create Role
# ─────────────────────────────────────────────────────────────────────────────
# Creates a role that represents who is allowed to perform operations on
# the protected data. Permissions are granted to roles, which are then
# mapped to users and groups via Member Sources.
#
# Permission  : Security Officer
# API Version : v2  — POST /pty/v2/pim/roles
# ─────────────────────────────────────────────────────────────────────────────
log "Step 4: Create Role — ${ROLE_NAME}"
ROLE_RESPONSE=$(api_call \
  -H "accept: application/json" \
  -H "Content-Type: application/json" \
  -X POST "https://${GW_HOST}/pty/v2/pim/roles" \
  -d '{
    "name": "'"${ROLE_NAME}"'",
    "description": "'"${ROLE_DESC}"'",
    "mode": "'"${ROLE_MODE}"'",
    "allowAll": false
  }')
echo "  Response: ${ROLE_RESPONSE}"
ROLE_UID=$(extract_uid "$ROLE_RESPONSE" "$ROLE_NAME")
echo "  Role UID: ${ROLE_UID}"

# ─────────────────────────────────────────────────────────────────────────────
# STEP 5: Assign Member Source to Role
# ─────────────────────────────────────────────────────────────────────────────
# Binds a specific user or group from the Member Source to the Role.
# This establishes the identity-to-role mapping that makes the policy
# enforceable for real users.
#
# Permission  : Security Officer
# API Version : v2  — POST /pty/v2/pim/roles/{id}/members
# ─────────────────────────────────────────────────────────────────────────────
log "Step 5: Assign Member '${MEMBER_NAME}' to Role — UID: ${ROLE_UID}"
api_call \
  -H "accept: application/json" \
  -H "Content-Type: application/json" \
  -X POST "https://${GW_HOST}/pty/v2/pim/roles/${ROLE_UID}/members" \
  -d '[
    {
      "name": "'"${MEMBER_NAME}"'",
      "source": "'"${SOURCE_UID}"'",
      "type": "'"${MEMBER_TYPE}"'"
    }
  ]'
echo ""

# ─────────────────────────────────────────────────────────────────────────────
# STEP 5.1: Sync Role Membership
# ─────────────────────────────────────────────────────────────────────────────
# Synchronizes the role membership from the Member Source. This pulls the
# current list of users and groups into the role so that access controls
# reflect the latest state of the identity source.
#
# Permission  : Security Officer
# API Version : v2  — POST /pty/v2/pim/roles/{id}/sync
# ─────────────────────────────────────────────────────────────────────────────
log "Step 5.1: Sync Role Membership — Role UID: ${ROLE_UID}"
SYNC_RESPONSE=$(api_call \
  -H "accept: application/json" \
  -H "Content-Type: application/json" \
  -X POST "https://${GW_HOST}/pty/v2/pim/roles/${ROLE_UID}/sync")
if [[ -z "${SYNC_RESPONSE}" ]]; then
  echo "  Status: OK"
else
  echo "  Response: ${SYNC_RESPONSE}"
fi
echo ""

# ─────────────────────────────────────────────────────────────────────────────
# STEP 6: Create Policy Shell
# ─────────────────────────────────────────────────────────────────────────────
# Creates the policy container that will hold the access rules. The policy
# is the deployable object that ties together Data Elements, Roles, and Rules.
#
# Permission  : Security Officer
# API Version : v2  — POST /pty/v2/pim/policies
# ─────────────────────────────────────────────────────────────────────────────
log "Step 6: Create Policy Shell — ${POLICY_NAME}"
POLICY_RESPONSE=$(api_call \
  -H "accept: application/json" \
  -H "Content-Type: application/json" \
  -X POST "https://${GW_HOST}/pty/v2/pim/policies" \
  -d '{
    "name": "'"${POLICY_NAME}"'",
    "description": "'"${POLICY_DESC}"'",
    "template": {
      "access": {
        "protect": false,
        "reProtect": false,
        "unProtect": false
      }
    }
  }')
echo "  Response: ${POLICY_RESPONSE}"
POLICY_UID=$(extract_uid "$POLICY_RESPONSE" "$POLICY_NAME")
echo "  Policy UID: ${POLICY_UID}"

# ─────────────────────────────────────────────────────────────────────────────
# STEP 7: Define Policy Rule
# ─────────────────────────────────────────────────────────────────────────────
# Creates the rule that binds a Role (who), a Data Element (what), and
# the permitted operations (protect / reProtect / unProtect) into the
# policy. Without rules, the policy exists but grants no access.
# Note: No mask is applied for DateTime data elements.
#
# Permission  : Security Officer
# API Version : v2  — POST /pty/v2/pim/policies/{id}/rules
# ─────────────────────────────────────────────────────────────────────────────
log "Step 7: Define Policy Rule — Policy UID: ${POLICY_UID}"
RULE_RESPONSE=$(api_call \
  -H "accept: application/json" \
  -H "Content-Type: application/json" \
  -X POST "https://${GW_HOST}/pty/v2/pim/policies/${POLICY_UID}/rules" \
  -d '{
    "role": "'"${ROLE_UID}"'",
    "dataElement": "'"${DE_UID}"'",
    "noAccessOperation": "'"${RULE_NO_ACCESS_OP}"'",
    "permission": {
      "access": {
        "protect": '"${RULE_PROTECT}"',
        "reProtect": '"${RULE_REPROTECT}"',
        "unProtect": '"${RULE_UNPROTECT}"'
      }
    }
  }')
if [[ -z "${RULE_RESPONSE}" ]]; then
  echo "  Status: OK"
else
  echo "  Response: ${RULE_RESPONSE}"
fi
echo ""

# ─────────────────────────────────────────────────────────────────────────────
# STEP 8: Create Datastore
# ─────────────────────────────────────────────────────────────────────────────
# Creates the datastore target to which the policy will be deployed.
# A policy is not active for protectors until it has been deployed to
# at least one datastore.
#
# Permission  : Security Officer
# API Version : v2  — POST /pty/v2/pim/datastores
# ─────────────────────────────────────────────────────────────────────────────
log "Step 8: Create Datastore — ${DS_NAME}"
DS_RESPONSE=$(api_call \
  -H "accept: application/json" \
  -H "Content-Type: application/json" \
  -X POST "https://${GW_HOST}/pty/v2/pim/datastores" \
  -d '{
    "name": "'"${DS_NAME}"'",
    "description": "'"${DS_DESC}"'",
    "default": '"${DS_DEFAULT}"'
  }')
echo "  Response: ${DS_RESPONSE}"
DS_UID=$(extract_uid "$DS_RESPONSE" "$DS_NAME")
echo "  Datastore UID: ${DS_UID}"

# ─────────────────────────────────────────────────────────────────────────────
# STEP 9: Deploy Policy to Datastore
# ─────────────────────────────────────────────────────────────────────────────
# Deploys the policy to the target datastore. After this step, runtime
# protectors that reference this datastore will be able to load and
# enforce the policy.
#
# Permission  : Security Officer
# API Version : v2  — POST /pty/v2/pim/datastores/{id}/deploy
# ─────────────────────────────────────────────────────────────────────────────
log "Step 9: Deploy Policy to Datastore — DS UID: ${DS_UID}, Policy UID: ${POLICY_UID}"
DEPLOY_RESPONSE=$(api_call \
  -H "accept: application/json" \
  -H "Content-Type: application/json" \
  -X POST "https://${GW_HOST}/pty/v2/pim/datastores/${DS_UID}/deploy" \
  -d '{
    "policies": ["'"${POLICY_UID}"'"],
    "applications": []
  }')
if [[ -z "${DEPLOY_RESPONSE}" ]]; then
  echo "  Status: OK"
else
  echo "  Response: ${DEPLOY_RESPONSE}"
fi
echo ""

# ─────────────────────────────────────────────────────────────────────────────
# STEP 10: Confirm Deployment
# ─────────────────────────────────────────────────────────────────────────────
# Verifies that the policy has been successfully deployed to the datastore.
# Confirms the policy is active, correctly mapped, and enforceable.
#
# Permission  : Security Viewer (read-only) or Security Officer
# API Version : v2  — GET /pty/v2/pim/deploy
# ─────────────────────────────────────────────────────────────────────────────
log "Step 10: Confirm Deployment"
api_call \
  -H "accept: application/json" \
  -X GET "https://${GW_HOST}/pty/v2/pim/deploy"
echo ""

###############################################################################
# SECTION 5: SUMMARY
# ───────────────────
# Displays a summary of all created resources and their UIDs.
###############################################################################
log "Workflow Complete ✅"
printf "\n%-20s %-30s %-10s\n" "Resource"             "Name"                          "UID"
printf "%-20s %-30s %-10s\n"   "────────────────────" "──────────────────────────────" "──────────"
printf "%-20s %-30s %-10s\n"   "Data Element"  "${DE_NAME}"      "${DE_UID}"
printf "%-20s %-30s %-10s\n"   "Role"          "${ROLE_NAME}"    "${ROLE_UID}"
printf "%-20s %-30s %-10s\n"   "Member Source" "${SOURCE_NAME}"  "${SOURCE_UID}"
printf "%-20s %-30s %-10s\n"   "Policy"        "${POLICY_NAME}"  "${POLICY_UID}"
printf "%-20s %-30s %-10s\n"   "Datastore"     "${DS_NAME}"      "${DS_UID}"
printf "\n"

Last modified : April 06, 2026