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

feat: sandbox deployment events #254

Merged
merged 100 commits into from
Sep 27, 2023

Conversation

sdstolworthy
Copy link
Contributor

@sdstolworthy sdstolworthy commented Sep 20, 2023

Issue #, if available:

Description of changes:
Refactors sandbox to extend EventEmitter so that sandbox can emit lifecycle events. In this PR, only one event has been implemented: successfulDeployment.

Clients of sandbox can now do:

const sandbox = new FileWatchingSandbox({});
sandbox.on('successfulDeployment', () => console.log('my event handler'))

This will allow for more flexible sandbox lifecycle side effects in the future.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@changeset-bot
Copy link

changeset-bot bot commented Sep 20, 2023

🦋 Changeset detected

Latest commit: f0b70c3

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment on lines 46 to 50
private readonly createConfigGenerationCallback: (
appName?: string,
outDir?: string,
format?: ClientConfigFormat
) => () => Promise<void>
Copy link
Member

Choose a reason for hiding this comment

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

Now I'm not sure if this is right direction, i.e. replacing types with "anonymous function".
The danger is, if everything accepts anonymous closures then it's hard to reason which puzzle is right for the other puzzle.
For simple cases closures are fine, here I think it's making it less obvious what's going on. The name of createConfigGenerationCallback saves the day a bit, but type would be better.

As for the unit testing part.
Why can't we just mock ClientConfigGeneratorAdapter in the test to provide different implementation and assert that callback is registered?
Mocking, aka altering types is fine in unit tests.

Copy link
Contributor Author

@sdstolworthy sdstolworthy Sep 22, 2023

Choose a reason for hiding this comment

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

The problem is that the tests at that level are only testing the command interface level. So this refactor (or one of a couple very similar options) are the only way I'm able to assert that that specific method is being registered for that specific event. I agree that this current approach is not ideal.

Take a look here: https://github.com/sdstolworthy/samsara-cli/blob/sandbox-hooks/packages/cli/src/commands/sandbox/sandbox_command.test.ts#L25

This is why the test that is being described in the previous discussion is not possible at this level. This is not an integration test, it's a unit test. So, start has been overriden with a mock method.

Start is what is responsible for emitting the successfulDeployment event.

I think between the e2e test that already exists, the assertion that the on method is called, and the assertion test that start emits the successfulDeployment event provide sufficient coverage in this case.

I don't think the refactor here is a good direction. It allows us to make this very specific assertion, but at the cost of a poorly typed interface.

Copy link
Member

Choose a reason for hiding this comment

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

Ah ok. I understand now. This makes sense then.

But perhaps instead of anonymous createConfigGenerationCallback we need type SandboxEventListener ?

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 makes sense. It could even be a creator function that returns

type SandboxEventHandlers = {
  successfulDeployment: VoidCallback[]
}

And then we can register those events from the sandbox command.

Copy link
Member

Choose a reason for hiding this comment

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

yes please. This is a good opportunity to build better abstraction around it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@sobolk done

sobolk
sobolk previously approved these changes Sep 22, 2023
Amplifiyer
Amplifiyer previously approved these changes Sep 22, 2023
@@ -14,6 +17,22 @@ export type SandboxCommandOptions = {
profile: string | undefined;
};

export type EventHandler = () => void;
Copy link
Contributor

Choose a reason for hiding this comment

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

Based on this line it looks like the return type here should be Promise<void>?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

node:EventEmitter does not accept Promise<void>. It is fire and forget. See here: #254 (comment)

