-
Notifications
You must be signed in to change notification settings - Fork 74
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
Conversation
🦋 Changeset detectedLatest 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 |
2c9cd5a
to
0feeafe
Compare
packages/cli/src/commands/sandbox/sandbox-delete/sandbox_delete_command.test.ts
Fixed
Show fixed
Hide fixed
packages/cli/src/commands/sandbox/sandbox-delete/sandbox_delete_command.test.ts
Fixed
Show fixed
Hide fixed
private readonly createConfigGenerationCallback: ( | ||
appName?: string, | ||
outDir?: string, | ||
format?: ClientConfigFormat | ||
) => () => Promise<void> |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sobolk done
@@ -14,6 +17,22 @@ export type SandboxCommandOptions = { | |||
profile: string | undefined; | |||
}; | |||
|
|||
export type EventHandler = () => void; |
There was a problem hiding this comment.
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>
?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
* 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]>
7885a8c
…li into sandbox-hooks
@@ -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)); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feat: add deployment events to sandbox
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:
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.