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

[BUG] Error: ENOENT: no such file or directory, open '/var/bin/chromium.br' #24

Closed
MarceloEmmerich opened this issue Dec 1, 2022 · 26 comments

Comments

@MarceloEmmerich
Copy link

Environment

  • chromium Version: 108
  • puppeteer-core Version: 19.3.0
  • Node.js Version: 14.x and 16.x
  • Lambda nodejs14.x and nodejs16.x -->

Expected Behavior

adding the layer and launching the browser works

Current Behavior

2022-12-01T20:25:16.992Z	83285b39-c65f-4d14-99ab-cbecd1546d68	ERROR	[Error: ENOENT: no such file or directory, open '/var/bin/chromium.br'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: '/var/bin/chromium.br'
}

Steps to Reproduce

  • build the zip file lie described here: https://github.com/Sparticuz/chromium#aws-lambda-layer
  • double-checked the ZIP is good (it is, contains "nodejs/node_modules"
  • project has puppeteer-core as prod dep
  • project has @sparticuz/chromium as dev dep
  • deployment with CDK
  • lambda code:
exports.handler = async (event: any) =>  {

  console.log(event);

  try {
    const browser = await puppeteer.launch({
        args: chromium.args,
        defaultViewport: chromium.defaultViewport,
        executablePath: await chromium.executablePath,
        headless: chromium.headless,
        ignoreHTTPSErrors: true,
    });
    return browser;
  } catch (err) {
    console.error(err);
  }
  return ''
}

-->

Possible Solution

@MarceloEmmerich MarceloEmmerich added the bug Something isn't working label Dec 1, 2022
@MarceloEmmerich
Copy link
Author

Well, found the problem after digging some more. What happens is that the layer places files inside the lambda at /opt/nodejs/node_modules/, so for example the chromium.br ends up in /opt/nodejs/node_modules/@sparticuz/chromium/bin/chromium.br.

Now the code for the function executablePath searches elsewhere:

static get executablePath(): Promise<string> {
    if (existsSync('/tmp/chromium') === true) {
      for (const file of readdirSync('/tmp')) {
        if (file.startsWith('core.chromium') === true) {
          unlinkSync(`/tmp/${file}`);
        }
      }

      return Promise.resolve('/tmp/chromium');
    }

    const input = join(__dirname, '..', 'bin');
    const promises = [
      LambdaFS.inflate(`${input}/chromium.br`),
      LambdaFS.inflate(`${input}/swiftshader.tar.br`),
    ];

    if (/^AWS_Lambda_nodejs(?:10|12|14|16|18)[.]x$/.test(process.env.AWS_EXECUTION_ENV) === true) {
      promises.push(LambdaFS.inflate(`${input}/aws.tar.br`));
    }

    return Promise.all(promises).then((result) => result.shift());
  }

The problem is this:

const input = join(__dirname, '..', 'bin');

__dirname for some lambda deployments is /var/task/, so the above line yields /var/bin/chromium.br, which does not exist, at least not for all types of lambda deployments.

Quick Fix proposal: refactor executablePath like this:

static get executablePath(): Promise<string> {
    if (existsSync('/tmp/chromium') === true) {
      for (const file of readdirSync('/tmp')) {
        if (file.startsWith('core.chromium') === true) {
          unlinkSync(`/tmp/${file}`);
        }
      }

      return Promise.resolve('/tmp/chromium');
    }

    let input = '';
    __dirname === '/var/task' ? input = '/opt/nodejs/node_modules/@sparticuz/chromium/bin' : input = (0, path_1.join)(__dirname, "..", "bin");
    const promises = [
      LambdaFS.inflate(`${input}/chromium.br`),
      LambdaFS.inflate(`${input}/swiftshader.tar.br`),
    ];

    if (/^AWS_Lambda_nodejs(?:10|12|14|16|18)[.]x$/.test(process.env.AWS_EXECUTION_ENV) === true) {
      promises.push(LambdaFS.inflate(`${input}/aws.tar.br`));
    }

    return Promise.all(promises).then((result) => result.shift());
  }

@nguyentoanit
Copy link

Same issue here:

Node.js version: 16.6.0

package.json:

{
  "dependencies": {
    "puppeteer-core": "19.3.0",
  },
  "devDependencies": {
    "@sparticuz/chromium": "108.0.3",
  }
}

And a lambda layer.

Error message:

{
  "type": "Error",
  "message": "ENOENT: no such file or directory, open '/var/bin/chromium.br'",
  "stack": "Error: ENOENT: no such file or directory, open '/var/bin/chromium.br'",
  "errno": -2,
  "code": "ENOENT",
  "syscall": "open",
  "path": "/var/bin/chromium.br"
}

Lambda code:

import chromium from '@sparticuz/chromium'
import puppeteer from 'puppeteer-core'

puppeteer.launch({
  executablePath: await chromium.executablePath,
  args: chromium.args,
  defaultViewport: chromium.defaultViewport,
  headless: chromium.headless,
  ignoreHTTPSErrors: true,
})

@avegao
Copy link

avegao commented Dec 7, 2022

Another one here

@avegao
Copy link

avegao commented Dec 7, 2022

With the new version v109.0.0 still failing

@jdahdah
Copy link

jdahdah commented Dec 8, 2022

Is there any way to work around this? Getting this error in a Netlify function and totally blocked right now.

@AdScoops
Copy link

AdScoops commented Dec 8, 2022

I had this issue at one point when packaging my lambda with webpack.

my solution: use webpack externals to mark "@sparticuz/chromium" as an external dependency. Without this webpack will still package this code meaning that the directory resolution was having issues.

@avegao
Copy link

avegao commented Dec 8, 2022

Is there any way to work around this? Getting this error in a Netlify function and totally blocked right now.

@jdahdah I only can copy the code provided by @MarceloEmmerich to replace the function executablePath() and remember to copy the class LambdaFS. With this work around (ñapa as we said in Spanish) I can run this package.

@MisterJimson
Copy link

I get path_1 is not defined when I try to use @MarceloEmmerich's replacement code

@Sparticuz Sparticuz removed the bug Something isn't working label Dec 9, 2022
@Sparticuz
Copy link
Owner

#18 will fix this, help wanted

@MarceloEmmerich
Copy link
Author

Happy to help on #18, however I think there is a difference in the use case. #18 reads as we should be able to explicitly pass the location of the brotli files, in other words, a scenario where we know exactly where these reside. This should not be the standard case IMHO. The standard case should remain as it is today, it just needs to check one more "default" location to automatically work with more deployment mechanisms. Having known alternative locations is of course a cool feature to have for the reasons mentioned in #18 .

@Sparticuz
Copy link
Owner

Agree, I'm not sure the performance implications of having multiple locations to check though. Testing would need to be done to insure the already pretty slow chromium doesn't get even slower because of that.

@FreddieFruitSticks
Copy link

FreddieFruitSticks commented Dec 20, 2022

Question, how does this work with containerised Typescript lambdas? https://docs.aws.amazon.com/lambda/latest/dg/typescript-image.html. I am noticing that I only get this error when I use the example in to aws doc. When I look inside the docker container I find no chromium.br file.

This is the aws doc reference example - has anyone gotten this to work with chromium?

@MarceloEmmerich
Copy link
Author

MarceloEmmerich commented Dec 20, 2022

Question, how does this work with containerised Typescript lambdas? https://docs.aws.amazon.com/lambda/latest/dg/typescript-image.html. I am noticing that I only get this error when I use the example in to aws doc. When I look inside the docker container I find no chromium.br file.

This is the aws doc reference example - has anyone gotten this to work with chromium?

can you post your package.json? Are you including puppeteer-core and @sparticuz/chromium as dependency or dev-dependency? Or are you using a layer?

@FreddieFruitSticks
Copy link

FreddieFruitSticks commented Dec 20, 2022

Thanks for the response. I am using both puppeteer-core and puppeteer (just testing the differences at the moment - will use pupeeteer-core)

{
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "compile": "esbuild index.ts --bundle --minify --sourcemap --platform=node --target=es2020 --outfile=./dist/index.js"
  },
...
  "dependencies": {
    "@aws-sdk/client-s3": "^3.231.0",
    "@types/fs-extra": "^9.0.13",
    "fs-extra": "^11.1.0",
    "puppeteer": "^19.2.2",
    "puppeteer-core": "^19.2.2"
  },
  "devDependencies": {
    "@sparticuz/chromium": "^109.0.0",
    "@types/aws-lambda": "^8.10.109",
    "esbuild": "^0.16.9"
  }
}

