UNPKG

@rushstack/rush-azure-storage-build-cache-plugin

Version:

Rush plugin for Azure storage cloud build cache

176 lines 10.3 kB
"use strict"; // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. Object.defineProperty(exports, "__esModule", { value: true }); exports.AzureStorageBuildCacheProvider = void 0; const rush_sdk_1 = require("@rushstack/rush-sdk"); const storage_blob_1 = require("@azure/storage-blob"); const identity_1 = require("@azure/identity"); const AzureStorageAuthentication_1 = require("./AzureStorageAuthentication"); class AzureStorageBuildCacheProvider extends AzureStorageAuthentication_1.AzureStorageAuthentication { get isCacheWriteAllowed() { var _a; return (_a = rush_sdk_1.EnvironmentConfiguration.buildCacheWriteAllowed) !== null && _a !== void 0 ? _a : this._isCacheWriteAllowedByConfiguration; } constructor(options) { super({ credentialUpdateCommandForLogging: `rush ${rush_sdk_1.RushConstants.updateCloudCredentialsCommandName}`, ...options }); this._blobPrefix = options.blobPrefix; this._environmentCredential = rush_sdk_1.EnvironmentConfiguration.buildCacheCredential; this._readRequiresAuthentication = !!options.readRequiresAuthentication; if (!(this._azureEnvironment in identity_1.AzureAuthorityHosts)) { throw new Error(`The specified Azure Environment ("${this._azureEnvironment}") is invalid. If it is specified, it must ` + `be one of: ${Object.keys(identity_1.AzureAuthorityHosts).join(', ')}`); } } async tryGetCacheEntryBufferByIdAsync(terminal, cacheId) { var _a, _b, _c, _d, _e, _f, _g, _h, _j; const blobClient = await this._getBlobClientForCacheIdAsync(cacheId, terminal); try { const blobExists = await blobClient.exists(); if (blobExists) { return await blobClient.downloadToBuffer(); } else { return undefined; } } catch (err) { const e = err; const errorMessage = 'Error getting cache entry from Azure Storage: ' + [e.name, e.message, (_a = e.response) === null || _a === void 0 ? void 0 : _a.status, (_c = (_b = e.response) === null || _b === void 0 ? void 0 : _b.parsedHeaders) === null || _c === void 0 ? void 0 : _c.errorCode] .filter((piece) => piece) .join(' '); if (((_e = (_d = e.response) === null || _d === void 0 ? void 0 : _d.parsedHeaders) === null || _e === void 0 ? void 0 : _e.errorCode) === 'PublicAccessNotPermitted') { // This error means we tried to read the cache with no credentials, but credentials are required. // We'll assume that the configuration of the cache is correct and the user has to take action. terminal.writeWarningLine(`${errorMessage}\n\n` + `You need to configure Azure Storage SAS credentials to access the build cache.\n` + `Update the credentials by running "rush ${rush_sdk_1.RushConstants.updateCloudCredentialsCommandName}", \n` + `or provide a SAS in the ` + `${rush_sdk_1.EnvironmentVariableNames.RUSH_BUILD_CACHE_CREDENTIAL} environment variable.`); } else if (((_g = (_f = e.response) === null || _f === void 0 ? void 0 : _f.parsedHeaders) === null || _g === void 0 ? void 0 : _g.errorCode) === 'AuthenticationFailed') { // This error means the user's credentials are incorrect, but not expired normally. They might have // gotten corrupted somehow, or revoked manually in Azure Portal. terminal.writeWarningLine(`${errorMessage}\n\n` + `Your Azure Storage SAS credentials are not valid.\n` + `Update the credentials by running "rush ${rush_sdk_1.RushConstants.updateCloudCredentialsCommandName}", \n` + `or provide a SAS in the ` + `${rush_sdk_1.EnvironmentVariableNames.RUSH_BUILD_CACHE_CREDENTIAL} environment variable.`); } else if (((_j = (_h = e.response) === null || _h === void 0 ? void 0 : _h.parsedHeaders) === null || _j === void 0 ? void 0 : _j.errorCode) === 'AuthorizationPermissionMismatch') { // This error is not solvable by the user, so we'll assume it is a configuration error, and revert // to providing likely next steps on configuration. (Hopefully this error is rare for a regular // developer, more likely this error will appear while someone is configuring the cache for the // first time.) terminal.writeWarningLine(`${errorMessage}\n\n` + `Your Azure Storage SAS credentials are valid, but do not have permission to read the build cache.\n` + `Make sure you have added the role 'Storage Blob Data Reader' to the appropriate user(s) or group(s)\n` + `on your storage account in the Azure Portal.`); } else { // We don't know what went wrong, hopefully we'll print something useful. terminal.writeWarningLine(errorMessage); } return undefined; } } async trySetCacheEntryBufferAsync(terminal, cacheId, entryStream) { var _a, _b, _c; if (!this.isCacheWriteAllowed) { terminal.writeErrorLine('Writing to Azure Blob Storage cache is not allowed in the current configuration.'); return false; } const blobClient = await this._getBlobClientForCacheIdAsync(cacheId, terminal); const blockBlobClient = blobClient.getBlockBlobClient(); let blobAlreadyExists = false; try { blobAlreadyExists = await blockBlobClient.exists(); } catch (err) { const e = err; // If RUSH_BUILD_CACHE_CREDENTIAL is set but is corrupted or has been rotated // in Azure Portal, or the user's own cached credentials have been corrupted or // invalidated, we'll print the error and continue (this way we don't fail the // actual rush build). const errorMessage = 'Error checking if cache entry exists in Azure Storage: ' + [e.name, e.message, (_a = e.response) === null || _a === void 0 ? void 0 : _a.status, (_c = (_b = e.response) === null || _b === void 0 ? void 0 : _b.parsedHeaders) === null || _c === void 0 ? void 0 : _c.errorCode] .filter((piece) => piece) .join(' '); terminal.writeWarningLine(errorMessage); } if (blobAlreadyExists) { terminal.writeVerboseLine('Build cache entry blob already exists.'); return true; } else { try { await blockBlobClient.upload(entryStream, entryStream.length); return true; } catch (e) { if (e.statusCode === 409 /* conflict */) { // If something else has written to the blob at the same time, // it's probably a concurrent process that is attempting to write // the same cache entry. That is an effective success. terminal.writeVerboseLine('Azure Storage returned status 409 (conflict). The cache entry has ' + `probably already been set by another builder. Code: "${e.code}".`); return true; } else { terminal.writeWarningLine(`Error uploading cache entry to Azure Storage: ${e}`); return false; } } } } async _getBlobClientForCacheIdAsync(cacheId, terminal) { const client = await this._getContainerClientAsync(terminal); const blobName = this._blobPrefix ? `${this._blobPrefix}/${cacheId}` : cacheId; return client.getBlobClient(blobName); } async _getContainerClientAsync(terminal) { if (!this._containerClient) { let sasString = this._environmentCredential; if (!sasString) { const credentialEntry = await this.tryGetCachedCredentialAsync({ expiredCredentialBehavior: 'logWarning', terminal }); sasString = credentialEntry === null || credentialEntry === void 0 ? void 0 : credentialEntry.credential; } let blobServiceClient; if (sasString) { const connectionString = this._getConnectionString(sasString); blobServiceClient = storage_blob_1.BlobServiceClient.fromConnectionString(connectionString); } else if (!this._readRequiresAuthentication && !this._isCacheWriteAllowedByConfiguration) { // If we don't have a credential and read doesn't require authentication, we can still read from the cache. blobServiceClient = new storage_blob_1.BlobServiceClient(this._storageAccountUrl); } else { throw new Error("An Azure Storage SAS credential hasn't been provided, or has expired. " + `Update the credentials by running "rush ${rush_sdk_1.RushConstants.updateCloudCredentialsCommandName}", ` + `or provide a SAS in the ` + `${rush_sdk_1.EnvironmentVariableNames.RUSH_BUILD_CACHE_CREDENTIAL} environment variable`); } this._containerClient = blobServiceClient.getContainerClient(this._storageContainerName); } return this._containerClient; } _getConnectionString(sasString) { const blobEndpoint = `BlobEndpoint=${this._storageAccountUrl}`; if (sasString) { const connectionString = `${blobEndpoint};SharedAccessSignature=${sasString}`; return connectionString; } else { return blobEndpoint; } } } exports.AzureStorageBuildCacheProvider = AzureStorageBuildCacheProvider; //# sourceMappingURL=AzureStorageBuildCacheProvider.js.map