[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:
Jeremy Meng 2023-07-20 14:19:41 -07:00 коммит произвёл GitHub
Родитель 7528a7a111
Коммит 9c9c709d58
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 157 добавлений и 8 удалений

Просмотреть файл

@ -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 });