[Storage] Port encryption context support from storage/stable branch (#26553)
Port PR https://github.com/Azure/azure-sdk-for-js/pull/25094 - update swagger and re-generate - update models - port tests - port product source changes - add recordings for new tests
This commit is contained in:
Родитель
7528a7a111
Коммит
9c9c709d58
|
@ -2,5 +2,5 @@
|
|||
"AssetsRepo": "Azure/azure-sdk-assets",
|
||||
"AssetsRepoPrefixPath": "js",
|
||||
"TagPrefix": "js/storage/storage-file-datalake",
|
||||
"Tag": "js/storage/storage-file-datalake_2407e32cf2"
|
||||
"Tag": "js/storage/storage-file-datalake_1bed51a03a"
|
||||
}
|
||||
|
|
|
@ -565,6 +565,7 @@ export interface FileParallelUploadOptions extends CommonOptions {
|
|||
close?: boolean;
|
||||
conditions?: DataLakeRequestConditions;
|
||||
customerProvidedKey?: CpkInfo;
|
||||
encryptionContext?: string;
|
||||
maxConcurrency?: number;
|
||||
metadata?: Metadata;
|
||||
onProgress?: (progress: TransferProgressEvent) => void;
|
||||
|
@ -656,6 +657,7 @@ export interface FileReadHeaders {
|
|||
createdOn?: Date;
|
||||
// (undocumented)
|
||||
date?: Date;
|
||||
encryptionContext?: string;
|
||||
// (undocumented)
|
||||
encryptionKeySha256?: string;
|
||||
// (undocumented)
|
||||
|
@ -1158,6 +1160,7 @@ export interface Path {
|
|||
// (undocumented)
|
||||
contentLength?: number;
|
||||
createdOn?: Date;
|
||||
encryptionContext?: string;
|
||||
encryptionScope?: string;
|
||||
// (undocumented)
|
||||
etag?: string;
|
||||
|
@ -1260,6 +1263,7 @@ export interface PathCreateIfNotExistsOptions extends CommonOptions {
|
|||
abortSignal?: AbortSignalLike;
|
||||
acl?: PathAccessControlItem[];
|
||||
customerProvidedKey?: CpkInfo;
|
||||
encryptionContext?: string;
|
||||
expiresOn?: number | Date;
|
||||
group?: string;
|
||||
leaseDuration?: number;
|
||||
|
@ -1288,6 +1292,7 @@ export interface PathCreateOptions extends CommonOptions {
|
|||
// (undocumented)
|
||||
conditions?: DataLakeRequestConditions;
|
||||
customerProvidedKey?: CpkInfo;
|
||||
encryptionContext?: string;
|
||||
expiresOn?: number | Date;
|
||||
group?: string;
|
||||
leaseDuration?: number;
|
||||
|
@ -1440,6 +1445,7 @@ export interface PathGetPropertiesHeaders {
|
|||
date?: Date;
|
||||
// (undocumented)
|
||||
destinationSnapshot?: string;
|
||||
encryptionContext?: string;
|
||||
// (undocumented)
|
||||
encryptionKeySha256?: string;
|
||||
encryptionScope?: string;
|
||||
|
@ -1540,6 +1546,8 @@ export interface PathModel {
|
|||
contentLength?: number;
|
||||
// (undocumented)
|
||||
creationTime?: string;
|
||||
// (undocumented)
|
||||
encryptionContext?: string;
|
||||
encryptionScope?: string;
|
||||
// (undocumented)
|
||||
etag?: string;
|
||||
|
|
|
@ -103,6 +103,7 @@ import {
|
|||
assertResponse,
|
||||
ensureCpkIfSpecified,
|
||||
getURLPathAndQuery,
|
||||
ParseEncryptionContextHeaderValue,
|
||||
setURLPath,
|
||||
setURLQueries,
|
||||
} from "./utils/utils.common";
|
||||
|
@ -671,11 +672,12 @@ export class DataLakePathClient extends StorageClient {
|
|||
"DataLakePathClient-getProperties",
|
||||
options,
|
||||
async (updatedOptions) => {
|
||||
return this.blobClient.getProperties({
|
||||
const response = await this.blobClient.getProperties({
|
||||
...options,
|
||||
customerProvidedKey: toBlobCpkInfo(options.customerProvidedKey),
|
||||
tracingOptions: updatedOptions.tracingOptions,
|
||||
});
|
||||
return ParseEncryptionContextHeaderValue(response as PathGetPropertiesResponse);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -1228,7 +1230,9 @@ export class DataLakeFileClient extends DataLakePathClient {
|
|||
customerProvidedKey: toBlobCpkInfo(updatedOptions.customerProvidedKey),
|
||||
});
|
||||
|
||||
const response = rawResponse as FileReadResponse;
|
||||
const response = ParseEncryptionContextHeaderValue(
|
||||
rawResponse as FileReadResponse
|
||||
) as FileReadResponse;
|
||||
if (!isNode && !response.contentAsBlob) {
|
||||
response.contentAsBlob = rawResponse.blobBody;
|
||||
}
|
||||
|
@ -1415,6 +1419,7 @@ export class DataLakeFileClient extends DataLakePathClient {
|
|||
pathHttpHeaders: options.pathHttpHeaders,
|
||||
customerProvidedKey: updatedOptions.customerProvidedKey,
|
||||
tracingOptions: updatedOptions.tracingOptions,
|
||||
encryptionContext: updatedOptions.encryptionContext,
|
||||
});
|
||||
// append() with empty data would return error, so do not continue
|
||||
if (size === 0) {
|
||||
|
@ -1553,6 +1558,7 @@ export class DataLakeFileClient extends DataLakePathClient {
|
|||
pathHttpHeaders: options.pathHttpHeaders,
|
||||
customerProvidedKey: options.customerProvidedKey,
|
||||
tracingOptions: updatedOptions.tracingOptions,
|
||||
encryptionContext: updatedOptions.encryptionContext,
|
||||
});
|
||||
|
||||
// After the File is Create, Lease ID is the only valid request parameter.
|
||||
|
|
|
@ -50,6 +50,7 @@ export interface Path {
|
|||
encryptionScope?: string;
|
||||
creationTime?: string;
|
||||
expiryTime?: string;
|
||||
encryptionContext?: string;
|
||||
}
|
||||
|
||||
/** An enumeration of blobs */
|
||||
|
@ -939,6 +940,8 @@ export interface PathCreateOptionalParams extends coreClient.OperationOptions {
|
|||
expiryOptions?: PathExpiryOptions;
|
||||
/** The time to set the blob to expiry */
|
||||
expiresOn?: string;
|
||||
/** Specifies the encryption context to set on the file. */
|
||||
encryptionContext?: string;
|
||||
}
|
||||
|
||||
/** Contains response data for the create operation. */
|
||||
|
|
|
@ -219,6 +219,13 @@ export const Path: coreClient.CompositeMapper = {
|
|||
type: {
|
||||
name: "String"
|
||||
}
|
||||
},
|
||||
encryptionContext: {
|
||||
serializedName: "EncryptionContext",
|
||||
xmlName: "EncryptionContext",
|
||||
type: {
|
||||
name: "String"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,7 +113,7 @@ export const timeout: OperationQueryParameter = {
|
|||
export const version: OperationParameter = {
|
||||
parameterPath: "version",
|
||||
mapper: {
|
||||
defaultValue: "2021-12-02",
|
||||
defaultValue: "2022-11-02",
|
||||
isConstant: true,
|
||||
serializedName: "x-ms-version",
|
||||
type: {
|
||||
|
@ -626,6 +626,17 @@ export const expiresOn: OperationParameter = {
|
|||
}
|
||||
};
|
||||
|
||||
export const encryptionContext: OperationParameter = {
|
||||
parameterPath: ["options", "encryptionContext"],
|
||||
mapper: {
|
||||
serializedName: "x-ms-encryption-context",
|
||||
xmlName: "x-ms-encryption-context",
|
||||
type: {
|
||||
name: "String"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const contentType1: OperationParameter = {
|
||||
parameterPath: ["options", "contentType"],
|
||||
mapper: {
|
||||
|
|
|
@ -300,7 +300,8 @@ const createOperationSpec: coreClient.OperationSpec = {
|
|||
Parameters.proposedLeaseId,
|
||||
Parameters.leaseDuration,
|
||||
Parameters.expiryOptions,
|
||||
Parameters.expiresOn
|
||||
Parameters.expiresOn,
|
||||
Parameters.encryptionContext
|
||||
],
|
||||
serializer
|
||||
};
|
||||
|
|
|
@ -63,7 +63,7 @@ export class StorageClient extends coreHttpCompat.ExtendedServiceClient {
|
|||
this.url = url;
|
||||
|
||||
// Assigning values to Constant parameters
|
||||
this.version = options.version || "2021-12-02";
|
||||
this.version = options.version || "2022-11-02";
|
||||
this.resource = options.resource || "filesystem";
|
||||
this.service = new ServiceImpl(this);
|
||||
this.fileSystemOperations = new FileSystemOperationsImpl(this);
|
||||
|
|
|
@ -502,6 +502,10 @@ export interface Path {
|
|||
* Expiry time of the path.
|
||||
*/
|
||||
expiresOn?: Date;
|
||||
/**
|
||||
* Specifies the encryption context to set on the file.
|
||||
*/
|
||||
encryptionContext?: string;
|
||||
}
|
||||
|
||||
export interface PathList {
|
||||
|
@ -705,6 +709,10 @@ export interface PathCreateOptions extends CommonOptions {
|
|||
* Does not apply to directories.
|
||||
*/
|
||||
expiresOn?: number | Date;
|
||||
/**
|
||||
* Optional. Specifies the encryption context to set on the file.
|
||||
*/
|
||||
encryptionContext?: string;
|
||||
}
|
||||
|
||||
export interface PathCreateIfNotExistsOptions extends CommonOptions {
|
||||
|
@ -744,6 +752,10 @@ export interface PathCreateIfNotExistsOptions extends CommonOptions {
|
|||
* Does not apply to directories.
|
||||
*/
|
||||
expiresOn?: number | Date;
|
||||
/**
|
||||
* Optional. Specifies the encryption context to set on the file.
|
||||
*/
|
||||
encryptionContext?: string;
|
||||
}
|
||||
|
||||
export interface PathDeleteOptions extends CommonOptions {
|
||||
|
@ -959,6 +971,10 @@ export interface PathGetPropertiesHeaders {
|
|||
* The time the file will expire.
|
||||
*/
|
||||
expiresOn?: Date;
|
||||
/**
|
||||
* Optional. Specifies the encryption context to set on the file.
|
||||
*/
|
||||
encryptionContext?: string;
|
||||
}
|
||||
|
||||
export type PathGetPropertiesResponse = WithResponse<
|
||||
|
@ -1175,6 +1191,10 @@ export interface FileReadHeaders {
|
|||
encryptionKeySha256?: string;
|
||||
fileContentMD5?: Uint8Array; // Content MD5 for whole file
|
||||
contentCrc64?: Uint8Array;
|
||||
/**
|
||||
* Specifies the encryption context to set on the file.
|
||||
*/
|
||||
encryptionContext?: string;
|
||||
}
|
||||
|
||||
export type FileReadResponse = WithResponse<
|
||||
|
@ -1333,6 +1353,10 @@ export interface FileParallelUploadOptions extends CommonOptions {
|
|||
* Customer Provided Key Info.
|
||||
*/
|
||||
customerProvidedKey?: CpkInfo;
|
||||
/**
|
||||
* Specifies the encryption context to set on the file.
|
||||
*/
|
||||
encryptionContext?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,8 @@ import {
|
|||
PathStylePorts,
|
||||
UrlConstants,
|
||||
} from "./constants";
|
||||
import { HttpResponse } from "@azure/storage-blob";
|
||||
import { HttpHeadersLike } from "@azure/core-http-compat";
|
||||
|
||||
/**
|
||||
* Reserved URL characters must be properly escaped for Storage services like Blob or File.
|
||||
|
@ -628,3 +630,31 @@ export function assertResponse<T extends object, Headers = undefined, Body = und
|
|||
|
||||
throw new TypeError(`Unexpected response object ${response}`);
|
||||
}
|
||||
|
||||
export interface RawResponseWithEncryptionContextLike {
|
||||
encryptionContext?: string;
|
||||
_response: HttpResponse & {
|
||||
parsedHeaders: {
|
||||
encryptionContext?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse value of encryption context from headers in raw response.
|
||||
*/
|
||||
export function ParseEncryptionContextHeaderValue(
|
||||
rawResponse: RawResponseWithEncryptionContextLike
|
||||
): RawResponseWithEncryptionContextLike {
|
||||
const response = rawResponse;
|
||||
if (rawResponse._response) {
|
||||
const headers = rawResponse._response.headers as HttpHeadersLike;
|
||||
if (headers) {
|
||||
response.encryptionContext = headers.get("x-ms-encryption-context");
|
||||
if (response._response.parsedHeaders) {
|
||||
response._response.parsedHeaders.encryptionContext = response.encryptionContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ enable-xml: true
|
|||
generate-metadata: false
|
||||
license-header: MICROSOFT_MIT_NO_VERSION
|
||||
output-folder: ../src/generated
|
||||
input-file: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/0cd0d5c070e85ea7d5816df056eddadc939b898a/specification/storage/data-plane/Azure.Storage.Files.DataLake/preview/2021-06-08/DataLakeStorage.json
|
||||
input-file: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/3f3b51edf8fd0eb65004df390d6ee98e0e23c53d/specification/storage/data-plane/Azure.Storage.Files.DataLake/preview/2021-06-08/DataLakeStorage.json
|
||||
model-date-time-as-string: true
|
||||
optional-response-headers: true
|
||||
v3: true
|
||||
|
@ -354,5 +354,5 @@ directive:
|
|||
directive:
|
||||
- from: swagger-document
|
||||
where: $.parameters.ApiVersionParameter
|
||||
transform: $.enum = [ "2021-12-02" ];
|
||||
transform: $.enum = [ "2022-11-02" ];
|
||||
```
|
||||
|
|
|
@ -243,6 +243,30 @@ describe("DataLakeFileSystemClient", () => {
|
|||
await dirClient.delete();
|
||||
});
|
||||
|
||||
it("listPaths - Encryption context", async function (this: Context) {
|
||||
const encryptionContext = "EncryptionContext";
|
||||
|
||||
const cClient = serviceClient.getFileSystemClient(
|
||||
recorder.variable(fileSystemName, getUniqueName(fileSystemName))
|
||||
);
|
||||
await cClient.create();
|
||||
|
||||
const fileClient = cClient.getFileClient(recorder.variable(`file`, getUniqueName(`file`)));
|
||||
await fileClient.create({ encryptionContext: encryptionContext });
|
||||
|
||||
const dirClient = cClient.getFileClient(recorder.variable(`dir`, getUniqueName(`dir`)));
|
||||
await dirClient.create({ encryptionContext: encryptionContext });
|
||||
|
||||
const result = (await cClient.listPaths().byPage().next()).value as FileSystemListPathsResponse;
|
||||
|
||||
assert.equal(result.pathItems!.length, 2);
|
||||
assert.equal(result.pathItems![0].encryptionContext, encryptionContext);
|
||||
assert.equal(result.pathItems![1].encryptionContext, encryptionContext);
|
||||
|
||||
await fileClient.delete();
|
||||
await dirClient.delete();
|
||||
});
|
||||
|
||||
it("listPaths - PagedAsyncIterableIterator with Encryption Scope", async function (this: Context) {
|
||||
let encryptionScopeName;
|
||||
try {
|
||||
|
|
|
@ -281,6 +281,7 @@ describe("DataLakePathClient", () => {
|
|||
const timeToExpireInMs = 60 * 1000; // 60s
|
||||
|
||||
const testFileName = recorder.variable("testfile", getUniqueName("testfile"));
|
||||
const encryptionContext = "EncryptionContext";
|
||||
const testFileClient = fileSystemClient.getFileClient(testFileName);
|
||||
await testFileClient.create({
|
||||
metadata: metadata,
|
||||
|
@ -290,6 +291,7 @@ describe("DataLakePathClient", () => {
|
|||
proposedLeaseId: leaseId,
|
||||
leaseDuration: leaseDuration,
|
||||
expiresOn: timeToExpireInMs,
|
||||
encryptionContext,
|
||||
});
|
||||
|
||||
const result = await testFileClient.getProperties();
|
||||
|
@ -303,6 +305,7 @@ describe("DataLakePathClient", () => {
|
|||
assert.equal(result.contentLanguage, httpHeader.contentLanguage);
|
||||
assert.equal(result.contentDisposition, httpHeader.contentDisposition);
|
||||
assert.equal(result.contentType, httpHeader.contentType);
|
||||
assert.equal(result.encryptionContext, encryptionContext);
|
||||
const aclResult = await testFileClient.getAccessControl();
|
||||
const permissions = {
|
||||
owner: {
|
||||
|
@ -440,6 +443,16 @@ describe("DataLakePathClient", () => {
|
|||
assert.ok(!(await testFileClient.exists()));
|
||||
});
|
||||
|
||||
it("DataLakeFileClient createIfNotExist with encryption context", async () => {
|
||||
const testFileName = recorder.variable("testfile", getUniqueName("testfile"));
|
||||
const encryptionContext = "EncryptionContext";
|
||||
const testFileClient = fileSystemClient.getFileClient(testFileName);
|
||||
await testFileClient.createIfNotExists({ encryptionContext: encryptionContext });
|
||||
|
||||
const result = await testFileClient.getProperties();
|
||||
assert.equal(result.encryptionContext, encryptionContext);
|
||||
});
|
||||
|
||||
it("DataLakeDirectoryClient create with default parameters", async () => {
|
||||
const testDirName = recorder.variable("testdir", getUniqueName("testdir"));
|
||||
const testdirClient = fileSystemClient.getDirectoryClient(testDirName);
|
||||
|
@ -447,6 +460,15 @@ describe("DataLakePathClient", () => {
|
|||
assert.ok(await testdirClient.exists());
|
||||
});
|
||||
|
||||
it("DataLakeDirectoryClient create with encryption context", async () => {
|
||||
const testDirName = recorder.variable("testdir", getUniqueName("testdir"));
|
||||
const testdirClient = fileSystemClient.getDirectoryClient(testDirName);
|
||||
const encryptionContext = "EncryptionContext";
|
||||
await testdirClient.create({ encryptionContext: encryptionContext });
|
||||
const properties = await testdirClient.getProperties();
|
||||
assert.equal(properties.encryptionContext, encryptionContext);
|
||||
});
|
||||
|
||||
it("DataLakeDirectoryClient create with meta data", async () => {
|
||||
const testDirName = recorder.variable("testdir", getUniqueName("testdir"));
|
||||
const testDirClient = fileSystemClient.getDirectoryClient(testDirName);
|
||||
|
@ -739,6 +761,19 @@ describe("DataLakePathClient", () => {
|
|||
assert.deepStrictEqual(await bodyToString(result, content.length), content);
|
||||
});
|
||||
|
||||
it("read a file with encryption context set", async () => {
|
||||
const testFileName = recorder.variable("file1", getUniqueName("file1"));
|
||||
const testFileClient = fileSystemClient.getFileClient(testFileName);
|
||||
const encryptionContext = "EncryptionContext";
|
||||
await testFileClient.create({ encryptionContext: encryptionContext });
|
||||
await testFileClient.append(content, 0, content.length);
|
||||
await testFileClient.flush(content.length);
|
||||
const result = await testFileClient.read();
|
||||
assert.equal(result.encryptionContext, encryptionContext);
|
||||
assert.deepStrictEqual(await bodyToString(result, content.length), content);
|
||||
assert.exists(result.createdOn);
|
||||
});
|
||||
|
||||
it("read should not have aborted error after read finishes", async () => {
|
||||
const aborter = new AbortController();
|
||||
const result = await fileClient.read(0, undefined, { abortSignal: aborter.signal });
|
||||
|
|
Загрузка…
Ссылка в новой задаче