Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Azure RemoteCache: Error: File download response doesn't contain valid content length header #30018

Open
1 of 4 tasks
mmslakr opened this issue Feb 13, 2025 · 12 comments
Open
1 of 4 tasks
Assignees

Comments

@mmslakr
Copy link

mmslakr commented Feb 13, 2025

Current Behavior

I'm trying to setup the Azure blob storage based Powerpack RemoteCache and ran into some problems:

$ cat nx.json | jq '.azure'
{
  "accountName": "<snip>",
  "container": "cache",
  "localMode": "read-write",
  "ciMode": "read-write"
}

$ export AZURE_STORAGE_CONNECTION_STRING="BlobEndpoint=https://<snip>.blob.core.windows.net/;SharedAccessSignature=sv=2022-11-02&ss=bfqt&srt=sco&sp=rwdlacupiytfx&se=2025-02-13T16:28:59Z&st=2025-02-13T08:28:59Z&spr=https&sig=r6%<snip>3D"

$ nx add @nx/powerpack-azure-cache

✔ Installing @nx/powerpack-azure-cache@latest...
✖ Initializing @nx/powerpack-azure-cache...


 NX   Failed to initialize @nx/powerpack-azure-cache

  - You must provide either an account name or a connection string
    Error: You must provide either an account name or a connection string
        at /workspaces/plugins/node_modules/@nx/powerpack-azure-cache/generators/init/generator.js:1:3690
        at d (/workspaces/plugins/node_modules/@nx/powerpack-azure-cache/generators/init/generator.js:1:3969)
        at async installPlugin (/workspaces/plugins/node_modules/nx/src/command-line/init/configure-plugins.js:50:18)
        at async initializePlugin (/workspaces/plugins/node_modules/nx/src/command-line/add/add.js:102:9)
        at async /workspaces/plugins/node_modules/nx/src/command-line/add/add.js:27:9
        at async handleErrors (/workspaces/plugins/node_modules/nx/src/utils/handle-errors.js:8:24)
        at async Object.handler (/workspaces/plugins/node_modules/nx/src/command-line/add/command-object.js:25:22)

Initializing @nx/powerpack-azure-cache... Is failing. I'm not sure why. I've gone ahead:

$ nx test emulator --verbose

 NX   Successfully ran target test for project emulator

 NX   File download response doesn't contain valid content length header

RangeError: File download response doesn't contain valid content length header
    at /workspaces/plugins/node_modules/@nx/powerpack-azure-cache/node_modules/@azure/storage-blob/dist/index.js:19636:23
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async Object.withSpan (/workspaces/plugins/node_modules/@nx/powerpack-azure-cache/node_modules/@azure/storage-blob/node_modules/@azure/core-tracing/dist/commonjs/tracingClient.js:36:28)
    at async T.retrieve (/workspaces/plugins/node_modules/@nx/powerpack-azure-cache/index.js:1:11547)
    at async DbCache.get (/workspaces/plugins/node_modules/nx/src/tasks-runner/cache.js:92:25)
    at async TaskOrchestrator.applyCachedResult (/workspaces/plugins/node_modules/nx/src/tasks-runner/task-orchestrator.js:130:30)
    at async Promise.all (index 0)
    at async TaskOrchestrator.applyCachedResults (/workspaces/plugins/node_modules/nx/src/tasks-runner/task-orchestrator.js:126:21)
    at async TaskOrchestrator.applyFromCacheOrRunTask (/workspaces/plugins/node_modules/nx/src/tasks-runner/task-orchestrator.js:217:40)
    at async TaskOrchestrator.executeNextBatchOfTasksUsingTaskSchedule (/workspaces/plugins/node_modules/nx/src/tasks-runner/task-orchestrator.js:85:13)

The first stack line points to this @azure/storage-blob code:

            console.log(this);
            console.log(res);
            if (res.contentLength === undefined) {
->              throw new RangeError(`File download response doesn't contain valid content length header`);
            }

I added the 2 console.log and the first indicated that the credentials from AZURE_STORAGE_CONNECTION_STRING are used as expected. The second console log:

{
  body: {
    Code: 'BlobNotFound',
    Message: 'The specified blob does not exist.\n' +
      'RequestId:ed013098-901e-0034-41f3-7d976c000000\n' +
      'Time:2025-02-13T08:44:25.6102225Z'
  }
}

The body is expected as well as I'm trying to make it work the container is still empty. As the @nx/powerpack-azure-cache code is highly obfuscated I couldn't check but it seems it didn't handle this case correctly.

I wonder if I did the setup wrong because that's clearly not an edge case. Is that just an follow-up problem of the failed initialization?

Expected Behavior

$ nx test emulator

Should detect the blob does not yet exist, run the task and upload a new blob.

GitHub Repo

No response

Steps to Reproduce

Already metioned above.

Nx Report

