Skip to content

Commit

Permalink
Merge branch 'main' into 2458
Browse files Browse the repository at this point in the history
  • Loading branch information
blueww authored Oct 31, 2024
2 parents 29de703 + 1dd0784 commit 8d38d31
Show file tree
Hide file tree
Showing 49 changed files with 3,422 additions and 315 deletions.
13 changes: 12 additions & 1 deletion ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,20 @@

Blob:

- Fixed an issue where all blob APIs allowed metadata names which were not valid C# identifiers.
- Fixed issue of download 0 size blob with range > 0 should has header "Content-Range: bytes \*/0" in returned error. (issue #2458)

## 2024.10 Version 3.33.0

General:

- Bump up service API version to 2025-01-05

Blob:

- Added support for filtering blob by tags.
- Fixed an issue where all blob APIs allowed metadata names which were not valid C# identifiers.
- Fixed always including metadata on blob list even when not requested

## 2024.08 Version 3.32.0

General:
Expand Down
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
| Version | Azure Storage API Version | Service Support | Description | Reference Links |
| ------------------------------------------------------------------ | ------------------------- | ------------------------------ | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 3.32.0 | 2024-11-04 | Blob, Queue and Table(preview) | Azurite V3 based on TypeScript & New Architecture | [NPM](https://www.npmjs.com/package/azurite) - [Docker](https://hub.docker.com/_/microsoft-azure-storage-azurite) - [Visual Studio Code Extension](https://marketplace.visualstudio.com/items?itemName=Azurite.azurite) |
| 3.33.0 | 2025-01-05 | Blob, Queue and Table(preview) | Azurite V3 based on TypeScript & New Architecture | [NPM](https://www.npmjs.com/package/azurite) - [Docker](https://hub.docker.com/_/microsoft-azure-storage-azurite) - [Visual Studio Code Extension](https://marketplace.visualstudio.com/items?itemName=Azurite.azurite) |
| [Legacy (v2)](https://github.com/Azure/Azurite/tree/legacy-master) | 2016-05-31 | Blob, Queue and Table | Legacy Azurite V2 | [NPM](https://www.npmjs.com/package/azurite) |

- [Azurite V3](#azurite-v3)
Expand Down Expand Up @@ -78,19 +78,19 @@ Compared to V2, Azurite V3 implements a new architecture leveraging code generat

## Features & Key Changes in Azurite V3

- Blob storage features align with Azure Storage API version 2024-11-04 (Refer to support matrix section below)
- Blob storage features align with Azure Storage API version 2025-01-05 (Refer to support matrix section below)
- SharedKey/Account SAS/Service SAS/Public Access Authentications/OAuth
- Get/Set Blob Service Properties
- Create/List/Delete Containers
- Create/Read/List/Update/Delete Block Blobs
- Create/Read/List/Update/Delete Page Blobs
- Queue storage features align with Azure Storage API version 2024-11-04 (Refer to support matrix section below)
- Queue storage features align with Azure Storage API version 2025-01-05 (Refer to support matrix section below)
- SharedKey/Account SAS/Service SAS/OAuth
- Get/Set Queue Service Properties
- Preflight Request
- Create/List/Delete Queues
- Put/Get/Peek/Update/Delete/Clear Messages
- Table storage features align with Azure Storage API version 2024-11-04 (Refer to support matrix section below)
- Table storage features align with Azure Storage API version 2025-01-05 (Refer to support matrix section below)
- SharedKey/Account SAS/Service SAS/OAuth
- Create/List/Delete Tables
- Insert/Update/Query/Delete Table Entities
Expand Down Expand Up @@ -971,7 +971,7 @@ All the generated code is kept in `generated` folder, including the generated mi

## Support Matrix

Latest release targets **2024-11-04** API version **blob** service.
Latest release targets **2025-01-05** API version **blob** service.

Detailed support matrix:

Expand All @@ -981,7 +981,8 @@ Detailed support matrix:
- OAuth authentication
- Shared Access Signature Account Level
- Shared Access Signature Service Level (Not support response header override in service SAS)
- Container Public Access
- Container Public Access
- Blob Tags (preview)
- Supported REST APIs
- List Containers
- Set Service Properties
Expand Down Expand Up @@ -1017,7 +1018,6 @@ Detailed support matrix:
- Soft delete & Undelete Container
- Soft delete & Undelete Blob
- Incremental Copy Blob
- Blob Tags
- Blob Query
- Blob Versions
- Blob Last Access Time
Expand All @@ -1029,8 +1029,9 @@ Detailed support matrix:
- Sync copy blob by access source with oauth
- Encryption Scope
- Get Page Ranges Continuation Token
- Blob Immutability Policy and Legal Hold

Latest version supports for **2024-11-04** API version **queue** service.
Latest version supports for **2025-01-05** API version **queue** service.
Detailed support matrix:

- Supported Vertical Features
Expand Down Expand Up @@ -1059,7 +1060,7 @@ Detailed support matrix:
- Following features or REST APIs are NOT supported or limited supported in this release (will support more features per customers feedback in future releases)
- SharedKey Lite

Latest version supports for **2024-11-04** API version **table** service (preview).
Latest version supports for **2025-01-05** API version **table** service (preview).
Detailed support matrix:

- Supported Vertical Features
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 2 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"displayName": "Azurite",
"description": "An open source Azure Storage API compatible server",
"icon": "icon.png",
"version": "3.32.0",
"version": "3.33.0",
"publisher": "Azurite",
"categories": [
"Other"
Expand Down Expand Up @@ -89,19 +89,7 @@
"vsce": "^2.7.0"
},
"activationEvents": [
"*",
"onCommand:azurite.start",
"onCommand:azurite.close",
"onCommand:azurite.clean",
"onCommand:azurite.start_blob",
"onCommand:azurite.close_blob",
"onCommand:azurite.clean_blob",
"onCommand:azurite.start_queue",
"onCommand:azurite.close_queue",
"onCommand:azurite.clean_queue",
"onCommand:azurite.start_table",
"onCommand:azurite.close_table",
"onCommand:azurite.clean_table"
"onStartupFinished"
],
"contributes": {
"commands": [
Expand Down
1 change: 1 addition & 0 deletions src/blob/authentication/ContainerSASPermissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ export enum ContainerSASPermission {
Write = "w",
Delete = "d",
List = "l",
Filter = "f",
Any = "AnyPermission" // This is only for blob batch operation.
}
4 changes: 4 additions & 0 deletions src/blob/authentication/OperationBlobSASPermission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,10 @@ OPERATION_BLOB_SAS_CONTAINER_PERMISSIONS.set(
Operation.Container_ListBlobFlatSegment,
new OperationBlobSASPermission(ContainerSASPermission.List)
);
OPERATION_BLOB_SAS_CONTAINER_PERMISSIONS.set(
Operation.Container_FilterBlobs,
new OperationBlobSASPermission(ContainerSASPermission.Filter)
);
OPERATION_BLOB_SAS_CONTAINER_PERMISSIONS.set(
Operation.Container_ListBlobHierarchySegment,
new OperationBlobSASPermission(ContainerSASPermission.List)
Expand Down
10 changes: 9 additions & 1 deletion src/blob/conditions/ConditionResourceAdapter.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { BlobModel, ContainerModel } from "../persistence/IBlobMetadataStore";
import { BlobModel, ContainerModel, FilterBlobModel } from "../persistence/IBlobMetadataStore";
import IConditionResource from "./IConditionResource";

export default class ConditionResourceAdapter implements IConditionResource {
public exist: boolean;
public etag: string;
public lastModified: Date;
public blobItemWithTags?: FilterBlobModel;

public constructor(resource: BlobModel | ContainerModel | undefined | null) {
if (
Expand Down Expand Up @@ -33,5 +34,12 @@ export default class ConditionResourceAdapter implements IConditionResource {

this.lastModified = new Date(resource.properties.lastModified);
this.lastModified.setMilliseconds(0); // Precision to seconds

const blobItem = resource as BlobModel;
this.blobItemWithTags = {
name: blobItem.name,
containerName: blobItem.containerName,
tags: blobItem.blobTags
};
}
}
3 changes: 3 additions & 0 deletions src/blob/conditions/ConditionalHeadersAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default class ConditionalHeadersAdapter implements IConditionalHeaders {
public ifUnmodifiedSince?: Date;
public ifMatch?: string[];
public ifNoneMatch?: string[];
public ifTags?: string;

public constructor(
context: Context,
Expand Down Expand Up @@ -43,5 +44,7 @@ export default class ConditionalHeadersAdapter implements IConditionalHeaders {
if (this.ifUnmodifiedSince) {
this.ifUnmodifiedSince.setMilliseconds(0); // Precision to seconds
}

this.ifTags = modifiedAccessConditions.ifTags;
}
}
3 changes: 3 additions & 0 deletions src/blob/conditions/IConditionResource.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { FilterBlobModel } from "../persistence/IBlobMetadataStore";

export default interface IConditionResource {
/**
* Whether resource exists or not.
Expand All @@ -13,4 +15,5 @@ export default interface IConditionResource {
* last modified time for container or blob.
*/
lastModified: Date;
blobItemWithTags?: FilterBlobModel;
}
5 changes: 5 additions & 0 deletions src/blob/conditions/IConditionalHeaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ export interface IConditionalHeaders {
* If-None-Match etag list without quotes.
*/
ifNoneMatch?: string[];

/**
* Specify a SQL where clause on blob tags to operate only on blobs with a matching value.
*/
ifTags?: string;
}
3 changes: 2 additions & 1 deletion src/blob/conditions/IConditionalHeadersValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface IConditionalHeadersValidator {
validate(
context: Context,
conditionalHeaders: IConditionalHeaders,
resource: IConditionResource
resource: IConditionResource,
isSourceBlob?: boolean
): void;
}
23 changes: 19 additions & 4 deletions src/blob/conditions/ReadConditionalHeadersValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import StorageErrorFactory from "../errors/StorageErrorFactory";
import { ModifiedAccessConditions } from "../generated/artifacts/models";
import Context from "../generated/Context";
import { BlobModel, ContainerModel } from "../persistence/IBlobMetadataStore";
import { generateQueryBlobWithTagsWhereFunction } from "../persistence/QueryInterpreter/QueryInterpreter";
import ConditionalHeadersAdapter from "./ConditionalHeadersAdapter";
import ConditionResourceAdapter from "./ConditionResourceAdapter";
import { IConditionalHeaders } from "./IConditionalHeaders";
Expand All @@ -11,12 +12,14 @@ import IConditionResource from "./IConditionResource";
export function validateReadConditions(
context: Context,
conditionalHeaders?: ModifiedAccessConditions,
model?: BlobModel | ContainerModel | null
model?: BlobModel | ContainerModel | null,
isSourceBlob?: boolean
) {
new ReadConditionalHeadersValidator().validate(
context,
new ConditionalHeadersAdapter(context, conditionalHeaders),
new ConditionResourceAdapter(model)
new ConditionResourceAdapter(model),
isSourceBlob
);
}

Expand All @@ -30,11 +33,13 @@ export default class ReadConditionalHeadersValidator
* @param context
* @param conditionalHeaders
* @param resource
* @param isSourceBlob
*/
public validate(
context: Context,
conditionalHeaders: IConditionalHeaders,
resource: IConditionResource
resource: IConditionResource,
isSourceBlob?: boolean
): void {
// If-Match && If-Unmodified-Since && (If-None-Match || If-Modified-Since)

Expand Down Expand Up @@ -66,7 +71,7 @@ export default class ReadConditionalHeadersValidator
// If-Match
const ifMatchPass = conditionalHeaders.ifMatch
? conditionalHeaders.ifMatch.includes(resource.etag) ||
conditionalHeaders.ifMatch[0] === "*"
conditionalHeaders.ifMatch[0] === "*"
: undefined;

// If-Unmodified-Since
Expand Down Expand Up @@ -107,6 +112,16 @@ export default class ReadConditionalHeadersValidator
if (isModifiedSincePass === false && ifNoneMatchPass !== true) {
throw StorageErrorFactory.getNotModified(context.contextId!);
}

if (conditionalHeaders.ifTags) {
const againstSourceBlob = isSourceBlob === undefined ? false : isSourceBlob;
const validateFunction = generateQueryBlobWithTagsWhereFunction(context, conditionalHeaders.ifTags, againstSourceBlob ? 'x-ms-source-if-tags' : 'x-ms-if-tags');

if (conditionalHeaders?.ifTags !== undefined
&& validateFunction(resource.blobItemWithTags).length === 0) {
throw StorageErrorFactory.getConditionNotMet(context.contextId!);
}
}
}
}
}
16 changes: 13 additions & 3 deletions src/blob/conditions/WriteConditionalHeadersValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
} from "../generated/artifacts/models";
import Context from "../generated/Context";
import { BlobModel, ContainerModel } from "../persistence/IBlobMetadataStore";
import { generateQueryBlobWithTagsWhereFunction } from "../persistence/QueryInterpreter/QueryInterpreter";
import ConditionalHeadersAdapter from "./ConditionalHeadersAdapter";
import ConditionResourceAdapter from "./ConditionResourceAdapter";
import { IConditionalHeaders } from "./IConditionalHeaders";
Expand All @@ -29,7 +30,7 @@ export function validateSequenceNumberWriteConditions(
if (
conditionalHeaders.ifSequenceNumberLessThanOrEqualTo !== undefined &&
conditionalHeaders.ifSequenceNumberLessThanOrEqualTo <
model.properties.blobSequenceNumber
model.properties.blobSequenceNumber
) {
throw StorageErrorFactory.getSequenceNumberConditionNotMet(
context.contextId!
Expand All @@ -39,7 +40,7 @@ export function validateSequenceNumberWriteConditions(
if (
conditionalHeaders.ifSequenceNumberLessThan !== undefined &&
conditionalHeaders.ifSequenceNumberLessThan <=
model.properties.blobSequenceNumber
model.properties.blobSequenceNumber
) {
throw StorageErrorFactory.getSequenceNumberConditionNotMet(
context.contextId!
Expand All @@ -49,7 +50,7 @@ export function validateSequenceNumberWriteConditions(
if (
conditionalHeaders.ifSequenceNumberEqualTo !== undefined &&
conditionalHeaders.ifSequenceNumberEqualTo !==
model.properties.blobSequenceNumber
model.properties.blobSequenceNumber
) {
throw StorageErrorFactory.getSequenceNumberConditionNotMet(
context.contextId!
Expand Down Expand Up @@ -167,6 +168,15 @@ export default class WriteConditionalHeadersValidator
}
return;
}

if (conditionalHeaders.ifTags) {
const validateFunction = generateQueryBlobWithTagsWhereFunction(context, conditionalHeaders.ifTags, 'x-ms-if-tags');

if (conditionalHeaders?.ifTags !== undefined
&& validateFunction(resource.blobItemWithTags).length === 0) {
throw StorageErrorFactory.getConditionNotMet(context.contextId!);
}
}
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/blob/errors/StorageErrorFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -785,7 +785,6 @@ export default class StorageErrorFactory {
{ ReceivedCopyStatus: copyStatus }
);
}


public static getInvalidMetadata(contextID: string): StorageError {
return new StorageError(
Expand Down Expand Up @@ -830,7 +829,7 @@ export default class StorageErrorFactory {
"The tags specified are invalid. It contains characters that are not permitted.",
contextID
);
}
}

public static getInvalidXmlDocument(
contextID: string = ""
Expand Down
Loading

0 comments on commit 8d38d31

Please sign in to comment.