From bf9a85199e0d490105ae7dd9d8da930313f5300d Mon Sep 17 00:00:00 2001 From: Slawomir Koszewski Date: Mon, 26 Jan 2026 21:26:08 +0100 Subject: [PATCH] Add scripts for managing Azure AD applications: setup, get, and delete --- bin/delete-app.js | 15 ++++++ bin/get-app.js | 40 +++++++++++++++ bin/setup-app.js | 121 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100755 bin/delete-app.js create mode 100755 bin/get-app.js create mode 100755 bin/setup-app.js diff --git a/bin/delete-app.js b/bin/delete-app.js new file mode 100755 index 0000000..6342b68 --- /dev/null +++ b/bin/delete-app.js @@ -0,0 +1,15 @@ +#!/usr/bin/env node + +const execSync = require('child_process').execSync; +const { config } = require('../config.js'); + +// Get the Azure AD application ID by name +const appId = execSync(`az ad app list --query "[?displayName=='${config.appName}'].appId" -o tsv`).toString().trim(); + +if (appId) { + // Delete the Azure AD application + execSync(`az ad app delete --id ${appId}`); + console.log(`Deleted App with ID: ${appId}`); +} else { + console.log(`No App found with name: ${APP_NAME}`); +} diff --git a/bin/get-app.js b/bin/get-app.js new file mode 100755 index 0000000..67048c0 --- /dev/null +++ b/bin/get-app.js @@ -0,0 +1,40 @@ +#!/usr/bin/env node + +const execSync = require('child_process').execSync; +const { config } = require('../config.js'); +const { parseArgs } = require('node:util'); + +// Let's parse command line arguments: +// --app-name to override the default app name from config.js +// --help to show usage information +// --env to print in environment variable format + +const args = parseArgs({ + options: { + 'app-name': { type: 'string', short: 'a' }, + 'help': { type: 'boolean', short: 'h' }, + 'env': { type: 'boolean', short: 'e' } + } +}); + +// Get the Azure AD application ID by name +const queryResult = execSync(`az ad app list --query "[?displayName=='${config.appName}']"`).toString().trim(); +const apps = JSON.parse(queryResult); + +if (apps.length === 1) { + const appId = apps[0].appId; + if (args.values.env) { + const tenantId = execSync(`az account show --query "tenantId" -o tsv`).toString().trim(); + console.log(`AZ_APP_NAME="${config.appName}"`); + console.log(`ARM_CLIENT_ID=${appId}`); + console.log(`ARM_TENANT_ID=${tenantId}`); + } else { + console.log(`Found App ID: ${appId} for App Name: "${config.appName}"`); + } +} else if (apps.length > 1) { + console.error(`Multiple apps found with the name: "${config.appName}". Please ensure app names are unique.`); + process.exit(1); +} else { + console.error(`No App found with name: "${config.appName}"`); + process.exit(1); +} diff --git a/bin/setup-app.js b/bin/setup-app.js new file mode 100755 index 0000000..66bf9c1 --- /dev/null +++ b/bin/setup-app.js @@ -0,0 +1,121 @@ +#!/usr/bin/env node + +const execSync = require("child_process").execSync; +const { writeFileSync } = require("fs"); +const { write } = require("node:fs"); +const { parseArgs } = require("node:util"); + +const args = parseArgs({ + options: { + "app-name": { type: "string", short: "a" }, + help: { type: "boolean", short: "h" }, + }, +}); + +const config = {}; + +if (args.values["app-name"]) { + config.appName = args.values["app-name"]; +} else { + config.appName = "Azure Node Playground"; +} + +// Get the tenant ID +try { + config.tenantId = execSync(`az account show --query "tenantId" -o tsv`) + .toString() + .trim(); +} catch (error) { + console.error("Failed to get Azure tenant ID."); + process.exit(1); +} + +if (config.tenantId) { + console.log(`Using Tenant ID: ${config.tenantId}`); +} else { + console.error("Failed to get Azure tenant ID."); + process.exit(1); +} + +try { + const appIdList = JSON.parse( + execSync( + `az ad app list --filter "displayName eq '${config.appName}'" -o json`, + ) + .toString() + .trim(), + ); + if (appIdList.length !== 1) { + throw new Error("App not found or multiple apps with the same name."); + } + config.appId = appIdList[0].appId; +} catch (error) { + console.error("Failed to query Azure AD applications."); + process.exit(1); +} + +if (config.appId) { + console.log(`App already exists with ID: ${config.appId}`); +} else { + // Create the Azure AD application, capturing the JSON output, and extracting the .appId + try { + config.appId = execSync( + `az ad app create --display-name "${config.appName}" --query "appId" -o tsv`, + ) + .toString() + .trim(); + } catch (error) { + console.error("Failed to create Azure AD application."); + process.exit(1); + } + console.log(`Created App with ID: ${config.appId}`); +} + +// Chech if service principal exists +let spObjId; + +try { + const spIdList = JSON.parse( + execSync(`az ad sp list --filter "appId eq '${config.appId}'" -o json`) + .toString() + .trim(), + ); + if (spIdList.length === 1) { + spObjId = spIdList[0].objectId; + } else { + spObjId = null; + } +} catch (error) { + console.error("Failed to query service principals."); + process.exit(1); +} + +if (spObjId) { + console.log("Using existing service principal."); +} else { + // Now create the service principal for the app + try { + spObjId = execSync(`az ad sp create --id ${config.appId}`); + console.log("Service principal created."); + } catch (error) { + console.log("Failed to create service principal."); + } +} + +// Write the APP_ID to the .env file +const envContent = `AZ_APP_NAME="${config.appName}" +ARM_CLIENT_ID=${config.appId} +ARM_TENANT_ID=${config.tenantId} +`; + +writeFileSync(".env", envContent); +console.log(".env file created with AZ_APP_NAME, ARM_CLIENT_ID, and ARM_TENANT_ID."); + +// Save the config to the 'config.js' file. +writeFileSync( + "config.js", + `export const config = ${JSON.stringify(config, null, 4)};\n`, +); +console.log("config.js file created."); + +console.log("Setup complete.");