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

chore: Download browsers for all platforms to release builds from single machine #165

Merged
merged 20 commits into from
Apr 6, 2022

Conversation

kyungeunni
Copy link
Contributor

@kyungeunni kyungeunni commented Feb 24, 2022

Summary

fixes #150
fixes #172 (fixed by 891b9f6)

This PR adds an additional pre-build step. When running npm run release script, the pre-build script will download browsers for Mac Windows, and Linux (if the browsers are already downloaded, it will skip downloading steps. The path contains the revision number so it also prevents packaging the stale browser.). Then electron builder will include the right browser for each platform as specified in electron-builder.yml.

Implementation details

I couldn't utilize the playwright's registry library, as they are hardly coupled with the host machine's OS. Instead, I've used fetchBrowser library that is used by the registry's install function.

How to validate this change

  1. runnpm run pack -- -mwl. It will digest builds in your local dist directory without publishing it.
  2. try run each of the binaries in each platform.
  • Mac
  • Windows
  • Linux (.deb for the time being)

Next Steps

  • Release the binaries as described in RELEASE.md
  • Stretch it to include Apple Silicon build
  • Run the release script in CI on tags push.

@kyungeunni kyungeunni changed the title chore: Download browsers for all platforms to release from single machine chore: Download browsers for all platforms to release builds from single machine Feb 24, 2022
@apmmachine
Copy link
Contributor

apmmachine commented Feb 24, 2022

💚 Build Succeeded

the below badges are clickable and redirect to their specific view in the CI or DOCS
Pipeline View Test View Changes Artifacts preview preview

Expand to view the summary

Build stats

  • Start Time: 2022-04-06T14:13:04.220+0000

  • Duration: 14 min 55 sec

🤖 GitHub comments

To re-run your PR in the CI, just comment with:

  • /test : Re-trigger the build.

@justinkambic
Copy link
Contributor

justinkambic commented Feb 24, 2022

Thanks for taking this on, it represents a huge accomplishment.

In my process of smoke testing binaries I built with this branch, I encountered a problem running tests on my recorded steps for Linux and Windows.

image

This problem doesn't happen on the Mac build, so I think we are probably seeing the same issue here as we had with the browsers. Sharp isn't getting copied over for non-native builds, and thus it seems to be causing the test feature to fail.

Copy link
Member

@vigneshshanmugam vigneshshanmugam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work @kyungeunni 🎉

@@ -0,0 +1 @@
BROWSER_REVISION=907428
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can automate this with ENV variable. We need to get it from

const SYNTHETICS_BROWSER_REVISIONS = require("@elastic/synthetics/node_modules/playwright-chromium/browsers.json");

const { getChromeVersion } = require("./install-pw");

