Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Deploy, evaluate, and manage AI agents end-to-end on Microsoft Azure AI Foundry
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
foundry-agent/create/scripts/resolve-project-id.sh
1#!/usr/bin/env bash2# resolve-project-id.sh3# Resolves a Foundry project ARM resource ID from a Foundry project endpoint.4# The endpoint is used only for Azure lookup keys; the printed ID is the `id`5# returned by Azure CLI, not a locally constructed ARM resource ID.6#7# Usage:8# ./resolve-project-id.sh --endpoint "https://my-account.services.ai.azure.com/api/projects/my-project"9# ./resolve-project-id.sh --endpoint "https://my-account.services.ai.azure.com/api/projects/my-project" --output json1011set -uo pipefail1213ENDPOINT=""14SUBSCRIPTION=""15RESOURCE_GROUP=""16ACCOUNT_NAME=""17PROJECT_NAME=""18OUTPUT="id"19TEMP_FILES=()2021cleanup() {22if [ "${#TEMP_FILES[@]}" -gt 0 ]; then23rm -f "${TEMP_FILES[@]}"24fi25}26trap cleanup EXIT2728usage() {29cat <<'EOF'30Usage: resolve-project-id.sh --endpoint <foundry-project-endpoint> [options]3132Options:33-e, --endpoint <url> Foundry project endpoint. Required.34--subscription <id> Azure subscription ID or name.35-g, --resource-group <name> Resource group for the Foundry account.36-n, --account-name <name> Foundry account name.37--project-name <name> Foundry project name.38-o, --output <id|json> Output format. Default: id.39-h, --help Show this help.40EOF41}4243fatal() {44echo "[ERROR] $1" >&245exit 146}4748while [ "$#" -gt 0 ]; do49case "$1" in50-e|--endpoint)51[ "$#" -ge 2 ] || fatal "$1 requires a value."52ENDPOINT="$2"53shift 254;;55--subscription)56[ "$#" -ge 2 ] || fatal "$1 requires a value."57SUBSCRIPTION="$2"58shift 259;;60-g|--resource-group)61[ "$#" -ge 2 ] || fatal "$1 requires a value."62RESOURCE_GROUP="$2"63shift 264;;65-n|--account-name)66[ "$#" -ge 2 ] || fatal "$1 requires a value."67ACCOUNT_NAME="$2"68shift 269;;70--project-name)71[ "$#" -ge 2 ] || fatal "$1 requires a value."72PROJECT_NAME="$2"73shift 274;;75-o|--output)76[ "$#" -ge 2 ] || fatal "$1 requires a value."77OUTPUT="$2"78shift 279;;80-h|--help)81usage82exit 083;;84*)85fatal "Unknown argument: $1"86;;87esac88done8990[ -n "$ENDPOINT" ] || fatal "--endpoint is required."91[ "$OUTPUT" = "id" ] || [ "$OUTPUT" = "json" ] || fatal "--output must be 'id' or 'json'."9293command -v az >/dev/null 2>&1 || fatal "Azure CLI 'az' was not found on PATH."94command -v python3 >/dev/null 2>&1 || fatal "python3 was not found on PATH."9596PARSED_ENDPOINT="$(97python3 - "$ENDPOINT" "$ACCOUNT_NAME" "$PROJECT_NAME" <<'PY'98import json99import sys100from urllib.parse import unquote, urlparse101102endpoint = (sys.argv[1] or "").strip().rstrip("/")103account_name = sys.argv[2] or ""104project_name = sys.argv[3] or ""105106parsed = urlparse(endpoint)107if parsed.scheme not in ("http", "https") or not parsed.netloc:108print("Endpoint must be an http or https URI.", file=sys.stderr)109raise SystemExit(1)110111if not project_name:112parts = [unquote(p) for p in parsed.path.strip("/").split("/") if p]113for index, part in enumerate(parts):114if part.lower() == "projects" and index + 1 < len(parts):115project_name = parts[index + 1]116break117118if not account_name:119host = parsed.hostname or ""120suffix = ".services.ai.azure.com"121if host.lower().endswith(suffix):122account_name = host[:-len(suffix)]123124print(json.dumps({125"endpoint": endpoint,126"accountName": account_name,127"projectName": project_name,128}))129PY130)" || fatal "Could not parse Foundry project endpoint."131132NORMALIZED_ENDPOINT="$(python3 -c 'import json,sys; print(json.loads(sys.stdin.read())["endpoint"])' <<<"$PARSED_ENDPOINT")"133if [ -z "$ACCOUNT_NAME" ]; then134ACCOUNT_NAME="$(python3 -c 'import json,sys; print(json.loads(sys.stdin.read())["accountName"])' <<<"$PARSED_ENDPOINT")"135fi136if [ -z "$PROJECT_NAME" ]; then137PROJECT_NAME="$(python3 -c 'import json,sys; print(json.loads(sys.stdin.read())["projectName"])' <<<"$PARSED_ENDPOINT")"138fi139140[ -n "$ACCOUNT_NAME" ] || fatal "Could not read the account name from the endpoint host. Re-run with --account-name."141[ -n "$PROJECT_NAME" ] || fatal "Could not read the project name from the endpoint path. Re-run with --project-name."142143add_subscription_arg() {144if [ -n "$SUBSCRIPTION" ]; then145printf '%s\n' "--subscription" "$SUBSCRIPTION"146fi147}148149run_az_json() {150local stderr_file151stderr_file="$(mktemp)"152local output153if output="$(az "$@" 2>"$stderr_file")"; then154rm -f "$stderr_file"155printf '%s' "$output"156return 0157fi158local error_text159error_text="$(cat "$stderr_file")"160rm -f "$stderr_file"161echo "$error_text" >&2162return 1163}164165if [ -z "$RESOURCE_GROUP" ]; then166AZ_ARGS=(cognitiveservices account list -o json)167while IFS= read -r arg; do168[ -n "$arg" ] && AZ_ARGS+=("$arg")169done < <(add_subscription_arg)170171ACCOUNTS_JSON="$(run_az_json "${AZ_ARGS[@]}")" || fatal "Failed to list Cognitive Services accounts."172ACCOUNTS_FILE="$(mktemp)"173TEMP_FILES+=("$ACCOUNTS_FILE")174printf '%s' "$ACCOUNTS_JSON" >"$ACCOUNTS_FILE"175MATCHED_ACCOUNT="$(176ACCOUNT_NAME="$ACCOUNT_NAME" python3 - "$ACCOUNTS_FILE" <<'PY'177import json178import os179import sys180181target = os.environ["ACCOUNT_NAME"].lower()182with open(sys.argv[1], encoding="utf-8") as handle:183accounts = json.load(handle)184matches = []185for account in accounts:186name = (account.get("name") or "")187custom = ((account.get("properties") or {}).get("customSubDomainName") or "")188if name.lower() == target or custom.lower() == target:189matches.append(account)190191if not matches:192print(f"Could not find a Cognitive Services account matching '{os.environ['ACCOUNT_NAME']}'.", file=sys.stderr)193raise SystemExit(1)194if len(matches) > 1:195choices = ", ".join(f"{m.get('resourceGroup')}/{m.get('name')}" for m in matches)196print(f"Multiple accounts matched '{os.environ['ACCOUNT_NAME']}': {choices}. Re-run with --resource-group.", file=sys.stderr)197raise SystemExit(1)198199print(json.dumps({200"resourceGroup": matches[0].get("resourceGroup") or "",201"accountName": matches[0].get("name") or "",202}))203PY204)" || fatal "Failed to resolve the Foundry account resource group."205206RESOURCE_GROUP="$(python3 -c 'import json,sys; print(json.loads(sys.stdin.read())["resourceGroup"])' <<<"$MATCHED_ACCOUNT")"207ACCOUNT_NAME="$(python3 -c 'import json,sys; print(json.loads(sys.stdin.read())["accountName"])' <<<"$MATCHED_ACCOUNT")"208fi209210PROJECT_JSON=""211AZ_SHOW_ARGS=(212cognitiveservices account project show213-g "$RESOURCE_GROUP"214-n "$ACCOUNT_NAME"215--project-name "$PROJECT_NAME"216-o json217)218while IFS= read -r arg; do219[ -n "$arg" ] && AZ_SHOW_ARGS+=("$arg")220done < <(add_subscription_arg)221222if ! PROJECT_JSON="$(run_az_json "${AZ_SHOW_ARGS[@]}")"; then223AZ_LIST_ARGS=(224cognitiveservices account project list225-g "$RESOURCE_GROUP"226-n "$ACCOUNT_NAME"227-o json228)229while IFS= read -r arg; do230[ -n "$arg" ] && AZ_LIST_ARGS+=("$arg")231done < <(add_subscription_arg)232233PROJECTS_JSON="$(run_az_json "${AZ_LIST_ARGS[@]}")" || fatal "Failed to list Foundry projects."234PROJECTS_FILE="$(mktemp)"235TEMP_FILES+=("$PROJECTS_FILE")236printf '%s' "$PROJECTS_JSON" >"$PROJECTS_FILE"237PROJECT_JSON="$(238NORMALIZED_ENDPOINT="$NORMALIZED_ENDPOINT" python3 - "$PROJECTS_FILE" <<'PY'239import json240import os241import sys242243expected = os.environ["NORMALIZED_ENDPOINT"].rstrip("/")244with open(sys.argv[1], encoding="utf-8") as handle:245projects = json.load(handle)246247def endpoints(project):248values = ((project.get("properties") or {}).get("endpoints") or {}).values()249return [value.rstrip("/") for value in values if isinstance(value, str) and value]250251for project in projects:252if expected in endpoints(project):253print(json.dumps(project))254break255else:256print(f"Could not find a Foundry project matching endpoint '{expected}'.", file=sys.stderr)257raise SystemExit(1)258PY259)" || fatal "Failed to resolve the Foundry project from endpoint metadata."260fi261262PROJECT_JSON="$PROJECT_JSON" \263NORMALIZED_ENDPOINT="$NORMALIZED_ENDPOINT" \264RESOURCE_GROUP="$RESOURCE_GROUP" \265ACCOUNT_NAME="$ACCOUNT_NAME" \266PROJECT_NAME="$PROJECT_NAME" \267OUTPUT="$OUTPUT" \268python3 - <<'PY'269import json270import os271272project = json.loads(os.environ["PROJECT_JSON"])273expected = os.environ["NORMALIZED_ENDPOINT"].rstrip("/")274endpoint_values = ((project.get("properties") or {}).get("endpoints") or {}).values()275endpoints = [value.rstrip("/") for value in endpoint_values if isinstance(value, str) and value]276277if endpoints and expected not in endpoints:278print(f"[ERROR] Resolved project endpoint metadata did not match '{expected}'.", file=__import__("sys").stderr)279raise SystemExit(1)280281resource_id = project.get("id")282if not resource_id:283print("[ERROR] Azure returned a project object without an id.", file=__import__("sys").stderr)284raise SystemExit(1)285286if os.environ["OUTPUT"] == "json":287print(json.dumps({288"id": resource_id,289"endpoint": endpoints[0] if endpoints else expected,290"resourceGroup": os.environ["RESOURCE_GROUP"],291"accountName": os.environ["ACCOUNT_NAME"],292"projectName": os.environ["PROJECT_NAME"],293}, indent=2))294else:295print(resource_id)296PY297