Add REST command support with method and URL options
All checks were successful
build / build (push) Successful in 12s
All checks were successful
build / build (push) Successful in 12s
This commit is contained in:
@@ -18,6 +18,9 @@ import { readJsonFromStdin } from "./utils.ts";
|
||||
type CommandValues = {
|
||||
[key: string]: string | boolean | undefined;
|
||||
type?: string;
|
||||
method?: string;
|
||||
url?: string;
|
||||
header?: string;
|
||||
resources?: string;
|
||||
"use-device-code"?: boolean;
|
||||
"no-browser"?: boolean;
|
||||
@@ -39,6 +42,66 @@ type DisplayNameRow = {
|
||||
displayName?: string | null;
|
||||
};
|
||||
|
||||
function parseHeaderLine(header?: string): { name: string; value: string } | null {
|
||||
if (!header || header.trim() === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const separatorIndex = header.indexOf(":");
|
||||
if (separatorIndex < 1) {
|
||||
throw new Error("--header must be in the format 'Name: Value'");
|
||||
}
|
||||
|
||||
const name = header.slice(0, separatorIndex).trim();
|
||||
const value = header.slice(separatorIndex + 1).trim();
|
||||
if (!name || !value) {
|
||||
throw new Error("--header must be in the format 'Name: Value'");
|
||||
}
|
||||
|
||||
return { name, value };
|
||||
}
|
||||
|
||||
function hasAuthorizationHeader(headers: Headers): boolean {
|
||||
for (const headerName of headers.keys()) {
|
||||
if (headerName.toLowerCase() === "authorization") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async function getAutoAuthorizationHeader(url: URL): Promise<string | null> {
|
||||
const host = url.hostname.toLowerCase();
|
||||
if (host !== "management.azure.com" && host !== "dev.azure.com") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const config = await loadPublicConfig();
|
||||
if (!config.tenantId) {
|
||||
throw new Error("tenantId is required");
|
||||
}
|
||||
if (!config.clientId) {
|
||||
throw new Error("clientId is required");
|
||||
}
|
||||
|
||||
if (host === "management.azure.com") {
|
||||
const result = await acquireResourceTokenFromLogin({
|
||||
tenantId: config.tenantId,
|
||||
clientId: config.clientId,
|
||||
resource: "arm",
|
||||
});
|
||||
const accessToken = result?.accessToken;
|
||||
if (!accessToken) {
|
||||
throw new Error("Failed to obtain AzureRM token");
|
||||
}
|
||||
return `Bearer ${accessToken}`;
|
||||
}
|
||||
|
||||
const accessToken = await getDevOpsApiToken(config.tenantId, config.clientId);
|
||||
return `Bearer ${accessToken}`;
|
||||
}
|
||||
|
||||
function filterByPermissionName<T extends PermissionRow>(rows: T[], pattern: string): T[] {
|
||||
return rows.filter((item) =>
|
||||
minimatch(item.permissionValue ?? "", pattern, { nocase: true })
|
||||
@@ -187,6 +250,55 @@ async function runGetTokenCommand(values: CommandValues): Promise<unknown> {
|
||||
throw new Error(`Invalid --type '${values.type}'. Allowed: azurerm, devops`);
|
||||
}
|
||||
|
||||
async function runRestCommand(values: CommandValues): Promise<unknown> {
|
||||
const method = (values.method ?? "GET").toString().trim().toUpperCase() || "GET";
|
||||
const urlValue = (values.url ?? "").toString().trim();
|
||||
|
||||
if (!urlValue) {
|
||||
throw new Error("--url is required for rest");
|
||||
}
|
||||
|
||||
let targetUrl: URL;
|
||||
try {
|
||||
targetUrl = new URL(urlValue);
|
||||
} catch {
|
||||
throw new Error(`Invalid --url '${urlValue}'`);
|
||||
}
|
||||
|
||||
const headers = new Headers();
|
||||
const customHeader = parseHeaderLine(values.header);
|
||||
if (customHeader) {
|
||||
headers.set(customHeader.name, customHeader.value);
|
||||
}
|
||||
|
||||
if (!hasAuthorizationHeader(headers)) {
|
||||
const authorization = await getAutoAuthorizationHeader(targetUrl);
|
||||
if (authorization) {
|
||||
headers.set("Authorization", authorization);
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(targetUrl, {
|
||||
method,
|
||||
headers,
|
||||
});
|
||||
|
||||
const contentType = response.headers.get("content-type") ?? "";
|
||||
let body: unknown;
|
||||
if (contentType.toLowerCase().includes("application/json")) {
|
||||
body = await response.json();
|
||||
} else {
|
||||
body = await response.text();
|
||||
}
|
||||
|
||||
return {
|
||||
ok: response.ok,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body,
|
||||
};
|
||||
}
|
||||
|
||||
export async function runCommand(command: string, values: CommandValues): Promise<unknown> {
|
||||
switch (command) {
|
||||
case "login":
|
||||
@@ -205,6 +317,8 @@ export async function runCommand(command: string, values: CommandValues): Promis
|
||||
return runListResourcePermissionsCommand(values);
|
||||
case "get-token":
|
||||
return runGetTokenCommand(values);
|
||||
case "rest":
|
||||
return runRestCommand(values);
|
||||
default:
|
||||
throw new Error(`Unknown command: ${command}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user