@@ -4,139 +4,160 @@ import { grantRolesToCloudSqlServiceAccount } from "./checkIam";
4
4
import { Instance } from "../gcp/cloudsql/types" ;
5
5
import { promiseWithSpinner } from "../utils" ;
6
6
import { logger } from "../logger" ;
7
+ import { freeTrialTermsLink , checkFreeTrialInstanceUsed } from "./freeTrial" ;
7
8
8
9
const GOOGLE_ML_INTEGRATION_ROLE = "roles/aiplatform.user" ;
9
10
10
- import { freeTrialTermsLink , checkFreeTrialInstanceUsed } from "./freeTrial" ;
11
+ /** Sets up a Cloud SQL instance, database and its permissions. */
12
+ export async function setupCloudSql ( args : {
13
+ projectId : string ;
14
+ location : string ;
15
+ instanceId : string ;
16
+ databaseId : string ;
17
+ requireGoogleMlIntegration : boolean ;
18
+ dryRun ?: boolean ;
19
+ } ) : Promise < void > {
20
+ await upsertInstance ( { ...args } ) ;
21
+ const { projectId, instanceId, requireGoogleMlIntegration, dryRun } = args ;
22
+ if ( requireGoogleMlIntegration && ! dryRun ) {
23
+ await grantRolesToCloudSqlServiceAccount ( projectId , instanceId , [ GOOGLE_ML_INTEGRATION_ROLE ] ) ;
24
+ }
25
+ }
11
26
12
- export async function provisionCloudSql ( args : {
27
+ async function upsertInstance ( args : {
13
28
projectId : string ;
14
29
location : string ;
15
30
instanceId : string ;
16
31
databaseId : string ;
17
- enableGoogleMlIntegration : boolean ;
18
- waitForCreation : boolean ;
19
- silent ?: boolean ;
32
+ requireGoogleMlIntegration : boolean ;
20
33
dryRun ?: boolean ;
21
- } ) : Promise < string > {
22
- let connectionName = "" ; // Not used yet, will be used for schema migration
23
- const {
24
- projectId,
25
- location,
26
- instanceId,
27
- databaseId,
28
- enableGoogleMlIntegration,
29
- waitForCreation,
30
- silent,
31
- dryRun,
32
- } = args ;
34
+ } ) : Promise < void > {
35
+ const { projectId, instanceId, requireGoogleMlIntegration, dryRun } = args ;
33
36
try {
34
37
const existingInstance = await cloudSqlAdminClient . getInstance ( projectId , instanceId ) ;
35
- silent ||
36
- utils . logLabeledBullet ( "dataconnect" , `Found existing Cloud SQL instance ${ instanceId } .` ) ;
37
- connectionName = existingInstance ?. connectionName || "" ;
38
- const why = getUpdateReason ( existingInstance , enableGoogleMlIntegration ) ;
38
+ utils . logLabeledBullet ( "dataconnect" , `Found existing Cloud SQL instance ${ instanceId } .` ) ;
39
+ const why = getUpdateReason ( existingInstance , requireGoogleMlIntegration ) ;
39
40
if ( why ) {
40
- const cta = dryRun
41
- ? `It will be updated on your next deploy.`
42
- : `Updating instance. This may take a few minutes...` ;
43
- silent ||
41
+ if ( dryRun ) {
44
42
utils . logLabeledBullet (
45
43
"dataconnect" ,
46
- `Instance ${ instanceId } settings not compatible with Firebase Data Connect. ` + cta + why ,
44
+ `Cloud SQL instance ${ instanceId } settings not compatible with Firebase Data Connect. ` +
45
+ `It will be updated on your next deploy.` +
46
+ why ,
47
+ ) ;
48
+ } else {
49
+ utils . logLabeledBullet (
50
+ "dataconnect" ,
51
+ `Cloud SQL instance ${ instanceId } settings not compatible with Firebase Data Connect. ` +
52
+ why ,
47
53
) ;
48
- if ( ! dryRun ) {
49
54
await promiseWithSpinner (
50
55
( ) =>
51
56
cloudSqlAdminClient . updateInstanceForDataConnect (
52
57
existingInstance ,
53
- enableGoogleMlIntegration ,
58
+ requireGoogleMlIntegration ,
54
59
) ,
55
- "Updating your instance..." ,
60
+ "Updating your Cloud SQL instance..." ,
56
61
) ;
57
- silent || utils . logLabeledBullet ( "dataconnect" , "Instance updated" ) ;
58
62
}
59
63
}
64
+ await upsertDatabase ( { ...args } ) ;
60
65
} catch ( err : any ) {
61
- // We only should catch NOT FOUND errors
62
66
if ( err . status !== 404 ) {
63
67
throw err ;
64
68
}
65
- const cta = dryRun ? "It will be created on your next deploy" : "Creating it now." ;
66
- const freeTrialUsed = await checkFreeTrialInstanceUsed ( projectId ) ;
67
- silent ||
68
- utils . logLabeledBullet (
69
- "dataconnect" ,
70
- `CloudSQL instance '${ instanceId } ' not found.` + cta + freeTrialUsed
71
- ? ""
72
- : `\nThis instance is provided under the terms of the Data Connect no-cost trial ${ freeTrialTermsLink ( ) } ` +
73
- dryRun
74
- ? `\nMonitor the progress at ${ cloudSqlAdminClient . instanceConsoleLink ( projectId , instanceId ) } `
75
- : "" ,
76
- ) ;
69
+ // Cloud SQL instance is not found, start its creation.
70
+ await createInstance ( { ...args } ) ;
71
+ }
72
+ }
77
73
78
- if ( ! dryRun ) {
79
- const newInstance = await promiseWithSpinner (
80
- ( ) =>
81
- cloudSqlAdminClient . createInstance ( {
82
- projectId,
83
- location,
84
- instanceId,
85
- enableGoogleMlIntegration,
86
- waitForCreation,
87
- freeTrial : ! freeTrialUsed ,
88
- } ) ,
89
- "Creating your instance..." ,
90
- ) ;
91
- if ( newInstance ) {
92
- silent || utils . logLabeledBullet ( "dataconnect" , "Instance created" ) ;
93
- connectionName = newInstance ?. connectionName || "" ;
94
- } else {
95
- silent ||
96
- utils . logLabeledBullet (
97
- "dataconnect" ,
98
- "Cloud SQL instance creation started. While it is being set up, your data will be saved in a temporary database. When it is ready, your data will be migrated." ,
99
- ) ;
100
- return connectionName ;
101
- }
102
- }
74
+ async function createInstance ( args : {
75
+ projectId : string ;
76
+ location : string ;
77
+ instanceId : string ;
78
+ requireGoogleMlIntegration : boolean ;
79
+ dryRun ?: boolean ;
80
+ } ) : Promise < void > {
81
+ const { projectId, location, instanceId, requireGoogleMlIntegration, dryRun } = args ;
82
+ const freeTrialUsed = await checkFreeTrialInstanceUsed ( projectId ) ;
83
+ if ( dryRun ) {
84
+ utils . logLabeledBullet (
85
+ "dataconnect" ,
86
+ `Cloud SQL Instance ${ instanceId } not found. It will be created on your next deploy.` ,
87
+ ) ;
88
+ } else {
89
+ await cloudSqlAdminClient . createInstance ( {
90
+ projectId,
91
+ location,
92
+ instanceId,
93
+ enableGoogleMlIntegration : requireGoogleMlIntegration ,
94
+ freeTrial : ! freeTrialUsed ,
95
+ } ) ;
96
+ utils . logLabeledBullet (
97
+ "dataconnect" ,
98
+ cloudSQLBeingCreated ( projectId , instanceId , ! freeTrialUsed ) ,
99
+ ) ;
103
100
}
101
+ }
102
+
103
+ /**
104
+ * Returns a message indicating that a Cloud SQL instance is being created.
105
+ */
106
+ export function cloudSQLBeingCreated (
107
+ projectId : string ,
108
+ instanceId : string ,
109
+ includeFreeTrialToS ?: boolean ,
110
+ ) : string {
111
+ return (
112
+ `Cloud SQL Instance ${ instanceId } is being created.` +
113
+ ( includeFreeTrialToS
114
+ ? `\nThis instance is provided under the terms of the Data Connect no-cost trial ${ freeTrialTermsLink ( ) } `
115
+ : "" ) +
116
+ `
117
+ Meanwhile, your data are saved in a temporary database and will be migrated once complete. Monitor its progress at
104
118
119
+ ${ cloudSqlAdminClient . instanceConsoleLink ( projectId , instanceId ) }
120
+ `
121
+ ) ;
122
+ }
123
+
124
+ async function upsertDatabase ( args : {
125
+ projectId : string ;
126
+ instanceId : string ;
127
+ databaseId : string ;
128
+ dryRun ?: boolean ;
129
+ } ) : Promise < void > {
130
+ const { projectId, instanceId, databaseId, dryRun } = args ;
105
131
try {
106
132
await cloudSqlAdminClient . getDatabase ( projectId , instanceId , databaseId ) ;
107
- silent || utils . logLabeledBullet ( "dataconnect" , `Found existing database ${ databaseId } .` ) ;
133
+ utils . logLabeledBullet ( "dataconnect" , `Found existing Postgres Database ${ databaseId } .` ) ;
108
134
} catch ( err : any ) {
109
- if ( err . status === 404 ) {
110
- if ( dryRun ) {
111
- silent ||
112
- utils . logLabeledBullet (
113
- "dataconnect" ,
114
- `Postgres database ${ databaseId } not found. It will be created on your next deploy.` ,
115
- ) ;
116
- } else {
117
- await cloudSqlAdminClient . createDatabase ( projectId , instanceId , databaseId ) ;
118
- silent || utils . logLabeledBullet ( "dataconnect" , `Postgres database ${ databaseId } created.` ) ;
119
- }
120
- } else {
135
+ if ( err . status !== 404 ) {
121
136
// Skip it if the database is not accessible.
122
137
// Possible that the CSQL instance is in the middle of something.
123
- logger . debug ( `Unexpected error from CloudSQL: ${ err } ` ) ;
124
- silent || utils . logLabeledWarning ( "dataconnect" , `Database ${ databaseId } is not accessible.` ) ;
138
+ logger . debug ( `Unexpected error from Cloud SQL: ${ err } ` ) ;
139
+ utils . logLabeledWarning ( "dataconnect" , `Postgres Database ${ databaseId } is not accessible.` ) ;
140
+ return ;
141
+ }
142
+ if ( dryRun ) {
143
+ utils . logLabeledBullet (
144
+ "dataconnect" ,
145
+ `Postgres Database ${ databaseId } not found. It will be created on your next deploy.` ,
146
+ ) ;
147
+ } else {
148
+ await cloudSqlAdminClient . createDatabase ( projectId , instanceId , databaseId ) ;
149
+ utils . logLabeledBullet ( "dataconnect" , `Postgres Database ${ databaseId } created.` ) ;
125
150
}
126
151
}
127
- if ( enableGoogleMlIntegration && ! dryRun ) {
128
- await grantRolesToCloudSqlServiceAccount ( projectId , instanceId , [ GOOGLE_ML_INTEGRATION_ROLE ] ) ;
129
- }
130
- return connectionName ;
131
152
}
132
153
133
154
/**
134
- * Validate that existing CloudSQL instances have the necessary settings.
155
+ * Validate that existing Cloud SQL instances have the necessary settings.
135
156
*/
136
157
export function getUpdateReason ( instance : Instance , requireGoogleMlIntegration : boolean ) : string {
137
158
let reason = "" ;
138
159
const settings = instance . settings ;
139
- // CloudSQL instances must have public IP enabled to be used with Firebase Data Connect.
160
+ // Cloud SQL instances must have public IP enabled to be used with Firebase Data Connect.
140
161
if ( ! settings . ipConfiguration ?. ipv4Enabled ) {
141
162
reason += "\n - to enable public IP." ;
142
163
}
@@ -154,7 +175,7 @@ export function getUpdateReason(instance: Instance, requireGoogleMlIntegration:
154
175
}
155
176
}
156
177
157
- // CloudSQL instances must have IAM authentication enabled to be used with Firebase Data Connect.
178
+ // Cloud SQL instances must have IAM authentication enabled to be used with Firebase Data Connect.
158
179
const isIamEnabled =
159
180
settings . databaseFlags ?. some (
160
181
( f ) => f . name === "cloudsql.iam_authentication" && f . value === "on" ,
0 commit comments