firebase-tools 13.7.5 → 13.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api.js +9 -1
- package/lib/apiv2.js +19 -13
- package/lib/apphosting/app.js +38 -58
- package/lib/apphosting/githubConnections.js +1 -1
- package/lib/apphosting/index.js +72 -20
- package/lib/checkValidTargetFilters.js +8 -1
- package/lib/commands/apphosting-backends-create.js +4 -3
- package/lib/commands/apphosting-backends-delete.js +24 -17
- package/lib/commands/apphosting-backends-list.js +3 -3
- package/lib/commands/apphosting-secrets-grantaccess.js +9 -5
- package/lib/commands/dataconnect-list.js +64 -0
- package/lib/commands/dataconnect-sdk-generate.js +37 -0
- package/lib/commands/dataconnect-sql-diff.js +25 -0
- package/lib/commands/dataconnect-sql-migrate.js +46 -0
- package/lib/commands/deploy.js +27 -1
- package/lib/commands/index.js +10 -0
- package/lib/commands/init.js +7 -0
- package/lib/commands/setup-emulators-dataconnect.js +12 -0
- package/lib/config.js +1 -0
- package/lib/dataconnect/build.js +23 -0
- package/lib/dataconnect/checkIam.js +30 -0
- package/lib/dataconnect/client.js +115 -0
- package/lib/dataconnect/dataplaneClient.js +16 -0
- package/lib/dataconnect/ensureApis.js +12 -0
- package/lib/dataconnect/fileUtils.js +89 -0
- package/lib/dataconnect/filters.js +45 -0
- package/lib/dataconnect/freeTrial.js +23 -0
- package/lib/dataconnect/graphqlError.js +13 -0
- package/lib/dataconnect/load.js +40 -0
- package/lib/dataconnect/names.js +48 -0
- package/lib/dataconnect/prompts.js +20 -0
- package/lib/dataconnect/provisionCloudSql.js +74 -0
- package/lib/dataconnect/schemaMigration.js +171 -0
- package/lib/dataconnect/types.js +23 -0
- package/lib/deploy/dataconnect/deploy.js +84 -0
- package/lib/deploy/dataconnect/index.js +9 -0
- package/lib/deploy/dataconnect/prepare.js +30 -0
- package/lib/deploy/dataconnect/release.js +65 -0
- package/lib/deploy/functions/checkIam.js +4 -34
- package/lib/deploy/functions/release/fabricator.js +7 -2
- package/lib/deploy/index.js +2 -0
- package/lib/downloadUtils.js +2 -2
- package/lib/emulator/constants.js +3 -0
- package/lib/emulator/controller.js +39 -12
- package/lib/emulator/dataconnectEmulator.js +88 -0
- package/lib/emulator/download.js +1 -1
- package/lib/emulator/downloadableEmulators.js +42 -3
- package/lib/emulator/portUtils.js +3 -3
- package/lib/emulator/registry.js +6 -1
- package/lib/emulator/types.js +3 -0
- package/lib/experiments.js +12 -5
- package/lib/extensions/emulator/specHelper.js +5 -39
- package/lib/frameworks/next/index.js +3 -1
- package/lib/frameworks/next/utils.js +1 -1
- package/lib/gcp/apphosting.js +6 -1
- package/lib/gcp/cloudsql/cloudsqladmin.js +155 -0
- package/lib/gcp/cloudsql/connect.js +128 -0
- package/lib/gcp/cloudsql/fbToolsAuthClient.js +42 -0
- package/lib/gcp/cloudsql/types.js +2 -0
- package/lib/gcp/firedata.js +26 -0
- package/lib/gcp/iam.js +33 -1
- package/lib/gcp/secretManager.js +1 -1
- package/lib/hosting/interactive.js +4 -0
- package/lib/init/features/dataconnect/index.js +160 -0
- package/lib/init/features/emulators.js +13 -0
- package/lib/init/features/functions/index.js +15 -3
- package/lib/init/features/index.js +3 -1
- package/lib/init/index.js +1 -0
- package/lib/logger.js +22 -2
- package/lib/operation-poller.js +8 -2
- package/lib/rc.js +10 -1
- package/lib/requireAuth.js +1 -0
- package/lib/requireTosAcceptance.js +21 -0
- package/lib/utils.js +55 -4
- package/package.json +6 -2
- package/schema/connector-yaml.json +54 -0
- package/schema/dataconnect-yaml.json +72 -0
- package/schema/firebase-config.json +103 -0
- package/templates/extensions/javascript/package.lint.json +2 -2
- package/templates/extensions/javascript/package.nolint.json +2 -2
- package/templates/extensions/typescript/package.lint.json +2 -2
- package/templates/extensions/typescript/package.nolint.json +2 -2
- package/templates/init/dataconnect/connector.yaml +2 -0
- package/templates/init/dataconnect/dataconnect.yaml +10 -0
- package/templates/init/dataconnect/mutations.gql +4 -0
- package/templates/init/dataconnect/queries.gql +6 -0
- package/templates/init/dataconnect/schema.gql +15 -0
- package/templates/init/functions/javascript/_gitignore +2 -1
- package/templates/init/functions/javascript/package.lint.json +2 -2
- package/templates/init/functions/javascript/package.nolint.json +2 -2
- package/templates/init/functions/python/_gitignore +1 -0
- package/templates/init/functions/typescript/_gitignore +1 -0
- package/templates/init/functions/typescript/package.lint.json +2 -2
- package/templates/init/functions/typescript/package.nolint.json +2 -2
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listUsers = exports.deleteUser = exports.getUser = exports.createUser = exports.createDatabase = exports.getDatabase = exports.listDatabases = exports.updateInstanceForDataConnect = exports.createInstance = exports.getInstance = exports.listInstances = void 0;
|
|
4
|
+
const apiv2_1 = require("../../apiv2");
|
|
5
|
+
const api_1 = require("../../api");
|
|
6
|
+
const operationPoller = require("../../operation-poller");
|
|
7
|
+
const API_VERSION = "v1";
|
|
8
|
+
const client = new apiv2_1.Client({
|
|
9
|
+
urlPrefix: (0, api_1.cloudSQLAdminOrigin)(),
|
|
10
|
+
auth: true,
|
|
11
|
+
apiVersion: API_VERSION,
|
|
12
|
+
});
|
|
13
|
+
async function listInstances(projectId) {
|
|
14
|
+
var _a;
|
|
15
|
+
const res = await client.get(`projects/${projectId}/instances`);
|
|
16
|
+
return (_a = res.body.items) !== null && _a !== void 0 ? _a : [];
|
|
17
|
+
}
|
|
18
|
+
exports.listInstances = listInstances;
|
|
19
|
+
async function getInstance(projectId, instanceId) {
|
|
20
|
+
const res = await client.get(`projects/${projectId}/instances/${instanceId}`);
|
|
21
|
+
return res.body;
|
|
22
|
+
}
|
|
23
|
+
exports.getInstance = getInstance;
|
|
24
|
+
async function createInstance(projectId, location, instanceId, enableGoogleMlIntegration) {
|
|
25
|
+
const databaseFlags = [{ name: "cloudsql.iam_authentication", value: "on" }];
|
|
26
|
+
if (enableGoogleMlIntegration) {
|
|
27
|
+
databaseFlags.push({ name: "cloudsql.enable_google_ml_integration", value: "on" });
|
|
28
|
+
}
|
|
29
|
+
const op = await client.post(`projects/${projectId}/instances`, {
|
|
30
|
+
name: instanceId,
|
|
31
|
+
region: location,
|
|
32
|
+
databaseVersion: "POSTGRES_15",
|
|
33
|
+
settings: {
|
|
34
|
+
tier: "db-f1-micro",
|
|
35
|
+
edition: "ENTERPRISE",
|
|
36
|
+
ipConfiguration: {
|
|
37
|
+
authorizedNetworks: [],
|
|
38
|
+
},
|
|
39
|
+
enableGoogleMlIntegration,
|
|
40
|
+
databaseFlags,
|
|
41
|
+
storageAutoResize: false,
|
|
42
|
+
userLabels: { "firebase-data-connect": "ft" },
|
|
43
|
+
insightsConfig: {
|
|
44
|
+
queryInsightsEnabled: true,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
const opName = `projects/${projectId}/operations/${op.body.name}`;
|
|
49
|
+
const pollRes = await operationPoller.pollOperation({
|
|
50
|
+
apiOrigin: (0, api_1.cloudSQLAdminOrigin)(),
|
|
51
|
+
apiVersion: API_VERSION,
|
|
52
|
+
operationResourceName: opName,
|
|
53
|
+
doneFn: (op) => op.status === "DONE",
|
|
54
|
+
masterTimeout: 1200000,
|
|
55
|
+
});
|
|
56
|
+
return pollRes;
|
|
57
|
+
}
|
|
58
|
+
exports.createInstance = createInstance;
|
|
59
|
+
async function updateInstanceForDataConnect(instance, enableGoogleMlIntegration) {
|
|
60
|
+
let dbFlags = setDatabaseFlag({ name: "cloudsql.iam_authentication", value: "on" }, instance.settings.databaseFlags);
|
|
61
|
+
if (enableGoogleMlIntegration) {
|
|
62
|
+
dbFlags = setDatabaseFlag({ name: "cloudsql.enable_google_ml_integration", value: "on" }, dbFlags);
|
|
63
|
+
}
|
|
64
|
+
const op = await client.patch(`projects/${instance.project}/instances/${instance.name}`, {
|
|
65
|
+
settings: {
|
|
66
|
+
ipConfiguration: {
|
|
67
|
+
ipv4Enabled: true,
|
|
68
|
+
},
|
|
69
|
+
databaseFlags: dbFlags,
|
|
70
|
+
enableGoogleMlIntegration,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
const opName = `projects/${instance.project}/operations/${op.body.name}`;
|
|
74
|
+
const pollRes = await operationPoller.pollOperation({
|
|
75
|
+
apiOrigin: (0, api_1.cloudSQLAdminOrigin)(),
|
|
76
|
+
apiVersion: API_VERSION,
|
|
77
|
+
operationResourceName: opName,
|
|
78
|
+
doneFn: (op) => op.status === "DONE",
|
|
79
|
+
masterTimeout: 1200000,
|
|
80
|
+
});
|
|
81
|
+
return pollRes;
|
|
82
|
+
}
|
|
83
|
+
exports.updateInstanceForDataConnect = updateInstanceForDataConnect;
|
|
84
|
+
function setDatabaseFlag(flag, flags = []) {
|
|
85
|
+
const temp = flags.filter((f) => f.name !== flag.name);
|
|
86
|
+
temp.push(flag);
|
|
87
|
+
return temp;
|
|
88
|
+
}
|
|
89
|
+
async function listDatabases(projectId, instanceId) {
|
|
90
|
+
const res = await client.get(`projects/${projectId}/instances/${instanceId}/databases`);
|
|
91
|
+
return res.body.items;
|
|
92
|
+
}
|
|
93
|
+
exports.listDatabases = listDatabases;
|
|
94
|
+
async function getDatabase(projectId, instanceId, databaseId) {
|
|
95
|
+
const res = await client.get(`projects/${projectId}/instances/${instanceId}/databases/${databaseId}`);
|
|
96
|
+
return res.body;
|
|
97
|
+
}
|
|
98
|
+
exports.getDatabase = getDatabase;
|
|
99
|
+
async function createDatabase(projectId, instanceId, databaseId) {
|
|
100
|
+
const op = await client.post(`projects/${projectId}/instances/${instanceId}/databases`, {
|
|
101
|
+
project: projectId,
|
|
102
|
+
instance: instanceId,
|
|
103
|
+
name: databaseId,
|
|
104
|
+
});
|
|
105
|
+
const opName = `projects/${projectId}/operations/${op.body.name}`;
|
|
106
|
+
const pollRes = await operationPoller.pollOperation({
|
|
107
|
+
apiOrigin: (0, api_1.cloudSQLAdminOrigin)(),
|
|
108
|
+
apiVersion: API_VERSION,
|
|
109
|
+
operationResourceName: opName,
|
|
110
|
+
doneFn: (op) => op.status === "DONE",
|
|
111
|
+
});
|
|
112
|
+
return pollRes;
|
|
113
|
+
}
|
|
114
|
+
exports.createDatabase = createDatabase;
|
|
115
|
+
async function createUser(projectId, instanceId, type, username, password) {
|
|
116
|
+
const op = await client.post(`projects/${projectId}/instances/${instanceId}/users`, {
|
|
117
|
+
name: username,
|
|
118
|
+
instance: instanceId,
|
|
119
|
+
project: projectId,
|
|
120
|
+
password: password,
|
|
121
|
+
sqlserverUserDetails: {
|
|
122
|
+
disabled: false,
|
|
123
|
+
serverRoles: ["cloudsqlsuperuser"],
|
|
124
|
+
},
|
|
125
|
+
type,
|
|
126
|
+
});
|
|
127
|
+
const opName = `projects/${projectId}/operations/${op.body.name}`;
|
|
128
|
+
const pollRes = await operationPoller.pollOperation({
|
|
129
|
+
apiOrigin: (0, api_1.cloudSQLAdminOrigin)(),
|
|
130
|
+
apiVersion: API_VERSION,
|
|
131
|
+
operationResourceName: opName,
|
|
132
|
+
doneFn: (op) => op.status === "DONE",
|
|
133
|
+
});
|
|
134
|
+
return pollRes;
|
|
135
|
+
}
|
|
136
|
+
exports.createUser = createUser;
|
|
137
|
+
async function getUser(projectId, instanceId, username) {
|
|
138
|
+
const res = await client.get(`projects/${projectId}/instances/${instanceId}/users/${username}`);
|
|
139
|
+
return res.body;
|
|
140
|
+
}
|
|
141
|
+
exports.getUser = getUser;
|
|
142
|
+
async function deleteUser(projectId, instanceId, username) {
|
|
143
|
+
const res = await client.delete(`projects/${projectId}/instances/${instanceId}/users`, {
|
|
144
|
+
queryParams: {
|
|
145
|
+
name: username,
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
return res.body;
|
|
149
|
+
}
|
|
150
|
+
exports.deleteUser = deleteUser;
|
|
151
|
+
async function listUsers(projectId, instanceId) {
|
|
152
|
+
const res = await client.get(`projects/${projectId}/instances/${instanceId}/users`);
|
|
153
|
+
return res.body.items;
|
|
154
|
+
}
|
|
155
|
+
exports.listUsers = listUsers;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.firebaseowner = exports.setupIAMUser = exports.execute = void 0;
|
|
4
|
+
const pg = require("pg");
|
|
5
|
+
const cloud_sql_connector_1 = require("@google-cloud/cloud-sql-connector");
|
|
6
|
+
const requireAuth_1 = require("../../requireAuth");
|
|
7
|
+
const projectUtils_1 = require("../../projectUtils");
|
|
8
|
+
const cloudSqlAdminClient = require("./cloudsqladmin");
|
|
9
|
+
const utils = require("../../utils");
|
|
10
|
+
const logger_1 = require("../../logger");
|
|
11
|
+
const error_1 = require("../../error");
|
|
12
|
+
const fbToolsAuthClient_1 = require("./fbToolsAuthClient");
|
|
13
|
+
async function execute(sqlStatements, opts) {
|
|
14
|
+
const logFn = opts.silent ? logger_1.logger.debug : logger_1.logger.info;
|
|
15
|
+
const instance = await cloudSqlAdminClient.getInstance(opts.projectId, opts.instanceId);
|
|
16
|
+
const user = await cloudSqlAdminClient.getUser(opts.projectId, opts.instanceId, opts.username);
|
|
17
|
+
const connectionName = instance.connectionName;
|
|
18
|
+
if (!connectionName) {
|
|
19
|
+
throw new error_1.FirebaseError(`Could not get instance conection string for ${opts.instanceId}:${opts.databaseId}`);
|
|
20
|
+
}
|
|
21
|
+
let connector;
|
|
22
|
+
let pool;
|
|
23
|
+
switch (user.type) {
|
|
24
|
+
case "CLOUD_IAM_USER": {
|
|
25
|
+
connector = new cloud_sql_connector_1.Connector({
|
|
26
|
+
auth: new fbToolsAuthClient_1.FBToolsAuthClient(),
|
|
27
|
+
});
|
|
28
|
+
const clientOpts = await connector.getOptions({
|
|
29
|
+
instanceConnectionName: connectionName,
|
|
30
|
+
ipType: cloud_sql_connector_1.IpAddressTypes.PUBLIC,
|
|
31
|
+
authType: cloud_sql_connector_1.AuthTypes.IAM,
|
|
32
|
+
});
|
|
33
|
+
pool = new pg.Pool(Object.assign(Object.assign({}, clientOpts), { user: opts.username, database: opts.databaseId, max: 1 }));
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
case "CLOUD_IAM_SERVICE_ACCOUNT": {
|
|
37
|
+
connector = new cloud_sql_connector_1.Connector();
|
|
38
|
+
const clientOpts = await connector.getOptions({
|
|
39
|
+
instanceConnectionName: connectionName,
|
|
40
|
+
ipType: cloud_sql_connector_1.IpAddressTypes.PUBLIC,
|
|
41
|
+
authType: cloud_sql_connector_1.AuthTypes.IAM,
|
|
42
|
+
});
|
|
43
|
+
pool = new pg.Pool(Object.assign(Object.assign({}, clientOpts), { user: opts.username, database: opts.databaseId, max: 1 }));
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
default: {
|
|
47
|
+
if (!opts.password) {
|
|
48
|
+
throw new error_1.FirebaseError(`Cannot connect as BUILT_IN user without a password.`);
|
|
49
|
+
}
|
|
50
|
+
connector = new cloud_sql_connector_1.Connector({
|
|
51
|
+
auth: new fbToolsAuthClient_1.FBToolsAuthClient(),
|
|
52
|
+
});
|
|
53
|
+
const clientOpts = await connector.getOptions({
|
|
54
|
+
instanceConnectionName: connectionName,
|
|
55
|
+
ipType: cloud_sql_connector_1.IpAddressTypes.PUBLIC,
|
|
56
|
+
});
|
|
57
|
+
pool = new pg.Pool(Object.assign(Object.assign({}, clientOpts), { user: opts.username, password: opts.password, database: opts.databaseId, max: 1 }));
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
for (const s of sqlStatements) {
|
|
62
|
+
logFn(`Executing: '${s}' as ${opts.username}`);
|
|
63
|
+
try {
|
|
64
|
+
await pool.query(s);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
throw new error_1.FirebaseError(`Error executing ${err}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
await pool.end();
|
|
71
|
+
connector.close();
|
|
72
|
+
}
|
|
73
|
+
exports.execute = execute;
|
|
74
|
+
async function setupIAMUser(instanceId, databaseId, options) {
|
|
75
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
76
|
+
const account = await (0, requireAuth_1.requireAuth)(options);
|
|
77
|
+
if (!account) {
|
|
78
|
+
throw new error_1.FirebaseError("No account to set up! Run `firebase login` or set Application Default Credentials");
|
|
79
|
+
}
|
|
80
|
+
const setupUser = "firebasesuperuser";
|
|
81
|
+
const temporaryPassword = utils.generateId(20);
|
|
82
|
+
await cloudSqlAdminClient.createUser(projectId, instanceId, "BUILT_IN", setupUser, temporaryPassword);
|
|
83
|
+
const { user, mode } = toDatabaseUser(account);
|
|
84
|
+
await cloudSqlAdminClient.createUser(projectId, instanceId, mode, user);
|
|
85
|
+
const grants = [
|
|
86
|
+
`do
|
|
87
|
+
$$
|
|
88
|
+
begin
|
|
89
|
+
if not exists (select FROM pg_catalog.pg_roles
|
|
90
|
+
WHERE rolname = '${firebaseowner(databaseId)}') then
|
|
91
|
+
CREATE ROLE "${firebaseowner(databaseId)}" WITH ADMIN "${setupUser}";
|
|
92
|
+
end if;
|
|
93
|
+
end
|
|
94
|
+
$$
|
|
95
|
+
;`,
|
|
96
|
+
`GRANT ALL PRIVILEGES ON DATABASE "${databaseId}" TO "${firebaseowner(databaseId)}"`,
|
|
97
|
+
`GRANT cloudsqlsuperuser TO "${firebaseowner(databaseId)}"`,
|
|
98
|
+
`GRANT "${firebaseowner(databaseId)}" TO "${setupUser}"`,
|
|
99
|
+
`GRANT "${firebaseowner(databaseId)}" TO "${user}"`,
|
|
100
|
+
`ALTER SCHEMA public OWNER TO "${firebaseowner(databaseId)}"`,
|
|
101
|
+
`GRANT USAGE ON SCHEMA "public" TO PUBLIC`,
|
|
102
|
+
`GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA "public" TO PUBLIC`,
|
|
103
|
+
`GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA "public" TO PUBLIC`,
|
|
104
|
+
];
|
|
105
|
+
await execute(grants, {
|
|
106
|
+
projectId,
|
|
107
|
+
instanceId,
|
|
108
|
+
databaseId,
|
|
109
|
+
username: setupUser,
|
|
110
|
+
password: temporaryPassword,
|
|
111
|
+
silent: true,
|
|
112
|
+
});
|
|
113
|
+
return user;
|
|
114
|
+
}
|
|
115
|
+
exports.setupIAMUser = setupIAMUser;
|
|
116
|
+
function firebaseowner(databaseId) {
|
|
117
|
+
return `firebaseowner_${databaseId}_public`;
|
|
118
|
+
}
|
|
119
|
+
exports.firebaseowner = firebaseowner;
|
|
120
|
+
function toDatabaseUser(account) {
|
|
121
|
+
let mode = "CLOUD_IAM_USER";
|
|
122
|
+
let user = account;
|
|
123
|
+
if (account.endsWith(".gserviceaccount.com")) {
|
|
124
|
+
user = account.replace(".gserviceaccount.com", "");
|
|
125
|
+
mode = "CLOUD_IAM_SERVICE_ACCOUNT";
|
|
126
|
+
}
|
|
127
|
+
return { user, mode };
|
|
128
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FBToolsAuthClient = void 0;
|
|
4
|
+
const google_auth_library_1 = require("google-auth-library");
|
|
5
|
+
const apiv2 = require("../../apiv2");
|
|
6
|
+
const error_1 = require("../../error");
|
|
7
|
+
class FBToolsAuthClient extends google_auth_library_1.AuthClient {
|
|
8
|
+
async request(opts) {
|
|
9
|
+
var _a;
|
|
10
|
+
if (!opts.url) {
|
|
11
|
+
throw new error_1.FirebaseError("opts.url was undefined");
|
|
12
|
+
}
|
|
13
|
+
const url = new URL(opts.url);
|
|
14
|
+
const client = new apiv2.Client({
|
|
15
|
+
urlPrefix: url.origin,
|
|
16
|
+
auth: true,
|
|
17
|
+
});
|
|
18
|
+
const res = await client.request({
|
|
19
|
+
method: (_a = opts.method) !== null && _a !== void 0 ? _a : "POST",
|
|
20
|
+
path: url.pathname,
|
|
21
|
+
queryParams: opts.params,
|
|
22
|
+
body: opts.data,
|
|
23
|
+
responseType: opts.responseType,
|
|
24
|
+
});
|
|
25
|
+
return {
|
|
26
|
+
config: opts,
|
|
27
|
+
status: res.status,
|
|
28
|
+
statusText: res.response.statusText,
|
|
29
|
+
data: res.body,
|
|
30
|
+
headers: res.response.headers,
|
|
31
|
+
request: {},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
async getAccessToken() {
|
|
35
|
+
return { token: await apiv2.getAccessToken() };
|
|
36
|
+
}
|
|
37
|
+
async getRequestHeaders() {
|
|
38
|
+
const token = await this.getAccessToken();
|
|
39
|
+
return Object.assign(Object.assign({}, apiv2.STANDARD_HEADERS), { Authorization: `Bearer ${token.token}` });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.FBToolsAuthClient = FBToolsAuthClient;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isProductTosAccepted = exports.getAcceptanceStatus = exports.getTosStatus = exports.APP_CHECK_TOS_ID = exports.APPHOSTING_TOS_ID = void 0;
|
|
4
|
+
const apiv2_1 = require("../apiv2");
|
|
5
|
+
const api_1 = require("../api");
|
|
6
|
+
const error_1 = require("../error");
|
|
7
|
+
const client = new apiv2_1.Client({ urlPrefix: (0, api_1.firedataOrigin)(), auth: true, apiVersion: "v1" });
|
|
8
|
+
exports.APPHOSTING_TOS_ID = "APP_HOSTING_TOS";
|
|
9
|
+
exports.APP_CHECK_TOS_ID = "APP_CHECK";
|
|
10
|
+
async function getTosStatus() {
|
|
11
|
+
const res = await client.get("accessmanagement/tos:getStatus");
|
|
12
|
+
return res.body;
|
|
13
|
+
}
|
|
14
|
+
exports.getTosStatus = getTosStatus;
|
|
15
|
+
function getAcceptanceStatus(response, tosId) {
|
|
16
|
+
const perServiceStatus = response.perServiceStatus.find((tosStatus) => tosStatus.tosId === tosId);
|
|
17
|
+
if (perServiceStatus === undefined) {
|
|
18
|
+
throw new error_1.FirebaseError(`Missing terms of service status for product: ${tosId}`);
|
|
19
|
+
}
|
|
20
|
+
return perServiceStatus.serviceStatus.status;
|
|
21
|
+
}
|
|
22
|
+
exports.getAcceptanceStatus = getAcceptanceStatus;
|
|
23
|
+
function isProductTosAccepted(response, tosId) {
|
|
24
|
+
return getAcceptanceStatus(response, tosId) === "ACCEPTED";
|
|
25
|
+
}
|
|
26
|
+
exports.isProductTosAccepted = isProductTosAccepted;
|
package/lib/gcp/iam.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.testIamPermissions = exports.testResourceIamPermissions = exports.getRole = exports.listServiceAccountKeys = exports.deleteServiceAccount = exports.createServiceAccountKey = exports.getServiceAccount = exports.createServiceAccount = exports.getDefaultComputeEngineServiceAgent = exports.getDefaultCloudBuildServiceAgent = void 0;
|
|
3
|
+
exports.printManualIamConfig = exports.mergeBindings = exports.testIamPermissions = exports.testResourceIamPermissions = exports.getRole = exports.listServiceAccountKeys = exports.deleteServiceAccount = exports.createServiceAccountKey = exports.getServiceAccount = exports.createServiceAccount = exports.getDefaultComputeEngineServiceAgent = exports.getDefaultCloudBuildServiceAgent = void 0;
|
|
4
4
|
const api_1 = require("../api");
|
|
5
5
|
const logger_1 = require("../logger");
|
|
6
6
|
const apiv2_1 = require("../apiv2");
|
|
7
|
+
const utils = require("../utils");
|
|
7
8
|
const apiClient = new apiv2_1.Client({ urlPrefix: (0, api_1.iamOrigin)(), apiVersion: "v1" });
|
|
8
9
|
function getDefaultCloudBuildServiceAgent(projectNumber) {
|
|
9
10
|
return `${projectNumber}@cloudbuild.gserviceaccount.com`;
|
|
@@ -82,3 +83,34 @@ async function testIamPermissions(projectId, permissions) {
|
|
|
82
83
|
return testResourceIamPermissions((0, api_1.resourceManagerOrigin)(), "v1", `projects/${projectId}`, permissions, `projects/${projectId}`);
|
|
83
84
|
}
|
|
84
85
|
exports.testIamPermissions = testIamPermissions;
|
|
86
|
+
function mergeBindings(policy, requiredBindings) {
|
|
87
|
+
let updated = false;
|
|
88
|
+
for (const requiredBinding of requiredBindings) {
|
|
89
|
+
const match = policy.bindings.find((b) => b.role === requiredBinding.role);
|
|
90
|
+
if (!match) {
|
|
91
|
+
updated = true;
|
|
92
|
+
policy.bindings.push(requiredBinding);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
for (const requiredMember of requiredBinding.members) {
|
|
96
|
+
if (!match.members.find((m) => m === requiredMember)) {
|
|
97
|
+
updated = true;
|
|
98
|
+
match.members.push(requiredMember);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return updated;
|
|
103
|
+
}
|
|
104
|
+
exports.mergeBindings = mergeBindings;
|
|
105
|
+
function printManualIamConfig(requiredBindings, projectId, prefix) {
|
|
106
|
+
utils.logLabeledBullet(prefix, "Failed to verify the project has the correct IAM bindings for a successful deployment.", "warn");
|
|
107
|
+
utils.logLabeledBullet(prefix, "You can either re-run this command as a project owner or manually run the following set of `gcloud` commands:", "warn");
|
|
108
|
+
for (const binding of requiredBindings) {
|
|
109
|
+
for (const member of binding.members) {
|
|
110
|
+
utils.logLabeledBullet(prefix, `\`gcloud projects add-iam-policy-binding ${projectId} ` +
|
|
111
|
+
`--member=${member} ` +
|
|
112
|
+
`--role=${binding.role}\``, "warn");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.printManualIamConfig = printManualIamConfig;
|
package/lib/gcp/secretManager.js
CHANGED
|
@@ -51,7 +51,7 @@ async function getSecretMetadata(projectId, secretName, version) {
|
|
|
51
51
|
const secretInfo = {};
|
|
52
52
|
try {
|
|
53
53
|
secretInfo.secret = await getSecret(projectId, secretName);
|
|
54
|
-
secretInfo.secretVersion = getSecretVersion(projectId, secretName, version);
|
|
54
|
+
secretInfo.secretVersion = await getSecretVersion(projectId, secretName, version);
|
|
55
55
|
}
|
|
56
56
|
catch (err) {
|
|
57
57
|
if (err.status !== 404) {
|
|
@@ -43,6 +43,7 @@ async function interactiveCreateHostingSite(siteId, appId, options) {
|
|
|
43
43
|
if (options.nonInteractive) {
|
|
44
44
|
throw err;
|
|
45
45
|
}
|
|
46
|
+
id = "";
|
|
46
47
|
suggestion = getSuggestionFromError(err);
|
|
47
48
|
}
|
|
48
49
|
}
|
|
@@ -71,5 +72,8 @@ function getSuggestionFromError(err) {
|
|
|
71
72
|
return match[1];
|
|
72
73
|
}
|
|
73
74
|
}
|
|
75
|
+
else {
|
|
76
|
+
(0, utils_1.logWarning)(err.message);
|
|
77
|
+
}
|
|
74
78
|
return;
|
|
75
79
|
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.doSetup = void 0;
|
|
4
|
+
const path_1 = require("path");
|
|
5
|
+
const prompt_1 = require("../../../prompt");
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const provisionCloudSql_1 = require("../../../dataconnect/provisionCloudSql");
|
|
8
|
+
const cloudsql = require("../../../gcp/cloudsql/cloudsqladmin");
|
|
9
|
+
const ensureApis_1 = require("../../../dataconnect/ensureApis");
|
|
10
|
+
const client_1 = require("../../../dataconnect/client");
|
|
11
|
+
const TEMPLATE_ROOT = (0, path_1.resolve)(__dirname, "../../../../templates/init/dataconnect/");
|
|
12
|
+
const DATACONNECT_YAML_TEMPLATE = (0, fs_1.readFileSync)((0, path_1.join)(TEMPLATE_ROOT, "dataconnect.yaml"), "utf8");
|
|
13
|
+
const CONNECTOR_YAML_TEMPLATE = (0, fs_1.readFileSync)((0, path_1.join)(TEMPLATE_ROOT, "connector.yaml"), "utf8");
|
|
14
|
+
const SCHEMA_TEMPLATE = (0, fs_1.readFileSync)((0, path_1.join)(TEMPLATE_ROOT, "schema.gql"), "utf8");
|
|
15
|
+
const QUERIES_TEMPLATE = (0, fs_1.readFileSync)((0, path_1.join)(TEMPLATE_ROOT, "queries.gql"), "utf8");
|
|
16
|
+
const MUTATIONS_TEMPLATE = (0, fs_1.readFileSync)((0, path_1.join)(TEMPLATE_ROOT, "mutations.gql"), "utf8");
|
|
17
|
+
async function doSetup(setup, config) {
|
|
18
|
+
var _a, _b, _c;
|
|
19
|
+
if (setup.projectId) {
|
|
20
|
+
await (0, ensureApis_1.ensureApis)(setup.projectId);
|
|
21
|
+
}
|
|
22
|
+
const serviceId = await (0, prompt_1.promptOnce)({
|
|
23
|
+
message: "What ID would you like to use for this service?",
|
|
24
|
+
type: "input",
|
|
25
|
+
default: "dataconnect",
|
|
26
|
+
});
|
|
27
|
+
let locationOptions = [
|
|
28
|
+
{ name: "us-central1", value: "us-central1" },
|
|
29
|
+
{ name: "europe-north1", value: "europe-north1" },
|
|
30
|
+
{ name: "europe-central2", value: "europe-central2" },
|
|
31
|
+
{ name: "europe-west1", value: "europe-west1" },
|
|
32
|
+
{ name: "southamerica-west1", value: "southamerica-west1" },
|
|
33
|
+
{ name: "us-east4", value: "us-east4" },
|
|
34
|
+
{ name: "us-west1", value: "us-west1" },
|
|
35
|
+
{ name: "asia-southeast1", value: "asia-southeast1" },
|
|
36
|
+
];
|
|
37
|
+
if (setup.projectId) {
|
|
38
|
+
const locations = await (0, client_1.listLocations)(setup.projectId);
|
|
39
|
+
locationOptions = locations.map((l) => {
|
|
40
|
+
return { name: l, value: l };
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const locationId = await (0, prompt_1.promptOnce)({
|
|
44
|
+
message: "What location would you like to deploy this service into?",
|
|
45
|
+
type: "list",
|
|
46
|
+
choices: locationOptions,
|
|
47
|
+
});
|
|
48
|
+
const connectorId = await (0, prompt_1.promptOnce)({
|
|
49
|
+
message: "What ID would you like to use for your connector?",
|
|
50
|
+
type: "input",
|
|
51
|
+
default: "my-connector",
|
|
52
|
+
});
|
|
53
|
+
const dir = config.get("dataconnect.source") || "dataconnect";
|
|
54
|
+
if (!config.has("dataconnect")) {
|
|
55
|
+
config.set("dataconnect.source", dir);
|
|
56
|
+
config.set("dataconnect.location", locationId);
|
|
57
|
+
}
|
|
58
|
+
let cloudSqlInstanceId = "";
|
|
59
|
+
let newInstance = false;
|
|
60
|
+
if (setup.projectId) {
|
|
61
|
+
const instances = await cloudsql.listInstances(setup.projectId);
|
|
62
|
+
const instancesInLocation = instances.filter((i) => i.region === locationId);
|
|
63
|
+
const choices = instancesInLocation.map((i) => {
|
|
64
|
+
return { name: i.name, value: i.name };
|
|
65
|
+
});
|
|
66
|
+
choices.push({ name: "Create a new instance", value: "" });
|
|
67
|
+
if (instancesInLocation.length) {
|
|
68
|
+
cloudSqlInstanceId = await (0, prompt_1.promptOnce)({
|
|
69
|
+
message: `Which CloudSSQL in ${locationId} would you like to use?`,
|
|
70
|
+
type: "list",
|
|
71
|
+
choices,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (cloudSqlInstanceId === "") {
|
|
76
|
+
newInstance = true;
|
|
77
|
+
cloudSqlInstanceId = await (0, prompt_1.promptOnce)({
|
|
78
|
+
message: `What ID would you like to use for your new CloudSQL instance?`,
|
|
79
|
+
type: "input",
|
|
80
|
+
default: `dataconnect-test`,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
let cloudSqlDatabase = "";
|
|
84
|
+
let newDB = false;
|
|
85
|
+
if (!newInstance && setup.projectId) {
|
|
86
|
+
const dbs = await cloudsql.listDatabases(setup.projectId, cloudSqlInstanceId);
|
|
87
|
+
const choices = dbs.map((d) => {
|
|
88
|
+
return { name: d.name, value: d.name };
|
|
89
|
+
});
|
|
90
|
+
choices.push({ name: "Create a new database", value: "" });
|
|
91
|
+
if (dbs.length) {
|
|
92
|
+
cloudSqlDatabase = await (0, prompt_1.promptOnce)({
|
|
93
|
+
message: `Which database in ${cloudSqlInstanceId} would you like to use?`,
|
|
94
|
+
type: "list",
|
|
95
|
+
choices,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (cloudSqlDatabase === "") {
|
|
100
|
+
newDB = true;
|
|
101
|
+
cloudSqlDatabase = await (0, prompt_1.promptOnce)({
|
|
102
|
+
message: `What ID would you like to use for your new database in ${cloudSqlInstanceId}?`,
|
|
103
|
+
type: "input",
|
|
104
|
+
default: `dataconnect`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const defaultConnectionString = (_c = (_b = (_a = setup.rcfile.dataconnectEmulatorConfig) === null || _a === void 0 ? void 0 : _a.postgres) === null || _b === void 0 ? void 0 : _b.localConnectionString) !== null && _c !== void 0 ? _c : "postgresql://localhost:5432?sslmode=disable";
|
|
108
|
+
const localConnectionString = await (0, prompt_1.promptOnce)({
|
|
109
|
+
type: "input",
|
|
110
|
+
name: "localConnectionString",
|
|
111
|
+
message: `What is the connection string of the local Postgres instance you would like to use with the Data Connect emulator?`,
|
|
112
|
+
default: defaultConnectionString,
|
|
113
|
+
});
|
|
114
|
+
setup.rcfile.dataconnectEmulatorConfig = { postgres: { localConnectionString } };
|
|
115
|
+
const subbedDataconnectYaml = subValues(DATACONNECT_YAML_TEMPLATE, {
|
|
116
|
+
serviceId,
|
|
117
|
+
cloudSqlInstanceId,
|
|
118
|
+
cloudSqlDatabase,
|
|
119
|
+
connectorId,
|
|
120
|
+
});
|
|
121
|
+
const subbedConnectorYaml = subValues(CONNECTOR_YAML_TEMPLATE, {
|
|
122
|
+
serviceId,
|
|
123
|
+
cloudSqlInstanceId,
|
|
124
|
+
cloudSqlDatabase,
|
|
125
|
+
connectorId,
|
|
126
|
+
});
|
|
127
|
+
await config.askWriteProjectFile((0, path_1.join)(dir, "dataconnect.yaml"), subbedDataconnectYaml);
|
|
128
|
+
await config.askWriteProjectFile((0, path_1.join)(dir, "connector", "connector.yaml"), subbedConnectorYaml);
|
|
129
|
+
await config.askWriteProjectFile((0, path_1.join)(dir, "schema", "schema.gql"), SCHEMA_TEMPLATE);
|
|
130
|
+
await config.askWriteProjectFile((0, path_1.join)(dir, "connector", "queries.gql"), QUERIES_TEMPLATE);
|
|
131
|
+
await config.askWriteProjectFile((0, path_1.join)(dir, "connector", "mutations.gql"), MUTATIONS_TEMPLATE);
|
|
132
|
+
if (setup.projectId &&
|
|
133
|
+
(newInstance || newDB) &&
|
|
134
|
+
(await (0, prompt_1.confirm)({
|
|
135
|
+
message: "Would you like to provision your CloudSQL instance and database now? This will take a few minutes.",
|
|
136
|
+
default: true,
|
|
137
|
+
}))) {
|
|
138
|
+
await (0, provisionCloudSql_1.provisionCloudSql)({
|
|
139
|
+
projectId: setup.projectId,
|
|
140
|
+
locationId,
|
|
141
|
+
instanceId: cloudSqlInstanceId,
|
|
142
|
+
databaseId: cloudSqlDatabase,
|
|
143
|
+
enableGoogleMlIntegration: false,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
exports.doSetup = doSetup;
|
|
148
|
+
function subValues(template, replacementValues) {
|
|
149
|
+
const replacements = {
|
|
150
|
+
serviceId: "__serviceId__",
|
|
151
|
+
cloudSqlDatabase: "__cloudSqlDatabase__",
|
|
152
|
+
cloudSqlInstanceId: "__cloudSqlInstanceId__",
|
|
153
|
+
connectorId: "__connectorId__",
|
|
154
|
+
};
|
|
155
|
+
let replaced = template;
|
|
156
|
+
for (const [k, v] of Object.entries(replacementValues)) {
|
|
157
|
+
replaced = replaced.replace(replacements[k], v);
|
|
158
|
+
}
|
|
159
|
+
return replaced;
|
|
160
|
+
}
|
|
@@ -9,6 +9,7 @@ const types_1 = require("../../emulator/types");
|
|
|
9
9
|
const constants_1 = require("../../emulator/constants");
|
|
10
10
|
const downloadableEmulators_1 = require("../../emulator/downloadableEmulators");
|
|
11
11
|
async function doSetup(setup, config) {
|
|
12
|
+
var _a, _b, _c;
|
|
12
13
|
const choices = types_1.ALL_SERVICE_EMULATORS.map((e) => {
|
|
13
14
|
return {
|
|
14
15
|
value: e,
|
|
@@ -76,6 +77,18 @@ async function doSetup(setup, config) {
|
|
|
76
77
|
ui.port = isNaN(portNum) ? undefined : portNum;
|
|
77
78
|
}
|
|
78
79
|
}
|
|
80
|
+
if (selections.emulators.includes(types_1.Emulators.DATACONNECT)) {
|
|
81
|
+
const defaultConnectionString = (_c = (_b = (_a = setup.rcfile.dataconnectEmulatorConfig) === null || _a === void 0 ? void 0 : _a.postgres) === null || _b === void 0 ? void 0 : _b.localConnectionString) !== null && _c !== void 0 ? _c : "postgresql://localhost:5432";
|
|
82
|
+
const localConnectionString = await (0, prompt_1.prompt)(setup.config.emulators[types_1.Emulators.DATACONNECT], [
|
|
83
|
+
{
|
|
84
|
+
type: "input",
|
|
85
|
+
name: "localConnectionString",
|
|
86
|
+
message: `What is the connection string of the local Postgres instance you would like to use with the Data Connect emulator?`,
|
|
87
|
+
default: defaultConnectionString,
|
|
88
|
+
},
|
|
89
|
+
]);
|
|
90
|
+
setup.rcfile.dataconnectEmulatorConfig = { postgres: { localConnectionString } };
|
|
91
|
+
}
|
|
79
92
|
await (0, prompt_1.prompt)(selections, [
|
|
80
93
|
{
|
|
81
94
|
name: "download",
|
|
@@ -153,13 +153,25 @@ async function languageSetup(setup, config) {
|
|
|
153
153
|
const cbconfig = (0, projectConfig_1.configForCodebase)(setup.config.functions, setup.functions.codebase);
|
|
154
154
|
switch (language) {
|
|
155
155
|
case "javascript":
|
|
156
|
-
cbconfig.ignore = [
|
|
156
|
+
cbconfig.ignore = [
|
|
157
|
+
"node_modules",
|
|
158
|
+
".git",
|
|
159
|
+
"firebase-debug.log",
|
|
160
|
+
"firebase-debug.*.log",
|
|
161
|
+
"*.local",
|
|
162
|
+
];
|
|
157
163
|
break;
|
|
158
164
|
case "typescript":
|
|
159
|
-
cbconfig.ignore = [
|
|
165
|
+
cbconfig.ignore = [
|
|
166
|
+
"node_modules",
|
|
167
|
+
".git",
|
|
168
|
+
"firebase-debug.log",
|
|
169
|
+
"firebase-debug.*.log",
|
|
170
|
+
"*.local",
|
|
171
|
+
];
|
|
160
172
|
break;
|
|
161
173
|
case "python":
|
|
162
|
-
cbconfig.ignore = ["venv", ".git", "firebase-debug.log", "firebase-debug.*.log"];
|
|
174
|
+
cbconfig.ignore = ["venv", ".git", "firebase-debug.log", "firebase-debug.*.log", "*.local"];
|
|
163
175
|
break;
|
|
164
176
|
}
|
|
165
177
|
return require("./" + language).setup(setup, config);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.apphosting = exports.hostingGithub = exports.remoteconfig = exports.project = exports.extensions = exports.emulators = exports.storage = exports.hosting = exports.functions = exports.firestore = exports.database = exports.account = void 0;
|
|
3
|
+
exports.apphosting = exports.dataconnect = exports.hostingGithub = exports.remoteconfig = exports.project = exports.extensions = exports.emulators = exports.storage = exports.hosting = exports.functions = exports.firestore = exports.database = exports.account = void 0;
|
|
4
4
|
var account_1 = require("./account");
|
|
5
5
|
Object.defineProperty(exports, "account", { enumerable: true, get: function () { return account_1.doSetup; } });
|
|
6
6
|
var database_1 = require("./database");
|
|
@@ -23,5 +23,7 @@ var remoteconfig_1 = require("./remoteconfig");
|
|
|
23
23
|
Object.defineProperty(exports, "remoteconfig", { enumerable: true, get: function () { return remoteconfig_1.doSetup; } });
|
|
24
24
|
var github_1 = require("./hosting/github");
|
|
25
25
|
Object.defineProperty(exports, "hostingGithub", { enumerable: true, get: function () { return github_1.initGitHub; } });
|
|
26
|
+
var dataconnect_1 = require("./dataconnect");
|
|
27
|
+
Object.defineProperty(exports, "dataconnect", { enumerable: true, get: function () { return dataconnect_1.doSetup; } });
|
|
26
28
|
var apphosting_1 = require("../../apphosting");
|
|
27
29
|
Object.defineProperty(exports, "apphosting", { enumerable: true, get: function () { return apphosting_1.doSetup; } });
|