Node           : 20.9.0
OS             : linux-arm64
Native Target  : aarch64-linux
yarn           : 4.6.0

nx                     : 20.4.2
@nx/js                 : 20.4.2
@nx/jest               : 20.4.2
@nx/eslint             : 20.4.2
@nx/workspace          : 20.4.2
@nx/angular            : 20.4.2
@nx/cypress            : 20.4.2
@nx/devkit             : 20.4.2
@nx/eslint-plugin      : 20.4.2
@nx/express            : 20.4.2
@nx/module-federation  : 20.4.2
@nx/nest               : 20.4.2
@nx/node               : 20.4.2
@nx/playwright         : 20.4.2
@nx/plugin             : 20.4.2
@nx/storybook          : 20.4.2
@nx/vite               : 20.4.2
@nx/web                : 20.4.2
@nx/webpack            : 20.4.2
typescript             : 5.7.3

---------------------------------------
Nx Powerpack
Licensed to <snip> for 5 users in an unlimited number of workspaces until 3/14/2025

@nx/powerpack-azure-cache  : 1.2.4

---------------------------------------
Community plugins:
@fortawesome/angular-fontawesome : 1.0.0
@ng-bootstrap/ng-bootstrap       : 18.0.0
@ngxs/store                      : 19.0.0
@storybook/angular               : 8.4.7
@testing-library/angular         : 17.3.5
angular-eslint                   : 19.0.2
ng-mocks                         : 14.13.2
---------------------------------------
Local workspace plugins:
         @plugins/workspace-plugin

Failure Logs

Package Manager Version

No response

Operating System

  • macOS
  • Linux
  • Windows
  • Other (Please specify)

Additional Information

No response

@Cammisuli Cammisuli self-assigned this Feb 13, 2025
@Cammisuli
Copy link
Member

Cammisuli commented Feb 13, 2025

Oh no, thanks for the report! I'll investigate this right away.

For your first issue with nx add @nx/powerpack-azure-cache, that seems to be related to a regression in the nx cli itself. We're looking into fixing that.

I'm also going to change the functionality to check for the existence of AZURE_CONNECTION_STRING when doing init so that we don't ask for the connection string.

You can run nx g @nx/powerpack-azure-cache:init to get the same prompts that would've appeared when doing nx add.

As for your second issue, I'm having a hard time reproducing that, so maybe it is related to the failed initialization. But the config for azure shouldn't have the accountName if you're using the AZURE_STORAGE_CONNECTION_STRING env variable. Try removing that and try again.

@mmslakr
Copy link
Author

mmslakr commented Feb 13, 2025

Thx for investigating that this fast, i appreciate!

I can confirm running nx g @nx/powerpack-azure-cache:init revealed some prompts:

$ nx g @nx/powerpack-azure-cache:init

 NX  Generating @nx/powerpack-azure-cache:init

✔ What is the name of the Azure Blob Storage container to store the Nx Cache in? · cache
✔ Select the cache mode for local environments: read, read-write, or no-cache. · read-write
✔ How would you like to connect to Azure with?: · connectionString
✔ Enter your Azure connection string: · BlobEndpoint=https://<snip>.blob.core.windows.net/;SharedAccessSignature=sv=2022-11-02&ss=bfqt&srt=sco&sp=rwdlacupiytfx&se=2025-02-13T16:28:59Z&st=2025-02-13T08:28:59Z&spr=https&sig=r6%<snip>3D

I saw that basically every answer given will be persisted into azure block inside nx.json. I don't like to put credentials like this into a file that is committed to version control but that's fine for testing and hopefully resolving the problem I get here.

But sadly the error stays the same.

As you can see I'm using a connection string with a SAS instead of account name and key. But I also tested it with an account name and key based connection string inside nx.json and in the environment variable. It's always the same error.

Is there anything I can provide?

@Cammisuli
Copy link
Member

@mmslakr can you share the settings you have setup for the SAS token?

Image

@mmslakr
Copy link
Author

mmslakr commented Feb 13, 2025

For now i just ckecked all the checkbox to avoid introducing permission problems:

Image

@mmslakr
Copy link
Author

mmslakr commented Feb 13, 2025

But we maybe don't need to focus to much on SAS. It's problem with an accountKey-based connection string as well. I just discovered that the second console.log (the download blob response) is different if I use the accountKey-based connection string:

$ export AZURE_STORAGE_CONNECTION_STRING=

$ cat nx.json | jq '.azure'
{
  "container": "cache",
  "localMode": "read-write",
  "ciMode": "read-write",
  "connectionString": "DefaultEndpointsProtocol=https;AccountName=<accountName>;AccountKey=NG4<snip>vw==;EndpointSuffix=core.windows.net"
}

$ nx t emulator

RangeError: File download response doesn't contain valid content length header

The response body:

