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

ENG-2884 Add multi-host support #132

Merged
merged 6 commits into from
Oct 18, 2024
Merged

Conversation

fabgo
Copy link
Contributor

@fabgo fabgo commented Oct 16, 2024

Adds multi-host support to the CLI by optionally looking up the CLI configuration, including the backend host, from Firestore. This allows us to specify separate backend hosts for each tenant.

  • Adds an optional config to the /orgs/{orgId} document
  • Upon login, If the config is found, the config in stored locally
  • When executing a command, the local config is used, if found

Adds multi-host support to the CLI by optionally looking up the CLI configuration, including the backend host from Firebase. This allows us to specify separate backend hosts for each tenant.

- Adds an optional config to the /orgs/{orgId} document
- Upon login, If the config is found, the config in stored locally
- When executing a command, the local config is used, if found
@fabgo fabgo requested a review from gergas3 October 16, 2024 18:14
Comment on lines 32 to 33
let firestore: Firestore;
let auth: Auth;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should avoid mutable global variables like these. Esp if they are about authentication. We may make a mistake in updating them and then the user is attempting to log in to the wrong host (= P0 environment).

An idea here is to keep the module-level const app, firestore, auth objects and only initialize another firestore when we access the public orgs document. That would be a different method, not getDoc, but e.g. getPublicDoc and it creates its own firestore using the bootstrap config.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was able to move auth to a local variable. But it's a bit tricky to make everything const. Is this a concern even if these are not exported?

Copy link
Contributor

Choose a reason for hiding this comment

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

imo not exported is good enough


export const CONFIG_FILE_PATH = path.join(P0_PATH, "config.json");

export let tenantConfig = bootstrapConfig;
Copy link
Contributor

Choose a reason for hiding this comment

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

A mutable variable at the module-level will lead to problems imo. We will have the wrong config in it, e.g. someone makes a change that overwrites its value directly.

Can this be accessed via a getter?

let tenantConfig = bootstrapConfig;
...
export const getTenantConfig = () => tenantConfig

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines 59 to 61
if (orgData.config) {
await saveTenantConfig(orgData.config);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't this have to move up, right after if (!orgData) throw "Could not find organization";?

Afaict currently we will log the user in to the bootstrap project and whatever the org is configured for login there. I think for orgs that actually use a different host, the login information is going to be in the Firestore of that other host (= P0 environment), and the p0-prod document for their org will only contain a config that points to the right environment.

So we don't want to log these users in to our p0-prod. We want them to log in to their environment. When loginFn is called tenantConfig should already be updated with the right config.

Copy link
Contributor

Choose a reason for hiding this comment

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

A second thought: we could always save tenantConfig and use the bootstrapConfig as a fallback right here. Then we don't have to pre-assign in drivers/config.ts, tenantConfig can be undefined until we save here.

await saveTenantConfig(orgData.config ?? bootstrapConfig);

Or, we could also read tenantConfig lazily in a getTenantConfig function that checks the variable, then checks the file, then loads. Since then you can check if tenantConfig === undefined.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@@ -32,6 +38,8 @@ export const login = async (
args: { org: string },
options?: { skipAuthenticate?: boolean }
) => {
initializeFirebase({ useBootstrapConfig: true });

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems the line below is the only getDoc call where we read a public collection.

Could we create a special getPublicDoc function that always initializes and uses the bootstrap config to retrieve the tenant config?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a great suggestion. Done.

fabgo added 2 commits October 17, 2024 10:48
* Add publicDoc() function to read from bootstrap firestore
* Ensure updated config is set before authentication
* Move auth from global to local scope
* Rename guard function to fsShutdownGuard for clarity
[
[
"/Users/fabianjoya/.p0/config.json",
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 won't pass on other computers because it has the home folder in it. I think jest has some way of asserting on a pattern instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

Comment on lines 63 to 64
const identity = await loadCredentials({ noRefresh: true });
await authenticateToFirebase(identity);
Copy link
Contributor

Choose a reason for hiding this comment

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

It used to call authenticate, so that function was reused in the login and all the other commands. I tried to understand why we can't reuse anymore. Can you explain please?

Copy link
Contributor Author

@fabgo fabgo Oct 18, 2024

Choose a reason for hiding this comment

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

It's because authenticate now saves the config & initializes firebase. I would like to rename the authenticate method now, but couldn't think of a better name.

And there used to be a circular call chain, which I am trying to break:

authenticate -> loadCredentials -> login -> authenticate

@fabgo fabgo merged commit 2b0cd3d into main Oct 18, 2024
3 checks passed
@fabgo fabgo deleted the fabian/eng-2884-multi-host-support branch October 18, 2024 14:58
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.

2 participants