const SUPPORTED_PLATFORMS = ["linux", "mac", "win"];
const EXECUTABLE_PATHS = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need ffmpeg as well if i remember correctly for all platforms. Please double check if thats not the case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for reminding me this!! I've updated the script to also download ffmpeg too.

}
}
// eslint-disable-next-line no-console
downloadAll().then(() => console.log("Download succeeded"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move them inside this check if we want to execute only when running as main module and not while importing -

if (require.main === module) {

package.json Outdated
"release": "npm run pack -- -mwl --publish=always",
"pack": "run-s prepack electron-builder",
"prepack": "run-p build prepack:browsers",
"prepack:browsers": "node scripts/download-browsers",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @justinkambic experienced, this would still break for native deps, We can fix it by running the preparation and package script serially for all platforms and uploading the pack one by one.

// 'mac-arm64': '%s/builds/chromium/%s/chromium-mac-arm64.zip',
};

async function download(platform, revision, directory) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try to check the Registry in Playwright if there was any other way to do this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I went through all the Registry in Playwright and it seemed to be no work way around in there, especially I couldn't mock the platform as they would check the host machine's platform in various steps. So I used one of the functions that Registry uses to download the browser which is not tied to the host machine's platform(you pass it via parameter)

@kyungeunni kyungeunni force-pushed the 150-poc-browser-per-platform branch 3 times, most recently from c33b1d4 to 2eed943 Compare March 18, 2022 11:00
@kyungeunni
Copy link
Contributor Author

kyungeunni commented Mar 18, 2022

This PR is ready for re-review. What I've fixed since the last review:

  • Download ffmpeg as well as chromium before packing. Previously I've downloaded all the browsers for all the platform prior to packing, but I've updated it to download browsers per the requested platform. (e.g. when building binary for windows, download only for windows).
  • Fixed native module download issue, namely sharp which is used by @elastic/synthetics. electron-builder is supposed to handle this but it is incompatible with how sharp's install script works. more details are described here.

_The latest build works well on Mac and Windows. When running it on Linux(Ubuntu 18.04) it had an issue launching the playback(test) but that is out of the scope as it doesn't seem to be related to the build. (I will create another ticket regarding that shortly).

As the binary for Linux doesn't work at the moment, I excluded Linux from the build configs, we will re-enable it once we make it work on Linux._
Update: @emilioalvap's suggestion to pass over parent's process.env to the child process actually fixed the issue. so including linux build, too 👍

cc @justinkambic @vigneshshanmugam

@justinkambic
Copy link
Contributor

Fantastic update @kyungeunni and thank you for the advice @emilioalvap ❤️

I will review as soon as possible.

@kyungeunni
Copy link
Contributor Author

Hey @lucasfcosta 👋 @justinkambic mentioned that it would be great if you could test running the build script from your machine and see if it works well just to make sure it is not customed to work only on my machine. When you have a minute, could you try running npm run pack -- -mwl and see all the builds are created under /dist without an error please? 🙏 I'm not sure

@justinkambic
Copy link
Contributor

@kyungeunni I'm still seeing an issue on Linux unfortunately 😞

image

@vigneshshanmugam
Copy link
Member

Context - .synthetics folder gets created by Synthetics library to put the screenshots and other cached artifacts to be constructed later when the journeys are completed. We need to add permission to be able to create that folder across all OS.

@kyungeunni
Copy link
Contributor Author

Thanks, @vigneshshanmugam !! I'm wondering if it was a permission issue, shouldn't the error code be EACCES: permission denied?

Copy link
Contributor

@lucasfcosta lucasfcosta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this @kyungeunni!

My only comments were around env vars and small nits.

Btw, I did build the runner on my Mac and ran the built recorder. I was able to record a multi-step journey and replay it just fine :)

@@ -245,6 +245,7 @@ async function onTest(data, browserWindow) {
*/
synthCliProcess = fork(`${SYNTHETICS_CLI}`, args, {
env: {
...process.env,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we're invoking our own cli here, but I'd usually be a bit concerned with passing the entire env to it because of the secrets it may contain. Can we detect which parts of process.env do we need and pass only those?

Not a big deal, but just thought I'd add the comment for us to discuss.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something I'm uncertain about as I'm not accustomed to the security implications of changes to CLI interaction. From speaking offline it seems like this is not a large risk, but I'd like to hear others' opinions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @emilioalvap mentioned in another comment, I think there will be various types of env values as well as platform specific ones, so it can be a bit tricky to track all of them. We should measure the effort vs worth by doing so (if we decide to do it).

Another note, when env option is not specified when spawning, Node assigns parent's process.env as a default

stdio: "inherit",
shell: true,
env: {
...process.env,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too, I'm not sure if this is a big deal, but I'd imagine that if anyone pushes a malicious update to libvips or any other library we use in the fix-sharp command, they could have an environment variable containing our secrets used for signing the app or other credentials.

Maybe I'm being paranoid or there are mechanisms to prevent this already, but I'd appreciate more thoughts from others here.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lucasfcosta Good point. I'm not sure we would like to follow an allow list approach here, since we are taking into consideration multiple types (X, chromium, playwright, distro, etc...) and user-defined variables.
IMO, it should suffice to filter those variables that are only meant to be used in the recorder itself: user auth, tokens and such.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's a valid point 👍 At the same time, I also thought would we have the same security concerns if we were to run the same script in the main process. Let me know if I'm being so naive!! Anyways It is always to be precautious about it, and I really like what @emilioalvap suggested. We should consider auditing some of the sensitive env vars and filter them out before passing to the child process.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, a filter out should be enough rather than a filter in just to be sure we're not passing our own signing keys to a malicious package downwards.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just so I can better understand here, isn't there a whole stack of dependencies we already use, given that we use an OSS toolchain? I guess my question is why are we particularly concerned here? I spoke briefly with @emilioalvap , and he pointed out that NPM is perhaps weaker than a linux distro in terms of supply chain (though probably not for many attacks).

CC @levinebw, your thoughts here?

Comment on lines 53 to 55
npmInstall.on("error", error => {
reject(error);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
npmInstall.on("error", error => {
reject(error);
});
npmInstall.on("error", reject);

Copy link
Contributor Author

@kyungeunni kyungeunni Mar 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks updated!(The less code the better 🧹)


exports.downloadForPlatform = async function downloadForPlatform(platform) {
platform = translatePlatform(platform);
for (const browser of ["chromium", "ffmpeg"]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: should we change browser to software here and in the references within the function like getBrowserVersion given we're not only dealing with browsers here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point, will amend 👍

@vigneshshanmugam
Copy link
Member

Thanks, @vigneshshanmugam !! I'm wondering if it was a permission issue, shouldn't the error code be EACCES: permission denied?

It seems like we are trying to access it for the screenshots, In that case, can you try with screenshots: off when running Synthetics test command. That should make the issue go away.

@lucasfcosta
Copy link
Contributor

Thanks, @vigneshshanmugam !! I'm wondering if it was a permission issue, shouldn't the error code be EACCES: permission denied?

It seems like we are trying to access it for the screenshots, In that case, can you try with screenshots: off when running Synthetics test command. That should make the issue go away.

@vigneshshanmugam I think even if we turn it off, the runner would still try to create a folder as specified here though, wouldn't it? Or am I missing something?

@vigneshshanmugam
Copy link
Member

@vigneshshanmugam I think even if we turn it off, the runner would still try to create a folder as specified here though, wouldn't it? Or am I missing something?

++ As you mentioned there is no way to turn it off, turning off screenshots wont just look for it.

Copy link
Contributor

@justinkambic justinkambic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to comment here and leave a passive LGTM - we should make sure that we don't leave these threads questioning security topics open before merging. I'll reach out to the other reviewers to see if they have any further thoughts on revisions we can make here, or if they're alright to merge as-is.

@@ -245,6 +245,7 @@ async function onTest(data, browserWindow) {
*/
synthCliProcess = fork(`${SYNTHETICS_CLI}`, args, {
env: {
...process.env,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something I'm uncertain about as I'm not accustomed to the security implications of changes to CLI interaction. From speaking offline it seems like this is not a large risk, but I'd like to hear others' opinions.

return Promise.all([downloadForPlatform(platform), fixSharp(arch, platform)]);
};

const { Arch } = require("electron-builder");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a functional reason that Arch needs to be required after beforePack is initialized?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh good catch @justinkambic, I will move it to the top of the file as it should be 👍

Copy link
Contributor

@lucasfcosta lucasfcosta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll follow @justinkambic's on this and leave this as a passive LGTM considering that filtering out the environment variables with our keys should be enough to prevent it from being passed downwards to other packages, although I do think we should just consult with security to be sure before merging.

As a note, I've also tested E2E the build for my MacOS and it worked fine for recording, replaying, and generating code for a journey.

@andrewvc
Copy link
Contributor

Works fine on windows!

@kyungeunni
Copy link
Contributor Author

Currently, we do not store any secrets in env variables. When we add then, we should remember and filter those secrets out when spawning the child process. We probably need to consider naming convention for it so we don't need to manually add what to filter. For example, we can name sensitive env variables with SECRET_* prefix and we can filter them whenever needed.

Copy link
Member

@vigneshshanmugam vigneshshanmugam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, Great work @kyungeunni

const platform = ctx.electronPlatformName;
if (platform === "linux") {
const resourcesPath = path.join(ctx.appOutDir, "resources");
changePermission(resourcesPath);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you miss the return statement here?

exports.default = function beforePack(ctx) {
const arch = Arch[ctx.arch];
const platform = ctx.electronPlatformName;
return Promise.all([downloadForPlatform(platform), fixSharp(arch, platform)]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just noted this today, which function would be responsible for handling the error when one of the promises break? Does electron builder takes care of this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behavior I observed is that when one of the promises breaks, the electron-builder would throw an error and abort the building process, which is what I expect it to do so. (downloading a browser and sharp lib for each platform is a prerequisite prior to bundling)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the error handling is taken care of the electron-builder, then all good 👍🏽 I just wanted us to avoid UnhandledPromiseRejection errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

yeah it's handled by electron-builder and looks like this 👍 thanks for double checking it with me!!

@lucasfcosta
Copy link
Contributor

Just adding an approval here considering the conversation we've just had with someone representing the security team (meaning we won't do any signing at this stage and thus not pass credentials down through env).

@kyungeunni kyungeunni merged commit 27cf388 into elastic:main Apr 6, 2022
@kyungeunni kyungeunni deleted the 150-poc-browser-per-platform branch April 6, 2022 14:28
v1v added a commit to v1v/synthetics-recorder that referenced this pull request Apr 26, 2022
…-in-the-ci

* upstream/main:
  Disable record journey button when URL input is empty. (elastic#218)
  Bump EUI version. (elastic#219)
  fix: use action.url instead of frameUrl in header (elastic#212)
  fix: Reset default values when action/assertion edit view closes (elastic#208)
  Fix Header layout when paused. (elastic#206)
  Make Test button disabled during recording/paused states. (elastic#207)
  chore: Add update to `README.md` to explain undefined reference error (elastic#198)
  chore: update .prettierrc to inherit Kibana's config (elastic#197)
  0.0.1-alpha.3
  Revert release version in `package.json` to reflect current version.
  feat: Step divider UI (elastic#175)
  chore: Remove unused exports (elastic#189)
  chore: Download browsers for all platforms to release builds from single machine (elastic#165)
  fix: wrap control button with tooltip when necessary only (elastic#184)
  fix: URL input expands for longer value (elastic#178)
  ci: release automation for unsigned artifacts (stage 1) (elastic#183)
  feat: switch `Steps` data structure from nested array to map (elastic#171)
  fix: reference the correct pw fork (elastic#186)
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

Successfully merging this pull request may close these issues.

[Bug] Playback does not work on Linux [Other] Fix browser inclusion in binary for all platforms
7 participants