Might it have something to do with the Dockerfile that those docs prescribe - The resulting container seems to have only the minified js files.

@MarceloEmmerich
Copy link
Author

Not very familiar with Docker deployments, but I would guess that it only bundles the dependencies, and not the devDependencies. Since @sparticuz/chromium is in the devDependencies section, it would not be included in the docker image. In this case, you could try putting @sparticuz/chromium in the dependencies and see if the binaries get included in the image. Or use a layer to hold your binaries in a reusable manner.

@anthonyLock
Copy link

I am also having exactly the same problem. Making it unusable in the lambda. Does anybody know if it works if you go to a earlier version?

@MarceloEmmerich
Copy link
Author

if you get the above error and you are using the layer approach, try replacing chromium.executablePath with this:

function executablePath(): Promise<string> {

    const input = '/opt/nodejs/node_modules/@sparticuz/chromium/bin';
    const promises = [
      LambdaFS.inflate(`${input}/chromium.br`),
      LambdaFS.inflate(`${input}/swiftshader.tar.br`),
      LambdaFS.inflate(`${input}/aws.tar.br`),
    ];

    return Promise.all(promises).then((result) => result.shift());
  }

and then whan you launch puppeteer you use this function and not the default one:

const browser = await puppeteer.launch({
          args: chromium.args,
          defaultViewport: chromium.defaultViewport,
          executablePath: await executablePath(),
          headless: chromium.headless,
          ignoreHTTPSErrors: true,
      });

