azurite
Version:
An open source Azure Storage API compatible server
598 lines • 26.6 kB
JavaScript
"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