Conversation
Adds ADSL Gen 2 feature parity to Azurite. See docs/design/ADLS-gen2-parity.md file for details.
|
@microsoft-github-policy-service agree |
|
hmm ok, just found this doc here: https://github.com/Azure/Azurite/wiki/ADLS-Gen2-Implementation-Guidance |
…DFS endpoint Close compliance gaps with the Azurite ADLS Gen2 Implementation Guidance wiki: - Add --enableHierarchicalNamespace CLI flag (default: true) so the emulator can run in FNS or HNS mode; wired through BlobEnvironment, Environment, VSCEnvironment, DfsServer, and FilesystemHandler. - Replace non-atomic copy+delete rename with persistence-level renameBlob() and renameBlobsByPrefix() on both LokiJS and SQL stores, keeping directory renames atomic and avoiding unnecessary extent copies. - Extract and forward If-Match, If-None-Match, If-Modified-Since, and If-Unmodified-Since conditional headers on DFS getProperties, read, and delete operations via ModifiedAccessConditions. - Expose full blob lease lifecycle (acquire, release, renew, break, change) on the DFS endpoint by detecting x-ms-lease-action and delegating to the existing IBlobMetadataStore lease infrastructure. - Add DFS-specific error helpers for conditional and lease failures. - Extend dfsProxy.test.ts with tests for conditional headers, all lease operations, and atomic directory rename with children verification. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduce the Swagger-generated DFS interface layer as called for by the Azurite ADLS Gen2 Implementation Guidance wiki: - swagger/dfs-storage-2023-11-03.json: OpenAPI 2.0 spec covering all DFS REST API operations (filesystem CRUD, path CRUD, read, update, lease). - swagger/dfs.md: AutoRest configuration for DFS code generation. - src/blob/generated-dfs/: Generated-style TypeScript artifacts mirroring the blob generated layer pattern (src/blob/generated/): - artifacts/operation.ts: DfsOperation enum (12 operations) - artifacts/models.ts: Typed request/response interfaces for all DFS ops - artifacts/specifications.ts: Dispatch specs for HTTP-to-operation matching - handlers/IFilesystemHandler.ts, IPathHandler.ts: Handler contracts - handlers/IHandlers.ts: Combined IDfsHandlers interface - handlers/handlerMappers.ts: Operation-to-handler routing map - Context.ts: DFS request context for the generated pattern - package.json: Added build:autorest:dfs script - DfsRequestListenerFactory.ts: Architecture docs referencing generated layer Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement the dedicated hierarchy table required by the Azurite ADLS Gen2
Implementation Guidance wiki ("Add table matching each item with parent").
- IBlobMetadataStore: Add registerHnsPath, unregisterHnsPath,
unregisterHnsPathsByPrefix, renameHnsPaths, isHnsDirectoryEmpty, and
hnsPathExists methods for managing parent-child relationships.
- LokiBlobMetadataStore: New $HNS_HIERARCHY$ collection indexed on
(accountName, containerName, path) and (parentPath) for fast lookups.
- SqlBlobMetadataStore: New HnsHierarchy table with unique path index and
parentPath index; rename operations use transactions.
- PathHandler: create() registers paths in hierarchy; delete() uses
isHnsDirectoryEmpty() for non-recursive guard and cleans up hierarchy
records; renamePath() updates hierarchy atomically;
ensureIntermediateDirectories() registers intermediate dirs.
- Tests: non-empty directory delete returns 409; recursive delete succeeds;
auto-created intermediate directories are visible via HEAD.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds native ADLS Gen2 (DFS) endpoint support to Azurite by standing up a first-class DFS server that shares the existing blob metadata/extent stores, plus a broad test suite and configuration/documentation updates.
Changes:
- Introduces a native DFS Express pipeline (context/auth/dispatch + filesystem/path handlers) running on a new configurable DFS host/port.
- Extends blob metadata stores with atomic rename operations to support DFS rename semantics (single-path + prefix/directory).
- Adds DFS-focused integration tests and updates CLI/VSC/Docker/README to expose the DFS endpoint and HNS toggle.
Reviewed changes
Copilot reviewed 30 out of 30 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/blob/dfsProxy.test.ts | Adds DFS end-to-end tests for filesystem/path ops, append/flush, ACLs, leases, and rename. |
| src/common/VSCEnvironment.ts | Adds dfsHost/dfsPort and enableHierarchicalNamespace to VS Code environment config. |
| src/common/Telemetry.ts | Adds dfsHost/dfsPort to telemetry parameter collection. |
| src/common/Environment.ts | Adds CLI flags for DFS host/port and hierarchical namespace toggle. |
| src/blob/utils/constants.ts | Adds DFS defaults and changes HNS default constant behavior. |
| src/blob/persistence/SqlBlobMetadataStore.ts | Implements SQL rename operations for DFS (single blob + prefix). |
| src/blob/persistence/LokiBlobMetadataStore.ts | Implements Loki rename operations for DFS (single blob + prefix). |
| src/blob/persistence/IBlobMetadataStore.ts | Extends store interface with rename methods used by DFS rename. |
| src/blob/main.ts | Starts DFS server alongside standalone blob server and wires shared stores. |
| src/blob/dfs/handlers/PathHandler.ts | Implements DFS path CRUD, list paths, append/flush, ACLs, leases, and rename. |
| src/blob/dfs/handlers/FilesystemHandler.ts | Implements DFS filesystem CRUD/list and property setting via container operations. |
| src/blob/dfs/DfsPropertyEncoding.ts | Adds shared base64 properties header encode/decode helper. |
| src/blob/dfs/DfsOperation.ts | Defines DFS operation enum for routing/auth mapping. |
| src/blob/dfs/DfsErrorFactory.ts | Adds DFS JSON error shaping (including special-casing HEAD). |
| src/blob/dfs/DfsContextFactory.ts | Creates minimal blob Context objects for store calls from DFS handlers. |
| src/blob/dfs/DfsContext.ts | Extracts DFS request context (account/filesystem/path) and performs version checks. |
| src/blob/dfs/DfsAuthenticationMiddleware.ts | Adds DFS auth middleware reusing blob authenticators. |
| src/blob/SqlBlobServer.ts | Exposes stores publicly to allow DFS to share them. |
| src/blob/IBlobEnvironment.ts | Extends blob environment interface with DFS host/port + HNS toggle. |
| src/blob/DfsServer.ts | Adds new DFS server implementation (HTTP/HTTPS) using shared stores. |
| src/blob/DfsRequestListenerFactory.ts | Adds DFS Express pipeline and operation dispatch. |
| src/blob/DfsConfiguration.ts | Adds DFS configuration wrapper based on existing ConfigurationBase. |
| src/blob/BlobServer.ts | Exposes stores publicly to allow DFS to share them. |
| src/blob/BlobEnvironment.ts | Adds blob-service CLI flags for DFS host/port and HNS toggle. |
| src/azurite.ts | Starts DFS server alongside blob/queue/table in the main Azurite entrypoint. |
| package.json | Adds VS Code extension config entries for dfsHost/dfsPort. |
| docs/designs/ADLS-gen2-parity.md | Documents the ADLS Gen2 parity approach, phases, and design rationale. |
| README.md | Documents DFS host/port usage and docker port exposure. |
| Dockerfile.Windows | Exposes DFS port and starts Azurite with dfsHost binding. |
| Dockerfile | Exposes DFS port and starts Azurite with dfsHost binding. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Parse x-ms-properties header (base64 encoded key=value pairs) | ||
| const propertiesHeader = req.headers["x-ms-properties"] as string | undefined; | ||
| if (propertiesHeader) { | ||
| const pairs = propertiesHeader.split(","); | ||
| for (const pair of pairs) { |
| const append1Response = await fetch(append1Url, { | ||
| method: "PATCH", | ||
| headers: { | ||
| "x-ms-version": BLOB_API_VERSION, | ||
| "Content-Type": "application/octet-stream" | ||
| }, | ||
| body: data1 | ||
| }); | ||
| assert.strictEqual(append1Response.status, 202); | ||
|
|
||
| const append2Url = `${dfsBaseUrl}/${fileSystemName}/${fileName}?action=append&position=${Buffer.byteLength(data1)}&${sas}`; | ||
| const append2Response = await fetch(append2Url, { | ||
| method: "PATCH", | ||
| headers: { | ||
| "x-ms-version": BLOB_API_VERSION, | ||
| "Content-Type": "application/octet-stream" | ||
| }, | ||
| body: data2 | ||
| }); | ||
| assert.strictEqual(append2Response.status, 202); | ||
|
|
||
| // Flush | ||
| const totalLength = Buffer.byteLength(data1) + Buffer.byteLength(data2); | ||
| const flushUrl = `${dfsBaseUrl}/${fileSystemName}/${fileName}?action=flush&position=${totalLength}&${sas}`; | ||
| const flushResponse = await fetch(flushUrl, { | ||
| method: "PATCH", | ||
| headers: { "x-ms-version": BLOB_API_VERSION } | ||
| }); | ||
| assert.strictEqual(flushResponse.status, 200); | ||
|
|
||
| // Read back via DFS | ||
| const readUrl = `${dfsBaseUrl}/${fileSystemName}/${fileName}?${sas}`; | ||
| const readResponse = await fetch(readUrl, { | ||
| headers: { "x-ms-version": BLOB_API_VERSION } | ||
| }); | ||
| assert.strictEqual(readResponse.status, 200); | ||
| const readBody = await readResponse.text(); |
src/blob/dfs/handlers/PathHandler.ts
Outdated
| const position = parseInt(req.query.position as string || "0", 10); | ||
|
|
||
| try { |
| let directoriesSuccessful = 0; | ||
| let filesSuccessful = 0; | ||
| const failureCount = 0; | ||
|
|
| where: { | ||
| accountName: account, | ||
| containerName: sourceContainer, | ||
| blobName: { [Op.like]: `${sourcePrefix}%` } |
| if (!pass) { | ||
| // Check if there's no auth header at all — allow for dev/test | ||
| const hasAuth = req.header("authorization") !== undefined; | ||
| const hasSas = req.query.sig !== undefined; | ||
| if (!hasAuth && !hasSas) { |
| "azurite.dfsHost": { | ||
| "type": "string", | ||
| "default": "127.0.0.1", | ||
| "description": "DFS service listening endpoint, by default 127.0.0.1" | ||
| }, |
| res.end(); | ||
| } catch (error: any) { | ||
| if (error.statusCode === 404) { | ||
| return sendDfsError(res, pathNotFound(renameSource)); |
| // Use Sequelize literal for SQL REPLACE to atomically rename all matching blobs | ||
| await BlobsModel.update( | ||
| { | ||
| containerName: destContainer, | ||
| blobName: this.sequelize.literal( | ||
| `REPLACE("blobName", ${this.sequelize.escape(sourcePrefix)}, ${this.sequelize.escape(destPrefix)})` | ||
| ), | ||
| lastModified: now, | ||
| etag | ||
| } as any, | ||
| { | ||
| where: { | ||
| accountName: account, | ||
| containerName: sourceContainer, | ||
| blobName: { [Op.like]: `${sourcePrefix}%` } | ||
| }, | ||
| transaction: t | ||
| } | ||
| ); |
| await this.metadataStore.setBlobMetadata( | ||
| createStorageContext(ctx.requestId), account, filesystem, pathName, | ||
| undefined, metadata | ||
| ); |
Add comprehensive integration tests using the official Azure DataLake SDK
to validate Azurite DFS endpoint compatibility, as required by the wiki
("Pass all language SDK tests").
- Install @azure/storage-file-datalake@^12.29.0 as devDependency
- tests/blob/dfsSDKIntegration.test.ts: 22 tests across 6 categories:
- Filesystem: create/delete, getProperties, listFileSystems
- Directory: create/delete, nested dirs, move (rename)
- File: create, append+flush+read, multi-chunk write, delete, move
- ACL: setAccessControl/getAccessControl, setPermissions
- List paths: recursive and non-recursive listing
- Cross-API: DFS file readable via Blob API and vice versa
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement OAuth with ACL enforcement as specified by the Azurite ADLS Gen2
wiki Phase III ("OAuth: ACL works when user login with AAD account").
- OAuthLevel: Add ACL level (--oauth acl) alongside existing BASIC level.
- ConfigurationBase: Accept "acl" as --oauth parameter value.
- DfsContext: Add IDfsAuthenticatedIdentity interface with oid, upn, tid,
appid fields; add identity field to IDfsContext.
- DfsAuthenticationMiddleware: Extract identity claims (oid, upn, tid,
appid) from Bearer JWT tokens when ACL mode is active; store in DFS
context for downstream enforcement.
- DfsAclEnforcer: New module implementing POSIX ACL evaluation:
- Parses ACL strings ("user::rwx,user:oid:r-x,group::r-x,other::---")
- Evaluates in POSIX order: owner -> named user -> group -> other
- Applies mask entries to limit named user/group permissions
- $superuser and unauthenticated requests bypass checks
- Maps operations to required permissions (r/w/x)
- PathHandler: Add enforceAcl() helper; check ACL before getProperties,
read, delete, and update operations; accept OAuthLevel from factory.
- DfsRequestListenerFactory: Pass OAuth level to PathHandler.
- Tests: 19 unit tests for ACL enforcer covering parsing, bypass scenarios,
owner/named-user/group/other permissions, mask application, and UPN
matching. All passing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
I added new commits to comply to the https://github.com/Azure/Azurite/wiki/ADLS-Gen2-Implementation-Guidance. |
The recursive delete was relying on the HNS hierarchy table to check for children, which missed blobs created via the Blob API or before HNS tracking was added. Now always uses listBlobs prefix scan (no delimiter) to find ALL descendant blobs regardless of how they were created, then deletes them all before removing the directory marker itself. Fixes: DeleteDirectory_WithContents_DeletesAll test failure where directoryClient.DeleteAsync(recursive: true) deleted the directory marker but left child blobs intact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds native ADLS Gen2 (DFS) endpoint support to Azurite by introducing a DFS server pipeline that shares the Blob service’s metadata/extent/account stores, along with HNS directory semantics and initial ACL enforcement.
Changes:
- Introduces a native DFS server (port 10004 by default) with filesystem/path handlers, auth middleware reuse, and DFS-specific context/error handling.
- Extends metadata stores (Loki + SQL) to support rename operations and an HNS hierarchy registry.
- Adds DFS-focused tests (proxy-style REST tests, SDK integration tests, and ACL enforcer unit tests) plus swagger artifacts/docs.
Reviewed changes
Copilot reviewed 45 out of 46 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/blob/dfsSDKIntegration.test.ts | Adds JS SDK integration coverage via @azure/storage-file-datalake. |
| tests/blob/dfsProxy.test.ts | Adds direct DFS REST-level tests and cross-API (Blob↔DFS) checks. |
| tests/blob/dfsAclEnforcer.test.ts | Adds unit tests for ACL parsing/evaluation logic. |
| swagger/dfs.md | Adds AutoRest configuration for DFS server generation. |
| swagger/dfs-storage-2023-11-03.json | Adds DFS swagger spec used for generated artifacts. |
| src/common/models.ts | Extends OAuth level enum with ACL enforcement mode. |
| src/common/VSCEnvironment.ts | Adds VS Code settings for DFS host/port + HNS toggle. |
| src/common/Telemetry.ts | Adds dfsHost/dfsPort telemetry parameter handling. |
| src/common/Environment.ts | Adds CLI flags for dfsHost/dfsPort and HNS enablement. |
| src/common/ConfigurationBase.ts | Parses new --oauth acl option. |
| src/blob/utils/constants.ts | Adds DFS defaults + flips HNS “enabled” constant default. |
| src/blob/persistence/SqlBlobMetadataStore.ts | Adds HNS hierarchy table + rename/HNS operations in SQL store. |
| src/blob/persistence/LokiBlobMetadataStore.ts | Adds rename/HNS operations and HNS collection in Loki store. |
| src/blob/persistence/IBlobMetadataStore.ts | Extends store interface with rename + HNS hierarchy methods. |
| src/blob/main.ts | Starts DFS server alongside blob server in azurite-blob. |
| src/blob/generated-dfs/handlers/handlerMappers.ts | Adds generated DFS handler mapper wiring. |
| src/blob/generated-dfs/handlers/IPathHandler.ts | Adds generated DFS path handler interface. |
| src/blob/generated-dfs/handlers/IHandlers.ts | Adds generated DFS handler registry interface. |
| src/blob/generated-dfs/handlers/IFilesystemHandler.ts | Adds generated DFS filesystem handler interface. |
| src/blob/generated-dfs/artifacts/specifications.ts | Adds operation matching specs for DFS routing. |
| src/blob/generated-dfs/artifacts/operation.ts | Adds generated DFS operation enum. |
| src/blob/generated-dfs/artifacts/models.ts | Adds generated DFS request/response model types. |
| src/blob/generated-dfs/Context.ts | Adds DFS-specific generated context wrapper. |
| src/blob/dfs/handlers/PathHandler.ts | Implements core DFS path operations (create/read/delete/list/update/lease/rename). |
| src/blob/dfs/handlers/FilesystemHandler.ts | Implements DFS filesystem ops (create/delete/properties/list/setProperties). |
| src/blob/dfs/DfsPropertyEncoding.ts | Adds helper for DFS x-ms-properties encoding/decoding. |
| src/blob/dfs/DfsOperation.ts | Adds DFS operation enum for middleware/dispatch. |
| src/blob/dfs/DfsErrorFactory.ts | Adds DFS JSON error writer + common DFS error types. |
| src/blob/dfs/DfsContextFactory.ts | Adds helper to create minimal Blob Context for store calls. |
| src/blob/dfs/DfsContext.ts | Adds DFS context middleware and URL parsing. |
| src/blob/dfs/DfsAuthenticationMiddleware.ts | Reuses blob authenticators for DFS + extracts identity for ACL mode. |
| src/blob/dfs/DfsAclEnforcer.ts | Adds ACL parsing and evaluation logic for DFS operations. |
| src/blob/SqlBlobServer.ts | Exposes stores publicly for DFS server sharing. |
| src/blob/IBlobEnvironment.ts | Extends blob environment interface with DFS host/port + HNS toggle. |
| src/blob/DfsServer.ts | Adds DFS server wrapper around request listener pipeline. |
| src/blob/DfsRequestListenerFactory.ts | Adds Express pipeline for DFS routing/auth/handlers. |
| src/blob/DfsConfiguration.ts | Adds configuration class for DFS endpoint. |
| src/blob/BlobServer.ts | Exposes stores publicly for DFS server sharing. |
| src/blob/BlobEnvironment.ts | Adds CLI flags for DFS host/port and HNS toggle in azurite-blob. |
| src/azurite.ts | Starts DFS server alongside blob/queue/table in the combined entrypoint. |
| package.json | Adds @azure/storage-file-datalake dev dependency and DFS extension settings/script. |
| docs/designs/ADLS-gen2-parity.md | Adds design/phase plan for DFS parity implementation. |
| README.md | Documents DFS host/port config and Docker port exposure. |
| Dockerfile.Windows | Exposes 10004 and passes --dfsHost. |
| Dockerfile | Exposes 10004 and passes --dfsHost. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (result.metadata) { | ||
| for (const [key, value] of Object.entries(result.metadata)) { | ||
| res.setHeader(`x-ms-properties-${key}`, Buffer.from(value).toString("base64")); | ||
| } | ||
| } |
| app.use(express.raw({ type: "*/*", limit: "256mb" })); | ||
|
|
||
| // 1. Parse DFS context (account, filesystem, path) | ||
| app.use(createDfsContextMiddleware()); |
| const docs = coll.find({ | ||
| accountName: account, | ||
| containerName: sourceContainer, | ||
| name: { $regex: new RegExp(`^${this.escapeRegExp(sourcePrefix)}`) } | ||
| }); |
| const append1Response = await fetch(append1Url, { | ||
| method: "PATCH", | ||
| headers: { | ||
| "x-ms-version": BLOB_API_VERSION, | ||
| "Content-Type": "application/octet-stream" |
src/blob/dfs/handlers/PathHandler.ts
Outdated
| const position = parseInt(req.query.position as string || "0", 10); | ||
|
|
| export const EMULATOR_ACCOUNT_ISHIERARCHICALNAMESPACEENABLED_DEFAULT = true; | ||
| // Alias for backward compatibility — existing code imports this name | ||
| export const EMULATOR_ACCOUNT_ISHIERARCHICALNAMESPACEENABLED = | ||
| EMULATOR_ACCOUNT_ISHIERARCHICALNAMESPACEENABLED_DEFAULT; |
| const [allChildren] = await this.metadataStore.listBlobs( | ||
| createStorageContext(ctx.requestId), account, filesystem, | ||
| undefined, undefined, prefix | ||
| ); |
src/blob/dfs/handlers/PathHandler.ts
Outdated
| const position = parseInt(req.query.position as string || "0", 10); | ||
|
|
||
| try { | ||
| const body = Buffer.isBuffer(req.body) ? req.body : Buffer.from(req.body || ""); | ||
|
|
| // Build commit block list from uncommitted blocks | ||
| const commitList = blockList.uncommittedBlocks.map(b => ({ | ||
| blockName: b.name, | ||
| blockCommitType: "Uncommitted" | ||
| })); |
| } catch { | ||
| // Skip failures for individual paths | ||
| } |
Azure ADLS Gen2 returns 409 when creating a directory that already exists. The SDK's CreateIfNotExistsAsync relies on this to return null/false for existing directories. Azurite was returning 201 regardless, causing CreateDirectory_AlreadyExists_ReturnsFalse to fail. Now checks if a directory blob with hdi_isfolder=true already exists before creating, and returns 409 PathAlreadyExists if so. File creates remain idempotent (overwrite) matching Azure behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
I've updated the unit tests on this PR: OrchardCMS/OrchardCore#19014 |
Guard against Express query params and request body being arrays instead of strings. CodeQL flagged req.query.position and req.body as potential vectors for type confusion in appendData and flushData. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@copilot open a new pull request to apply changes based on the comments in this thread |
There was a problem hiding this comment.
Pull request overview
Adds native ADLS Gen2 (DFS) endpoint support to Azurite by introducing a dedicated DFS server/pipeline that shares the existing Blob metadata + extent stores, plus new persistence helpers (rename + HNS hierarchy) and a broad set of DFS-focused tests and swagger artifacts.
Changes:
- Introduce a DFS server (port 10004 by default) with native Express handlers for filesystem/path operations, append/flush, leases, rename, and ACL metadata.
- Extend metadata stores (Loki + SQL) with rename APIs and HNS hierarchy tracking.
- Add DFS integration/proxy/unit tests, swagger inputs/config, and environment/Docker/README wiring for DFS.
Reviewed changes
Copilot reviewed 45 out of 46 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/blob/dfsSDKIntegration.test.ts | New JS SDK integration tests using @azure/storage-file-datalake. |
| tests/blob/dfsProxy.test.ts | New REST-level DFS behavior tests (create/list/append/flush/rename/ACL/leases). |
| tests/blob/dfsAclEnforcer.test.ts | Unit tests for ACL parsing/evaluation. |
| swagger/dfs.md | AutoRest config for DFS codegen. |
| swagger/dfs-storage-2023-11-03.json | DFS swagger used for codegen/artifacts. |
| src/common/models.ts | Add OAuthLevel.ACL enum value. |
| src/common/VSCEnvironment.ts | Add dfsHost/dfsPort + enableHierarchicalNamespace settings. |
| src/common/Telemetry.ts | Include DFS flags in telemetry parameter list. |
| src/common/Environment.ts | Add --dfsHost/--dfsPort and --enableHierarchicalNamespace CLI flags. |
| src/common/ConfigurationBase.ts | Parse --oauth acl into OAuthLevel.ACL. |
| src/blob/utils/constants.ts | Add DFS defaults; switch emulator HNS default to enabled. |
| src/blob/persistence/SqlBlobMetadataStore.ts | Add HNS hierarchy table + rename helpers. |
| src/blob/persistence/LokiBlobMetadataStore.ts | Add HNS hierarchy collection + rename helpers. |
| src/blob/persistence/IBlobMetadataStore.ts | Add interfaces for rename and HNS hierarchy operations. |
| src/blob/main.ts | Start DFS server alongside blob for azurite-blob. |
| src/blob/generated-dfs/** | New generated DFS artifacts/interfaces/specs. |
| src/blob/dfs/** | New DFS middleware, handlers, ACL enforcer, errors, property encoding. |
| src/blob/SqlBlobServer.ts | Expose stores publicly for DFS server sharing. |
| src/blob/IBlobEnvironment.ts | Add DFS + HNS configuration surface. |
| src/blob/DfsServer.ts | New DFS server wrapper over ServerBase. |
| src/blob/DfsRequestListenerFactory.ts | New DFS Express pipeline (context/dispatch/auth/routing/errors). |
| src/blob/DfsConfiguration.ts | New DFS configuration class. |
| src/blob/BlobServer.ts | Expose stores publicly for DFS server sharing. |
| src/blob/BlobEnvironment.ts | Add DFS host/port + HNS flag for azurite-blob. |
| src/azurite.ts | Start DFS server alongside blob/queue/table for azurite. |
| package.json | Add DFS autorest script + @azure/storage-file-datalake dep + VS Code settings for DFS host/port. |
| docs/designs/ADLS-gen2-parity.md | Design/phase plan for ADLS Gen2 parity implementation. |
| README.md | Document DFS host/port + Docker port mapping updates. |
| Dockerfile.Windows | Expose port 10004 and pass --dfsHost. |
| Dockerfile | Expose port 10004 and pass --dfsHost. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const positionParam = Array.isArray(req.query.position) | ||
| ? req.query.position[0] | ||
| : req.query.position; | ||
| const position = parseInt(String(positionParam || "0"), 10); | ||
|
|
||
| try { | ||
| const rawBody = Array.isArray(req.body) ? Buffer.from(req.body) : req.body; | ||
| const body = Buffer.isBuffer(rawBody) ? rawBody : Buffer.from(rawBody || ""); | ||
|
|
||
| // Content-MD5 validation | ||
| const contentMD5 = req.headers["content-md5"] as string | undefined; | ||
| if (contentMD5) { | ||
| const crypto = require("crypto"); | ||
| const computedMD5 = crypto.createHash("md5").update(body).digest("base64"); | ||
| if (computedMD5 !== contentMD5) { | ||
| return sendDfsError(res, { | ||
| statusCode: 400, | ||
| code: "Md5Mismatch", | ||
| message: "The MD5 value specified in the request did not match with the MD5 value calculated by the server." | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| if (body.length === 0) { | ||
| res.status(202); | ||
| res.setHeader("x-ms-request-id", ctx.requestId); | ||
| res.setHeader("x-ms-version", BLOB_API_VERSION); | ||
| return res.end(); | ||
| } | ||
|
|
||
| // Write to extent store | ||
| const extentChunk = await this.extentStore.appendExtent(body); | ||
|
|
||
| // Stage as an uncommitted block (reusing block blob infrastructure) | ||
| const blockId = Buffer.from( | ||
| `dfs-${position.toString().padStart(20, "0")}` | ||
| ).toString("base64"); | ||
|
|
| } catch (error: any) { | ||
| if (error.statusCode === 404) { | ||
| return sendDfsError(res, filesystemNotFound(filesystem)); | ||
| } |
| res.setHeader("x-ms-version", BLOB_API_VERSION); | ||
| res.setHeader("x-ms-resource-type", "filesystem"); | ||
| res.setHeader("x-ms-namespace-enabled", String(this.enableHierarchicalNamespace)); | ||
|
|
||
| if (result.metadata) { | ||
| for (const [key, value] of Object.entries(result.metadata)) { | ||
| res.setHeader(`x-ms-properties-${key}`, Buffer.from(value).toString("base64")); | ||
| } | ||
| } |
| blobServerAny.metadataStore, | ||
| blobServerAny.extentStore, | ||
| blobServerAny.accountDataStore, | ||
| undefined, |
| const blobServerAny = blobServer as any; | ||
| const enableHns = env.enableHierarchicalNamespace(); | ||
| const dfsServer = new DfsServer( | ||
| dfsConfig, | ||
| blobServerAny.metadataStore, | ||
| blobServerAny.extentStore, | ||
| blobServerAny.accountDataStore, |
| "type": "number", | ||
| "default": 10004, | ||
| "description": "DFS service listening port, by default 10004" | ||
| }, |
| // Build commit block list from uncommitted blocks | ||
| const commitList = blockList.uncommittedBlocks.map(b => ({ | ||
| blockName: b.name, | ||
| blockCommitType: "Uncommitted" | ||
| })); | ||
|
|
||
| const now = new Date(); | ||
| const etag = `"${now.getTime().toString(16)}"`; | ||
|
|
||
| const updatedBlob: BlobModel = { | ||
| ...blob, | ||
| properties: { | ||
| ...blob.properties, | ||
| lastModified: now, | ||
| etag, | ||
| contentLength: position, | ||
| contentType: blob.properties.contentType || "application/octet-stream" | ||
| } | ||
| }; |
| if (renameSource) { | ||
| return this.renamePath(req, res); | ||
| } | ||
|
|
||
| try { | ||
| const now = new Date(); | ||
| const metadata: { [key: string]: string } = {}; | ||
| if (isDirectory) { | ||
| metadata[HNS_DIRECTORY_METADATA_KEY] = "true"; | ||
| } | ||
|
|
| // Parse raw body for append operations | ||
| app.use(express.raw({ type: "*/*", limit: "256mb" })); | ||
|
|
| // Use Sequelize literal for SQL REPLACE to atomically rename all matching blobs | ||
| await BlobsModel.update( | ||
| { | ||
| containerName: destContainer, | ||
| blobName: this.sequelize.literal( | ||
| `REPLACE("blobName", ${this.sequelize.escape(sourcePrefix)}, ${this.sequelize.escape(destPrefix)})` | ||
| ), | ||
| lastModified: now, |
|
@copilot open a new pull request to apply changes based on the comments in this thread |
|
I'm sorry, I probably don't have the permission to run agents on this repository. |
Adds ADSL Gen 2 feature parity to Azurite. See docs/design/ADLS-gen2-parity.md file for details.
This was created by Claude Sonnet 4.6. Just sharing my progress here.
See OrchardCMS/OrchardCore#19014 for some integration tests.
The Github CI over there uses the current latest Docker image but I've succesfully ran my unit tests using a local Docker container based on this PR.
Maybe we need to create more unit tests. I let you guys decide, but it is looking good for a start.