* @inheritdoc
*/
override emit(eventName: SandboxEvents, ...args: unknown[]): boolean {
return super.emit(eventName, args);
Copy link
Contributor

Choose a reason for hiding this comment

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

Where are the async handlers actually awaited?

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 the cool thing, they aren't :).
See comment above. EventEmitter is fire and forget only. It does not await promises. This makes sense, its whole job is simply to call the methods. Handling errors or async logic is the responsibility of the registered handler.

Copy link
Contributor

@edwardfoyle edwardfoyle Sep 22, 2023

Choose a reason for hiding this comment

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

What if the process exits before the promise finishes? This could actually be a real problem for our e2e tests that sigints the process the moment the deployment is complete

Copy link
Member

Choose a reason for hiding this comment

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

Node drains event loop, unless there's abnormal termination.

Copy link
Contributor

Choose a reason for hiding this comment

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

If we want to be sure this won't cause e2e failures, you'll need to copy these changes to a branch in the repo and run e2e on that (because AWS creds aren't shared with forks)

Copy link
Contributor

@edwardfoyle edwardfoyle Sep 22, 2023

Choose a reason for hiding this comment

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

Well the windows e2e tests failed with the exact error I would expect if it failed to write the client config file...
https://github.com/aws-amplify/samsara-cli/actions/runs/6278705272/job/17053506116?pr=275#step:7:319

Mac and Linux e2e passed but perhaps because the promise finished in time on those machines

Copy link
Contributor

@edwardfoyle edwardfoyle Sep 22, 2023

Choose a reason for hiding this comment

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

Node drains event loop, unless there's abnormal termination.

Actually I bet this is it. Windows doesn't have SIGINT, instead we have to hard kill the process. See https://github.com/aws-amplify/samsara-cli/blob/main/packages/integration-tests/src/process-controller/process_controller.ts#L74-L82

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I'm reading that code right, this is failing because of basically modal behavior of our integration suite with Windows, and the failure does not necessarily indicate that this would fail under normal circumstances?

Copy link
Member

Choose a reason for hiding this comment

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

perhaps Windows script can take a nap before the kill.

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 the current thought for the workaround. Not ideal, but serviceable.

@edwardfoyle edwardfoyle added the run-e2e Label that will include e2e tests in PR checks workflow label Sep 22, 2023
* feat: sandbox options add format

* test: add format to available options

* fix: sandbox command option format

* add changeset

* chore: update API.md

* refactor: move get_config_path to client-config

* chore: type and lint

* chore: update API.md

* chore: import formatChoices to sandbox

* chore: change template literal

* chore: update types, use Enum

* chore: update types for FormatOption

* chore: fix type

* chore: move getConfigPath to internal api

* chore: rename getClientConfigPath

* chore: rename FormatChoice

* chore: update API.md

* chore: add index.js to internal.ts

* chore: remove configFileName and formatChoices from API

* fix: test

* sandbox: initial implementation of deployment hooks

* sandbox: simplify hook registration example

* lint: fix lints

* refactor: use events instead of hooks

* api: update api description files

* logging: added debug logging

* tests: fix broken test

* sandbox: base event handler on node:events EventEmitter

* tests: fix broken test

* chore: move getConfigPath from GenerateConfigCommand to generateClientConfigToFile

* Update packages/client-config/src/paths/get_client_config_path.ts

Co-authored-by: Kamil Sobol <[email protected]>

* chore: rename FormatChoice to ClientConfigFormat

* refactor: generateClientConfigToFile to accept out and format

* test: update tests for generate config and sandbox

* Update packages/cli/src/commands/sandbox/sandbox_command.ts

Co-authored-by: Edward Foyle <[email protected]>

* test: misc var change

* chore: move ClientConfigFormat to public API

* chore: update API.md

* test: add test for getClientConfigPath

* test: use path.join to resolve windows slash

* test: use path.join to resolve windows slash

* chore: fix linting error

* chore: fix linting error

* chore: remove defaultOptions

* test: add asserts for both out and format

* test: add absolut path test

* fix: throw error if provided file path

* fix: broken test

* fix: integrate format into event callback

* api: update api description files

* fix: address pr comments

* chore: rename --out to --outDir

* fix: use lstatSync to detect file path

* chore: update API.md

* chore: changeset

* chore: remove un-used var

* fix: package-lock resolve registry

* chore: ignore spell of lstat

* chore: refactor mock-fs with node:mock

* chore: remove un-used var

* chore: make getClientConfigPath async

* fix: path for windows

* chore: remove auto-generated changelogs

* fix: remove references to deleted events

* api: update api description files

* refactor: rename event, move generate-config-adapter

* refactor: remove sandbox config adapter

* refactor: use args.out directly for config write path

* refactor: use exported getClientConfigPath from client-config package

* refactor: use mocks for test

* fix: explicitly export getClientConfigPath

* Update packages/sandbox/src/file_watching_sandbox.ts

Co-authored-by: Kamil Sobol <[email protected]>

* fix: remove unused import

* Update packages/cli/src/commands/sandbox/sandbox_command.ts

Co-authored-by: Amplifiyer <[email protected]>

* refactor: extract getBackendIdentifier

* test: add test to validate that successfulDeployment event is emitted

* test: refactor to allow asserting that a specific callback was registered

* fix: remove unused import

* refactor: creator method for registering sandbox events

* fix: remove unused import

* fix: fix naming, refactor for readability

---------

Co-authored-by: MJ☔ <[email protected]>
Co-authored-by: MJ Zhang <[email protected]>
Co-authored-by: Kamil Sobol <[email protected]>
Co-authored-by: Edward Foyle <[email protected]>
Co-authored-by: Amplifiyer <[email protected]>
@edwardfoyle edwardfoyle removed the run-e2e Label that will include e2e tests in PR checks workflow label Sep 22, 2023
@sdstolworthy sdstolworthy dismissed stale reviews from Amplifiyer and sobolk via 7885a8c September 27, 2023 14:51
@@ -72,6 +72,8 @@ export class ProcessController {
if (typeof currentInteraction.payload === 'string') {
if (currentInteraction.payload === CONTROL_C) {
if (process.platform.startsWith('win')) {
// Wait X milliseconds before sending kill in hopes of draining the node event queue
await new Promise((resolve) => setTimeout(resolve, 5000));
Copy link
Contributor

Choose a reason for hiding this comment

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

approved as long as we add a GH issue to the backlog to track this. Bonus points for linking to that issue in the comment 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.

@sdstolworthy sdstolworthy merged commit ee3d55f into aws-amplify:main Sep 27, 2023
37 checks passed
@sdstolworthy sdstolworthy deleted the sandbox-hooks branch September 27, 2023 17:28
abhi7cr pushed a commit that referenced this pull request Sep 27, 2023
feat: add deployment events to sandbox
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.

6 participants