The actual executablePath function does a bunch of other stuff, so use this at your own risk, it is a hacky workaround.

@EmanuelCampos
Copy link

if you get the above error and you are using the layer approach, try replacing chromium.executablePath with this:

function executablePath(): Promise<string> {

    const input = '/opt/nodejs/node_modules/@sparticuz/chromium/bin';
    const promises = [
      LambdaFS.inflate(`${input}/chromium.br`),
      LambdaFS.inflate(`${input}/swiftshader.tar.br`),
      LambdaFS.inflate(`${input}/aws.tar.br`),
    ];

    return Promise.all(promises).then((result) => result.shift());
  }

and then whan you launch puppeteer you use this function and not the default one:

const browser = await puppeteer.launch({
          args: chromium.args,
          defaultViewport: chromium.defaultViewport,
          executablePath: await executablePath(),
          headless: chromium.headless,
          ignoreHTTPSErrors: true,
      });

The actual executablePath function does a bunch of other stuff, so use this at your own risk, it is a hacky workaround.

This solution works well for me, thank you

@Sparticuz
Copy link
Owner

I've published v109.0.1 which supports an input variable in exectuablePath().

To fix the OP, you can use executablePath: await chromium.executablePath("/var/bin")
Others can use the bin folder location they need.

@carlosdp
Copy link

carlosdp commented Feb 2, 2023

For anyone that comes here trying to use this with Netlify Functions, you have to include this dependency as an external, and then it works out of the box. Otherwise, their build system tries to be smart and inline everything:

# netlify.toml
[functions]
external_node_modules = ["@sparticuz/chromium"]

@Nicoowr
Copy link

Nicoowr commented Mar 2, 2023

I had this issue at one point when packaging my lambda with webpack.

my solution: use webpack externals to mark "@sparticuz/chromium" as an external dependency. Without this webpack will still package this code meaning that the directory resolution was having issues.

I had a similar problem with the folder var/task/bin. Using webpack externals solved it, thanks @AdScoops

@walker-tx
Copy link

Thank you @carlosdp - that was very helpful for me.

I also wanted to follow-up and share that if you're using pnpm, you'll want to include a environment variable on your site called PNPM_FLAGS that has a value of --shamefully-hoist. Netlify recommended it for NextJS in this article, and it seems to have solved my package issue as well.

@aarongustafson
Copy link

For anyone that comes here trying to use this with Netlify Functions, you have to include this dependency as an external, and then it works out of the box. Otherwise, their build system tries to be smart and inline everything:

# netlify.toml
[functions]
external_node_modules = ["@sparticuz/chromium"]

Do you have an example project that shows how you rigged this up?

@carlosdp
Copy link

For anyone that comes here trying to use this with Netlify Functions, you have to include this dependency as an external, and then it works out of the box. Otherwise, their build system tries to be smart and inline everything:

# netlify.toml
[functions]
external_node_modules = ["@sparticuz/chromium"]

Do you have an example project that shows how you rigged this up?

Not a public one, no. It's pretty simple though, just adding that line to the netlify.toml should make it work

@aarongustafson
Copy link

Not a public one, no. It's pretty simple though, just adding that line to the netlify.toml should make it work

Thanks! Was trying to update 11ty/api-screenshot to get it working for me on Netlify. Need to dig a little deeper into the screenshotting function I think.

+@zachleat

@ham-evans
Copy link

If using next.js: #147

think it can be configured with webpack regardless

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

No branches or pull requests