{
  body: {
    Code: 'AuthenticationFailed',
    Message: 'Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.\n' +
      'RequestId:c39564e9-501e-0014-3a4f-7eeccb000000\n' +
      'Time:2025-02-13T19:45:57.6367453Z',
    AuthenticationErrorDetail: "The MAC signature found in the HTTP request '9ASId2Xe6lgyIYfyWIlhlJOtzyMvxFRqinlHQ/n11W0=' is not the same as any computed signature. Server used following string to sign: 'GET\n" +
      '\n' +
      '\n' +
      '\n' +
      'x-ms-client-request-id:382a9c3f-bb3c-46ea-9c9f-15c8ca4a6293\n' +
      'x-ms-date:Thu, 13 Feb 2025 19:45:57 GMT\n' +
      "/<accountName>/cache/838662881377633678'."
  }
}

That's clearly an authentication problem (even a strange one) whereas the SAS-based connection string told us that the blob does not exist.

@Cammisuli
Copy link
Member

Cammisuli commented Feb 13, 2025

That is a very weird error for sure. I know we should handle that error, so the run wouldn't fail if we get it. But we'd see a warning if we try to write to the remote cache if we cant authenticate. I'll do a quick search to see what that error means.

And FYI, you can do NX_VERBOSE_LOGGING=true to get more details from the cache plugin, in this case, it might be the same error you already console.log.

@Cammisuli
Copy link
Member

From what i found regarding the error.. is to try and make sure that the account name or account key are properly URL encoded. So if there are any / \ those should be encoded.

@kraiz
Copy link

kraiz commented Feb 13, 2025

Thx for hinting NX_VERBOSE_LOGGING=true!

I copied the connection string from the "Access Keys" Azure portal page. I remember it contained some % chars that indicate encoding. But I'll check that tomorrow as first.

@mmslakr
Copy link
Author

mmslakr commented Feb 14, 2025

Sorry, just now saw that i used my private account @kraiz for the last comment. So it's me as well. Nevermind.

NX_VERBOSE_LOGGING=true does not give me more details than adding --verbose.

And the account key does not contain %. There are just 2 + and padded with =. It looks like base64 encoding and i can sucdessfully decode it, so it should be fine.

I also rotated the account key multiple times and the error stays the same.

@Cammisuli
Copy link
Member

Here's a quick test script to see if you're able to download properly. You can put this in the same workspace that has the azure cache plugin, as it will have the right dependencies. Change the consts at the top to your specific use case, then you can run it with node:

//@ts-check
const { BlobServiceClient } = require("@azure/storage-blob");
const fs = require("fs");

const CONNECTION_STRING =
  process.env.AZURE_STORAGE_CONNECTION_STRING ??
  "DefaultEndpointsProtocol=https;AccountName=ACCOUNT_NAME;AccountKey=ACCOUNT_KEY;EndpointSuffix=core.windows.net";
const CONTAINER_NAME = "nxcache";
const BLOB_NAME = "test";

async function main() {
  const blobServiceClient =
    BlobServiceClient.fromConnectionString(CONNECTION_STRING);
  const containerClient = blobServiceClient.getContainerClient(CONTAINER_NAME);
  const blobClient = containerClient.getBlockBlobClient(BLOB_NAME);

  const downloadResponse = await blobClient.download();
  const outputFilePath = "downloaded_test";

  await new Promise((resolve, reject) => {
    const writeStream = fs.createWriteStream(outputFilePath);
    downloadResponse.readableStreamBody?.pipe(writeStream);
    writeStream.on("finish", resolve);
    writeStream.on("error", reject);
  });

  console.log(`File downloaded to ${outputFilePath}`);
}

main()
  .catch(console.error)
  .finally(() => process.exit(0));

Do you still get authentication errors with this script?

@mmslakr
Copy link
Author

mmslakr commented Feb 14, 2025

Your script delivers a perfectly valid 404 "BlobNotFound" (for both accountKey and SAS based connection string).

I changed the storage-blob import to:

const { BlobServiceClient } = require("@nx/powerpack-azure-cache/node_modules/@azure/storage-blob");

and boom, this resulted in:

RangeError: File download response doesn't contain valid content length header

So I have different versions:

$ yarn why @azure/storage-blob
├─ @nx/powerpack-azure-cache@npm:1.2.4
│  └─ @azure/storage-blob@npm:12.25.0 (via npm:12.25.0)
│
└─ @plugins/plugins@workspace:.
   └─ @azure/storage-blob@npm:12.18.0 (via npm:12.18.0)

12.18.0 works fine but 12.25.0 does not, so I forced 12.18.0 and it just works as as expected.

// package.json
{
   "resolutions": {
    "@nx/powerpack-azure-cache/@azure/storage-blob": "12.18.0"
  }
}

Seem like i need to find out what's wrong with the latest @azure/storage-blob for me.
I thank you for investigating and spending the time :)

@Cammisuli
Copy link
Member

Hmm interesting. I'll see if I can reproduce that and get the same error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants