scripts.v2/utils.js (166 lines of code) (raw):
const fs = require("fs");
const path = require("path");
const https = require("https");
const { BlobServiceClient } = require("@azure/storage-blob");
const blobStorageContainer = "content";
const mime = require("mime");
const apiVersion = '2021-08-01';
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
function listFilesInDirectory(dir) {
const results = [];
fs.readdirSync(dir).forEach((file) => {
file = dir + "/" + file;
const stat = fs.statSync(file);
if (stat && stat.isDirectory()) {
results.push(...listFilesInDirectory(file));
} else {
results.push(file);
}
});
return results;
}
/**
* Attempts to get a developer portal storage connection string in two ways:
* 1) if the connection string is explicitly set by the user, use it.
* 2) retrieving the connection string from the management API using the instance endpoint and SAS token
* @param {string} managementApiEndpoint the management endpoint of service instance
* @param {string} managementApiAccessToken the SAS token
*/
async function getStorageSasTokenOrThrow(managementApiEndpoint, managementApiAccessToken) {
if (managementApiAccessToken) {
// token should always be available, because we call
// getTokenOrThrow before this
return await getStorageSasToken(managementApiEndpoint, managementApiAccessToken);
}
throw Error('Storage connection could not be retrieved');
}
/**
* Gets a storage connection string from the management API for the specified APIM instance and
* SAS token.
* @param {string} managementApiEndpoint the management endpoint of service instance
* @param {string} managementApiAccessToken the SAS token
*/
async function getStorageSasToken(managementApiEndpoint, managementApiAccessToken) {
const response = await request("POST", `https://${managementApiEndpoint}/subscriptions/00000/resourceGroups/00000/providers/Microsoft.ApiManagement/service/00000/portalSettings/mediaContent/listSecrets?api-version=${apiVersion}`, managementApiAccessToken);
return response.containerSasUrl;
}
/**
* A wrapper for making a request and returning its response body.
* @param {Object} options https options
*/
async function request(method, url, accessToken, body) {
let requestBody;
const headers = {
"If-Match": "*",
"Content-Type": "application/json",
"Authorization": accessToken
};
if (body) {
if (!body.properties) {
body = {
properties: body
}
}
requestBody = JSON.stringify(body);
headers["Content-Length"] = Buffer.byteLength(requestBody);
}
const options = {
port: 443,
method: method,
headers: headers
};
return new Promise((resolve, reject) => {
const req = https.request(url, options, (resp) => {
let chunks = [];
resp.on('data', (chunk) => {
chunks.push(chunk);
});
resp.on('end', () => {
let data = Buffer.concat(chunks).toString('utf8');
switch (resp.statusCode) {
case 200:
case 201:
data.startsWith("{") ? resolve(JSON.parse(data)) : resolve(data);
break;
case 404:
reject({ code: "NotFound", message: `Resource not found: ${url}` });
break;
case 401:
reject({ code: "Unauthorized", message: `Unauthorized. Make sure you correctly specified management API access token before running the script.` });
break;
case 403:
reject({ code: "Forbidden", message: `Looks like you are not allowed to perform this operation. Please check with your administrator.` });
break;
default:
reject({ code: "UnhandledError", message: `Could not complete request to ${url}. Status: ${resp.statusCode} ${resp.statusMessage}` });
}
});
});
req.on('error', (e) => {
reject(e);
});
if (requestBody) {
req.write(requestBody);
}
req.end();
});
}
async function downloadBlobs(blobStorageUrl, snapshotMediaFolder) {
try {
const blobServiceClient = new BlobServiceClient(blobStorageUrl.replace(`/${blobStorageContainer}`, ""));
const containerClient = blobServiceClient.getContainerClient(blobStorageContainer);
await fs.promises.mkdir(path.resolve(snapshotMediaFolder), { recursive: true });
await downloadBlobsRecursive(containerClient, snapshotMediaFolder)
}
catch (error) {
throw new Error(`Unable to download media files. ${error.message}`);
}
}
async function downloadBlobsRecursive(containerClient, outputFolder, prefix = undefined) {
let blobs = containerClient.listBlobsByHierarchy("/", prefix ? { prefix: prefix } : undefined);
for await (const blob of blobs) {
if (blob.kind === "prefix") {
await downloadBlobsRecursive(containerClient, outputFolder, blob.name);
continue;
}
const blockBlobClient = containerClient.getBlockBlobClient(blob.name);
const extension = mime.getExtension(blob.properties.contentType);
let pathToFile;
if (extension != null) {
pathToFile = `${outputFolder}/${blob.name}.${extension}`;
}
else {
pathToFile = `${outputFolder}/${blob.name}`;
}
const folderPath = pathToFile.substring(0, pathToFile.lastIndexOf("/"));
await fs.promises.mkdir(path.resolve(folderPath), { recursive: true });
await blockBlobClient.downloadToFile(pathToFile);
}
}
async function uploadBlobs(blobStorageUrl, localMediaFolder) {
if (!fs.existsSync(localMediaFolder)) {
console.info("No media files found in the snapshot folder. Skipping media upload...");
return;
}
try {
const blobServiceClient = new BlobServiceClient(blobStorageUrl.replace(`/${blobStorageContainer}`, ""));
const containerClient = blobServiceClient.getContainerClient(blobStorageContainer);
const fileNames = listFilesInDirectory(localMediaFolder);
for (const fileName of fileNames) {
const blobKey = fileName.replace(localMediaFolder + "/", "").split(".")[0];
const contentType = mime.getType(fileName) || "application/octet-stream";
const blockBlobClient = containerClient.getBlockBlobClient(blobKey);
await blockBlobClient.uploadFile(fileName, {
blobHTTPHeaders: {
blobContentType: contentType
}
});
}
}
catch (error) {
throw new Error(`Unable to upload media files. ${error.message}`);
}
}
async function deleteBlobs(blobStorageUrl) {
try {
const blobServiceClient = new BlobServiceClient(blobStorageUrl.replace(`/${blobStorageContainer}`, ""));
const containerClient = blobServiceClient.getContainerClient(blobStorageContainer);
let blobs = containerClient.listBlobsFlat();
for await (const blob of blobs) {
const blockBlobClient = containerClient.getBlockBlobClient(blob.name);
await blockBlobClient.delete();
}
}
catch (error) {
throw new Error(`Unable to delete media files. ${error.message}`);
}
}
module.exports = {
apiVersion,
request,
downloadBlobs,
uploadBlobs,
deleteBlobs,
getStorageSasTokenOrThrow
};