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

Fdc brownfield setup #8150

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open

Fdc brownfield setup #8150

wants to merge 9 commits into from

Conversation

tammam-g
Copy link
Contributor

@tammam-g tammam-g commented Jan 29, 2025

Description

Support brownfield database setup. When we detect the database schema already have other tables (brownfield or greenfield but then a table was added manually) we ask the user if they want to setup the database as greenfield.
1- They say yes -> We transfer schema and table ownerships to firebase roles, and grant previous roles writer access only.
2- They say no -> We setup the database in brownfield mode which means that deployed connectors have writer permissions but SQL migration will stop working through cli.

Note: We should find a way to automatically turn on compat mode when database is setup as greenfield from brownfield to avoid accidentally dropping important tables.

Note 2: I think I can improve the brownfield experience by allowing firebase deploy to run if the database diff isn't incompatible (say only drops tables and remove fields). Right now sql:migrate will fail if database is found as brownfield which will fail firebase deploy.

Scenarios Tested

Fresh greenfield setup

Setup greenfield database from scratch using firebase deploy/sql:migrate -> no change

Brownfield to greenfield setup

I created one database with one table (not owned by firebase) and then user agrees to set it up as greenfield.

Screenshot 2025-01-31 at 4 13 02 PM Screenshot 2025-01-31 at 4 14 41 PM

Brownfield setup test

I create a new database with one table not owned by firebase and firebase isn't setup.
Screenshot 2025-01-31 at 4 30 57 PM

User runs firebase deploy, we then detect it's brownfield and ask the user if they want to setup the database as greenfield. -> User chooses to keep project as brownfield which sets up firebase writer/reader roles and p4sa access but deployment fails.

Screenshot 2025-01-31 at 4 31 38 PM

Confirming the user which has firebase writer role now can edit and select but can't create new tables.
Screenshot 2025-01-31 at 4 31 56 PM

Screenshot 2025-01-31 at 4 37 58 PM

BrownField Default Permissions Test

Finally, I test default permissions when database is setup. Firebase writer/reader should be able to see and edit tables created by other users in the schema ("public") after the setup is done in brownfield mode.

Screenshot 2025-01-31 at 4 40 50 PM

Additional flags test

Trying --force when migrating a brownfield database fails.

Screenshot 2025-01-31 at 4 50 53 PM

Sample Commands

firebase dataconnect:sql:setup

@tammam-g tammam-g requested review from fredzqm and joehan January 31, 2025 21:52
Copy link
Contributor

@fredzqm fredzqm 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! Excited to have brown field support very soon!

@@ -200,18 +201,6 @@ export async function setupIAMUsers(
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we update jsdoc now that permissions part are moved out?

Like this clean up.

ELSE FALSE
END AS schema_exists,
(SELECT pg_get_userbyid(nspowner) FROM pg_namespace WHERE nspname = '${schema}') AS schema_owner;`,
],
Copy link
Contributor

Choose a reason for hiding this comment

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

SELECT pg_get_userbyid(nspowner) AS schema_owner
FROM pg_namespace
WHERE nspname = '${schema}'

Would this work for the purpose? A bit simpler

owner: row.tableowner,
};
});

Copy link
Contributor

Choose a reason for hiding this comment

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

After tables, the below lines are to find setupSchema.

nit: Can we extract a helper caller getupSetupSchema? To make it clear that other fields are the same in 3 branches below.

@@ -237,10 +237,19 @@ export async function grantRoleToUserInSchema(options: Options, schema: Schema)
}

// Run the database roles setup. This should be idempotent.
await setupIAMUsers(instanceId, databaseId, options);
const schemaInfo = await getSchemaMetaData(instanceId, databaseId, DEFAULT_SCHEMA, options);
if (schemaInfo.setupStatus === SchemaSetupStatus.GreenField) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The condition here looks wrong.

Green field database would always return early without grant any permissions.

`ALTER DEFAULT PRIVILEGES IN SCHEMA "${schema}" GRANT SELECT, USAGE ON SEQUENCES TO "${firebaseReaderRole}";`,
`ALTER DEFAULT PRIVILEGES IN SCHEMA "${schema}" GRANT EXECUTE ON FUNCTIONS TO "${firebaseReaderRole}"`,
`SET ROLE = cloudsqlsuperuser`,
function defaultPermissions(databaseId: string, schema: string, ownerRole: string) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice cleanup.

Would it make sense to port those changes to backend code as well to keep them reasonably aligned?

I know they should do the same thing for green field.

To make this sync easier in the future, it might make sense to split caller logics to separate files.

We can split logics that aren't copied to the backend permissions.go into permissions_setup.ts. It can have getSchemaMetaData‎ , greenFieldSchemaSetup, brownfieldSqlSetup, etc.

};
}

export async function getUserAccessLevel(
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it used?

// Step 1: Grant firebasesuperuser access to the original owner
const uniqueTablesOwners = [...new Set(schemaInfo.tables.map((t) => t.owner))];
const grants = uniqueTablesOwners.map((owner) => `GRANT ${owner} TO ${FIREBASE_SUPER_USER}`);
await executeSqlCmdsAsSuperUser(options, instanceId, databaseId, grants, silent);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: any reason, this couldn't be executed in the same executeSqlCmdsAsSuperUser as below.

Each executeSqlCmdsAsSuperUser would call UpsertUser with random password. Adds a few seconds of delay.

return true;
}
} else {
logger.info(`Detected schema "${schema}" isn't fully setup or setup as brownfield project.`);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Shall we log schemaInfo.setupStatus here to help debugging?

logger.info(
clc.yellow(
"Setting up database in brownfield mode.\n" +
"Note: SQL migrations can't be done through FDC in this mode.",
Copy link
Contributor

Choose a reason for hiding this comment

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

done through clc.bold("firebase dataconnect:sql:migrate")

Be specific about what command we are referring to.

// until we confirm stability it's ok to run it on every migration by admin user.
if (userIsCSQLAdmin) {
await setupIAMUsers(instanceId, databaseId, options);
const schemaInfo = await getSchemaMetaData(instanceId, databaseId, DEFAULT_SCHEMA, options);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we check setupStatus the first thing in handleIncompatibleSchemaError?

When handleIncompatibleSchemaError is called, we have detected some diffs in sql:migrate. We should surface any permission setup error before any SQL migration specific errors.

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