UNPKG

azurite

Version:

An open source Azure Storage API compatible server

598 lines 26.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const stream_1 = require("stream"); const utils_1 = require("../../common/utils/utils"); const BlobStorageContext_1 = tslib_1.__importDefault(require("../context/BlobStorageContext")); const NotImplementedError_1 = tslib_1.__importDefault(require("../errors/NotImplementedError")); const Models = tslib_1.__importStar(require("../generated/artifacts/models")); const constants_1 = require("../utils/constants"); const constants_2 = require("../utils/constants"); const utils_2 = require("../utils/utils"); const BaseHandler_1 = tslib_1.__importDefault(require("./BaseHandler")); const BlobBatchHandler_1 = require("./BlobBatchHandler"); /** * ContainerHandler handles Azure Storage Blob container related requests. * * @export * @class ContainerHandler * @implements {IHandler} */ class ContainerHandler extends BaseHandler_1.default { constructor(accountDataStore, oauth, metadataStore, extentStore, logger, loose, disableProductStyle) { super(metadataStore, extentStore, logger, loose); this.accountDataStore = accountDataStore; this.oauth = oauth; this.disableProductStyle = disableProductStyle; } /** * Create container. * * @param {Models.ContainerCreateOptionalParams} options * @param {Context} context * @returns {Promise<Models.ContainerCreateResponse>} * @memberof ContainerHandler */ async create(options, context) { const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; const lastModified = blobCtx.startTime; const etag = (0, utils_1.newEtag)(); // Preserve metadata key case const metadata = (0, utils_1.convertRawHeadersToMetadata)(blobCtx.request.getRawHeaders(), context.contextId); await this.metadataStore.createContainer(context, { accountName, name: containerName, metadata, properties: { etag, lastModified, leaseStatus: Models.LeaseStatusType.Unlocked, leaseState: Models.LeaseStateType.Available, publicAccess: options.access, hasImmutabilityPolicy: false, hasLegalHold: false } }); const response = { statusCode: 201, requestId: blobCtx.contextId, clientRequestId: options.requestId, eTag: etag, lastModified, version: constants_1.BLOB_API_VERSION }; return response; } /** * get container properties * * @param {Models.ContainerGetPropertiesOptionalParams} options * @param {Context} context * @returns {Promise<Models.ContainerGetPropertiesResponse>} * @memberof ContainerHandler */ async getProperties(options, context) { const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; const containerProperties = await this.metadataStore.getContainerProperties(context, accountName, containerName, options.leaseAccessConditions); const response = { statusCode: 200, requestId: context.contextId, clientRequestId: options.requestId, eTag: containerProperties.properties.etag, ...containerProperties.properties, blobPublicAccess: containerProperties.properties.publicAccess, metadata: containerProperties.metadata, version: constants_1.BLOB_API_VERSION }; return response; } /** * get container properties with headers * * @param {Models.ContainerGetPropertiesOptionalParams} options * @param {Context} context * @returns {Promise<Models.ContainerGetPropertiesResponse>} * @memberof ContainerHandler */ async getPropertiesWithHead(options, context) { return this.getProperties(options, context); } /** * Delete container. * * @param {Models.ContainerDeleteMethodOptionalParams} options * @param {Context} context * @returns {Promise<Models.ContainerDeleteResponse>} * @memberof ContainerHandler */ async delete(options, context) { const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; // TODO: Mark container as being deleted status, then (mark) delete all blobs async // When above finishes, execute following delete container operation // Because following delete container operation will only delete DB metadata for container and // blobs under the container, but will not clean up blob data in disk // The current design will directly remove the container and all the blobs belong to it. await this.metadataStore.deleteContainer(context, accountName, containerName, options); const response = { statusCode: 202, requestId: context.contextId, clientRequestId: options.requestId, date: context.startTime, version: constants_1.BLOB_API_VERSION }; return response; } /** * Set container metadata. * * @param {Models.ContainerSetMetadataOptionalParams} options * @param {Context} context * @returns {Promise<Models.ContainerSetMetadataResponse>} * @memberof ContainerHandler */ async setMetadata(options, context) { const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; const date = blobCtx.startTime; const eTag = (0, utils_1.newEtag)(); // Preserve metadata key case const metadata = (0, utils_1.convertRawHeadersToMetadata)(blobCtx.request.getRawHeaders(), context.contextId); await this.metadataStore.setContainerMetadata(context, accountName, containerName, date, eTag, metadata, options.leaseAccessConditions, options.modifiedAccessConditions); const response = { statusCode: 200, requestId: context.contextId, clientRequestId: options.requestId, date, eTag, lastModified: date }; return response; } /** * Get container access policy. * * @param {Models.ContainerGetAccessPolicyOptionalParams} options * @param {Context} context * @returns {Promise<Models.ContainerGetAccessPolicyResponse>} * @memberof ContainerHandler */ async getAccessPolicy(options, context) { const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; const containerAcl = await this.metadataStore.getContainerACL(context, accountName, containerName, options.leaseAccessConditions); const response = []; const responseArray = response; const responseObject = response; if (containerAcl.containerAcl !== undefined) { responseArray.push(...containerAcl.containerAcl); } responseObject.date = containerAcl.properties.lastModified; responseObject.blobPublicAccess = containerAcl.properties.publicAccess; responseObject.eTag = containerAcl.properties.etag; responseObject.lastModified = containerAcl.properties.lastModified; responseObject.requestId = context.contextId; responseObject.version = constants_1.BLOB_API_VERSION; responseObject.statusCode = 200; responseObject.clientRequestId = options.requestId; // TODO: Need fix generator code since the output containerAcl can't be serialized correctly // tslint:disable-next-line:max-line-length // Correct responds: <?xml version="1.0" encoding="utf-8"?><SignedIdentifiers><SignedIdentifier><Id>123</Id><AccessPolicy><Start>2019-04-30T16:00:00.0000000Z</Start><Expiry>2019-12-31T16:00:00.0000000Z</Expiry><Permission>r</Permission></AccessPolicy></SignedIdentifier></SignedIdentifiers> // tslint:disable-next-line:max-line-length // Current responds: <?xml version="1.0" encoding="UTF-8" standalone="yes"?><parsedResponse><Id>123</Id><AccessPolicy><Start>2019-04-30T16:00:00Z</Start><Expiry>2019-12-31T16:00:00Z</Expiry><Permission>r</Permission></AccessPolicy></parsedResponse>" return response; } /** * set container access policy * * @param {Models.ContainerSetAccessPolicyOptionalParams} options * @param {Context} context * @returns {Promise<Models.ContainerSetAccessPolicyResponse>} * @memberof ContainerHandler */ async setAccessPolicy(options, context) { const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; const date = blobCtx.startTime; const eTag = (0, utils_1.newEtag)(); await this.metadataStore.setContainerACL(context, accountName, containerName, { lastModified: date, etag: eTag, publicAccess: options.access, containerAcl: options.containerAcl, leaseAccessConditions: options.leaseAccessConditions, modifiedAccessConditions: options.modifiedAccessConditions }); const response = { date, eTag, lastModified: date, requestId: context.contextId, version: constants_1.BLOB_API_VERSION, statusCode: 200, clientRequestId: options.requestId }; return response; } async restore(options, context) { throw new NotImplementedError_1.default(context.contextId); } async submitBatch(body, contentLength, multipartContentType, options, context) { const blobServiceCtx = new BlobStorageContext_1.default(context); const requestBatchBoundary = blobServiceCtx.request.getHeader("content-type").split("=")[1]; const blobBatchHandler = new BlobBatchHandler_1.BlobBatchHandler(this.accountDataStore, this.oauth, this.metadataStore, this.extentStore, this.logger, this.loose, this.disableProductStyle); const responseBodyString = await blobBatchHandler.submitBatch(body, requestBatchBoundary, blobServiceCtx.request.getPath(), context.request, context); const responseBody = new stream_1.Readable(); responseBody.push(responseBodyString); responseBody.push(null); // No client request id defined in batch response, should refine swagger and regenerate from it. // batch response succeed code should be 202 instead of 200, should refine swagger and regenerate from it. const response = { statusCode: 202, requestId: context.contextId, version: constants_1.BLOB_API_VERSION, contentType: "multipart/mixed; boundary=" + requestBatchBoundary, body: responseBody }; return response; } async filterBlobs(options, context) { const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; await this.metadataStore.checkContainerExist(context, accountName, containerName); const request = context.request; const marker = options.marker; options.marker = options.marker || ""; if (options.maxresults === undefined || options.maxresults > constants_2.DEFAULT_LIST_BLOBS_MAX_RESULTS) { options.maxresults = constants_2.DEFAULT_LIST_BLOBS_MAX_RESULTS; } const [blobs, nextMarker] = await this.metadataStore.filterBlobs(context, accountName, containerName, options.where, options.maxresults, marker); const serviceEndpoint = `${request.getEndpoint()}/${accountName}`; const response = { statusCode: 200, requestId: context.contextId, version: constants_1.BLOB_API_VERSION, date: context.startTime, serviceEndpoint, where: options.where, blobs: blobs, clientRequestId: options.requestId, nextMarker: `${nextMarker || ""}` }; return response; } /** * Acquire container lease. * * @see https://docs.microsoft.com/en-us/rest/api/storageservices/lease-container * * @param {Models.ContainerAcquireLeaseOptionalParams} options * @param {Context} context * @returns {Promise<Models.ContainerAcquireLeaseResponse>} * @memberof ContainerHandler */ async acquireLease(options, context) { const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; const res = await this.metadataStore.acquireContainerLease(context, accountName, containerName, options); const response = { statusCode: 201, requestId: context.contextId, clientRequestId: options.requestId, date: blobCtx.startTime, eTag: res.properties.etag, lastModified: res.properties.lastModified, leaseId: res.leaseId, version: constants_1.BLOB_API_VERSION }; return response; } /** * Release container lease. * * @see https://docs.microsoft.com/en-us/rest/api/storageservices/lease-container * * @param {string} leaseId * @param {Models.ContainerReleaseLeaseOptionalParams} options * @param {Context} context * @returns {Promise<Models.ContainerReleaseLeaseResponse>} * @memberof ContainerHandler */ async releaseLease(leaseId, options, context) { const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; const res = await this.metadataStore.releaseContainerLease(context, accountName, containerName, leaseId, options); const response = { statusCode: 200, requestId: context.contextId, clientRequestId: options.requestId, date: blobCtx.startTime, eTag: res.etag, lastModified: res.lastModified, version: constants_1.BLOB_API_VERSION }; return response; } /** * Renew container lease. * * @see https://docs.microsoft.com/en-us/rest/api/storageservices/lease-container * * @param {string} leaseId * @param {Models.ContainerRenewLeaseOptionalParams} options * @param {Context} context * @returns {Promise<Models.ContainerRenewLeaseResponse>} * @memberof ContainerHandler */ async renewLease(leaseId, options, context) { const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; const res = await this.metadataStore.renewContainerLease(context, accountName, containerName, leaseId, options); const response = { statusCode: 200, requestId: context.contextId, clientRequestId: options.requestId, date: blobCtx.startTime, leaseId: res.leaseId, eTag: res.properties.etag, lastModified: res.properties.lastModified, version: constants_1.BLOB_API_VERSION }; return response; } /** * Break container lease. * * @see https://docs.microsoft.com/en-us/rest/api/storageservices/lease-container * * @param {Models.ContainerBreakLeaseOptionalParams} options * @param {Context} context * @returns {Promise<Models.ContainerBreakLeaseResponse>} * @memberof ContainerHandler */ async breakLease(options, context) { const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; const res = await this.metadataStore.breakContainerLease(context, accountName, containerName, options.breakPeriod, options); const response = { statusCode: 202, requestId: context.contextId, clientRequestId: options.requestId, date: blobCtx.startTime, eTag: res.properties.etag, lastModified: res.properties.lastModified, leaseTime: res.leaseTime, version: constants_1.BLOB_API_VERSION }; return response; } /** * Change container lease. * * @see https://docs.microsoft.com/en-us/rest/api/storageservices/lease-container * * @param {string} leaseId * @param {string} proposedLeaseId * @param {Models.ContainerChangeLeaseOptionalParams} options * @param {Context} context * @returns {Promise<Models.ContainerChangeLeaseResponse>} * @memberof ContainerHandler */ async changeLease(leaseId, proposedLeaseId, options, context) { const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; const res = await this.metadataStore.changeContainerLease(context, accountName, containerName, leaseId, proposedLeaseId, options); const response = { statusCode: 200, requestId: context.contextId, clientRequestId: options.requestId, date: blobCtx.startTime, eTag: res.properties.etag, lastModified: res.properties.lastModified, leaseId: res.leaseId, version: constants_1.BLOB_API_VERSION }; return response; } /** * list blobs flat segments * * @param {Models.ContainerListBlobFlatSegmentOptionalParams} options * @param {Context} context * @returns {Promise<Models.ContainerListBlobFlatSegmentResponse>} * @memberof ContainerHandler */ async listBlobFlatSegment(options, context) { const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; await this.metadataStore.checkContainerExist(context, accountName, containerName); const request = context.request; const marker = options.marker; options.marker = options.marker || ""; let includeSnapshots = false; let includeUncommittedBlobs = false; let includeTags = false; let includeMetadata = false; if (options.include !== undefined) { options.include.forEach(element => { if (Models.ListBlobsIncludeItem.Snapshots.toLowerCase() === element.toLowerCase()) { includeSnapshots = true; } if (Models.ListBlobsIncludeItem.Uncommittedblobs.toLowerCase() === element.toLowerCase()) { includeUncommittedBlobs = true; } if (Models.ListBlobsIncludeItem.Tags.toLowerCase() === element.toLowerCase()) { includeTags = true; } if (Models.ListBlobsIncludeItem.Metadata.toLowerCase() === element.toLowerCase()) { includeMetadata = true; } }); } if (options.maxresults === undefined || options.maxresults > constants_2.DEFAULT_LIST_BLOBS_MAX_RESULTS) { options.maxresults = constants_2.DEFAULT_LIST_BLOBS_MAX_RESULTS; } const [blobs, _prefixes, nextMarker] = await this.metadataStore.listBlobs(context, accountName, containerName, undefined, undefined, options.prefix, options.maxresults, marker, includeSnapshots, includeUncommittedBlobs); const serviceEndpoint = `${request.getEndpoint()}/${accountName}`; const response = { statusCode: 200, contentType: "application/xml", requestId: context.contextId, version: constants_1.BLOB_API_VERSION, date: context.startTime, serviceEndpoint, containerName, prefix: options.prefix || "", marker: options.marker, maxResults: options.maxresults, segment: { blobItems: blobs.map(item => { return { ...item, deleted: item.deleted !== true ? undefined : true, snapshot: item.snapshot || undefined, blobTags: includeTags ? item.blobTags : undefined, metadata: includeMetadata ? item.metadata : undefined, properties: { ...item.properties, etag: (0, utils_2.removeQuotationFromListBlobEtag)(item.properties.etag), tagCount: (0, utils_2.getBlobTagsCount)(item.blobTags), accessTierInferred: item.properties.accessTierInferred === true ? true : undefined } }; }) }, clientRequestId: options.requestId, nextMarker: `${nextMarker || ""}` }; return response; } /** * List blobs hierarchy. * * @param {string} delimiter * @param {Models.ContainerListBlobHierarchySegmentOptionalParams} options * @param {Context} context * @returns {Promise<Models.ContainerListBlobHierarchySegmentResponse>} * @memberof ContainerHandler */ async listBlobHierarchySegment(delimiter, options, context) { // TODO: Need update list out blobs lease properties with BlobHandler.updateLeaseAttributes() const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; await this.metadataStore.checkContainerExist(context, accountName, containerName); const request = context.request; const marker = options.marker; options.prefix = options.prefix || ""; options.marker = options.marker || ""; let includeSnapshots = false; let includeUncommittedBlobs = false; let includeTags = false; let includeMetadata = false; if (options.include !== undefined) { options.include.forEach(element => { if (Models.ListBlobsIncludeItem.Snapshots.toLowerCase() === element.toLowerCase()) { includeSnapshots = true; } if (Models.ListBlobsIncludeItem.Uncommittedblobs.toLowerCase() === element.toLowerCase()) { includeUncommittedBlobs = true; } if (Models.ListBlobsIncludeItem.Tags.toLowerCase() === element.toLowerCase()) { includeTags = true; } if (Models.ListBlobsIncludeItem.Metadata.toLowerCase() === element.toLowerCase()) { includeMetadata = true; } }); } if (options.maxresults === undefined || options.maxresults > constants_2.DEFAULT_LIST_BLOBS_MAX_RESULTS) { options.maxresults = constants_2.DEFAULT_LIST_BLOBS_MAX_RESULTS; } const [blobItems, blobPrefixes, nextMarker] = await this.metadataStore.listBlobs(context, accountName, containerName, delimiter === "" ? undefined : delimiter, undefined, options.prefix, options.maxresults, marker, includeSnapshots, includeUncommittedBlobs); const serviceEndpoint = `${request.getEndpoint()}/${accountName}`; const response = { statusCode: 200, contentType: "application/xml", requestId: context.contextId, version: constants_1.BLOB_API_VERSION, date: context.startTime, serviceEndpoint, containerName, prefix: options.prefix, marker: options.marker, maxResults: options.maxresults, delimiter, segment: { blobPrefixes, blobItems: blobItems.map(item => { item.deleted = item.deleted !== true ? undefined : true; return { ...item, snapshot: item.snapshot || undefined, blobTags: includeTags ? item.blobTags : undefined, metadata: includeMetadata ? item.metadata : undefined, properties: { ...item.properties, etag: (0, utils_2.removeQuotationFromListBlobEtag)(item.properties.etag), tagCount: (0, utils_2.getBlobTagsCount)(item.blobTags), accessTierInferred: item.properties.accessTierInferred === true ? true : undefined } }; }) }, clientRequestId: options.requestId, nextMarker: `${nextMarker || ""}` }; return response; } /** * get account info * * @param {Context} context * @returns {Promise<Models.ContainerGetAccountInfoResponse>} * @memberof ContainerHandler */ async getAccountInfo(context) { const response = { statusCode: 200, requestId: context.contextId, clientRequestId: context.request.getHeader("x-ms-client-request-id"), skuName: constants_1.EMULATOR_ACCOUNT_SKUNAME, accountKind: constants_1.EMULATOR_ACCOUNT_KIND, date: context.startTime, version: constants_1.BLOB_API_VERSION }; return response; } /** * get account info with headers * * @param {Context} context * @returns {Promise<Models.ContainerGetAccountInfoResponse>} * @memberof ContainerHandler */ async getAccountInfoWithHead(context) { return this.getAccountInfo(context); } } exports.default = ContainerHandler; //# sourceMappingURL=ContainerHandler.js.map