Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Build and deploy AI applications on Azure AI Foundry using Microsoft's model catalog and AI services
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