@dataroadinc/setup-auth
Version:
CLI tool and programmatic API for automated OAuth setup across cloud platforms
125 lines (116 loc) • 3.86 kB
text/typescript
import { SetupAuthError } from "../../utils/error.js"
import axios from "axios"
import { gcpGetAuthorizationHeader } from "./auth.js"
/**
* Make HTTP requests to GCP APIs
*/
export async function gcpCallAPI(
url: string,
method: string,
data?: unknown
): Promise<unknown> {
try {
const response = await axios({
url,
method,
data,
headers: {
Authorization: await gcpGetAuthorizationHeader(),
"Content-Type": "application/json",
},
})
return response.data
} catch (error) {
if (axios.isAxiosError(error) && error.response) {
// Clean up error message to avoid showing HTML in 404 responses
const statusCode = error.response.status
const statusText = error.response.statusText
let errorDetail = ""
// For 404 errors, just show a simple message without the HTML body
if (statusCode === 404) {
errorDetail = `Resource not found at ${url}`
} else if (error.response.data) {
// Handle different error response formats
if (typeof error.response.data === "string") {
if (error.response.data.includes("<!DOCTYPE html>")) {
errorDetail = "HTML error page returned"
} else {
errorDetail = error.response.data
}
} else if (typeof error.response.data === "object") {
// Extract useful error information from GCP API response
const errorData = error.response.data
if (errorData.error) {
if (typeof errorData.error === "string") {
errorDetail = errorData.error
} else {
errorDetail = JSON.stringify(
{
message: errorData.error.message,
status: errorData.error.status,
code: errorData.error.code,
details: errorData.error.details,
},
null,
2
)
}
} else {
errorDetail = JSON.stringify(errorData, null, 2)
}
}
}
// Check if it's the ALREADY_EXISTS error before logging details
const responseData = error.response?.data as
| { error?: { status?: string } }
| undefined
const isAlreadyExists =
statusCode === 409 && responseData?.error?.status === "ALREADY_EXISTS"
if (!isAlreadyExists) {
console.error(
`GCP API Error Details:\nURL: ${url}\nMethod: ${method}\nStatus: ${statusCode} ${statusText}\nError: ${errorDetail}\n${data ? `Request Data: ${JSON.stringify(data, null, 2)}` : ""}`
)
}
// Always throw the error so the caller can handle it
throw new SetupAuthError(
`GCP API error: ${statusCode} ${statusText} - ${errorDetail}`,
{ cause: error }
)
}
throw error
}
}
/**
* Retry a function with exponential backoff
*/
export async function retryWithBackoff<T>(
fn: () => Promise<T>,
maxRetries: number = 3,
initialDelay: number = 1000,
operation: string = "operation"
): Promise<T> {
let retries = 0
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let lastError: any
while (retries <= maxRetries) {
try {
return await fn()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
lastError = error
if (!error?.message?.includes("concurrent policy changes")) {
throw error
}
if (retries === maxRetries) {
break
}
retries++
const delay = initialDelay * Math.pow(2, retries - 1)
console.log(
`Retrying ${operation} after ${delay}ms due to concurrent policy change (attempt ${retries}/${maxRetries})...`
)
await new Promise(resolve => setTimeout(resolve, delay))
}
}
throw lastError
}