firebase-tools 13.15.4 → 13.17.0
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 +3 -1
- package/lib/apphosting/index.js +2 -1
- package/lib/commands/dataconnect-sql-diff.js +2 -1
- package/lib/commands/dataconnect-sql-migrate.js +0 -1
- package/lib/commands/firestore-databases-restore.js +39 -1
- package/lib/dataconnect/client.js +2 -1
- package/lib/dataconnect/schemaMigration.js +134 -48
- package/lib/dataconnect/types.js +1 -0
- package/lib/deploy/functions/prepare.js +0 -8
- package/lib/deploy/functions/release/planner.js +2 -1
- package/lib/emulator/apphosting/index.js +38 -0
- package/lib/emulator/apphosting/serve.js +26 -0
- package/lib/emulator/auth/handlers.js +27 -0
- package/lib/emulator/auth/operations.js +41 -3
- package/lib/emulator/auth/state.js +2 -1
- package/lib/emulator/constants.js +10 -0
- package/lib/emulator/controller.js +24 -3
- package/lib/emulator/downloadableEmulators.js +12 -12
- package/lib/emulator/env.js +3 -0
- package/lib/emulator/functionsEmulator.js +19 -0
- package/lib/emulator/functionsEmulatorShared.js +17 -0
- package/lib/emulator/portUtils.js +2 -0
- package/lib/emulator/registry.js +2 -0
- package/lib/emulator/taskQueue.js +341 -0
- package/lib/emulator/tasksEmulator.js +238 -0
- package/lib/emulator/types.js +6 -0
- package/lib/experiments.js +4 -0
- package/lib/firestore/api.js +2 -1
- package/lib/firestore/options.js +7 -0
- package/lib/gcp/cloudsql/connect.js +49 -37
- package/lib/gcp/cloudsql/permissions.js +160 -0
- package/lib/init/features/dataconnect/index.js +5 -2
- package/lib/utils.js +9 -1
- package/package.json +1 -1
- package/schema/firebase-config.json +24 -0
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.toDatabaseUser = exports.setupIAMUsers = exports.getIAMUser = exports.getDataConnectP4SA = exports.executeSqlCmdsAsSuperUser = exports.executeSqlCmdsAsIamUser = exports.execute = void 0;
|
|
4
4
|
const pg = require("pg");
|
|
5
5
|
const cloud_sql_connector_1 = require("@google-cloud/cloud-sql-connector");
|
|
6
6
|
const requireAuth_1 = require("../../requireAuth");
|
|
7
7
|
const projectUtils_1 = require("../../projectUtils");
|
|
8
|
+
const api_1 = require("../../api");
|
|
8
9
|
const cloudSqlAdminClient = require("./cloudsqladmin");
|
|
9
10
|
const utils = require("../../utils");
|
|
10
11
|
const logger_1 = require("../../logger");
|
|
11
12
|
const error_1 = require("../../error");
|
|
12
13
|
const fbToolsAuthClient_1 = require("./fbToolsAuthClient");
|
|
14
|
+
const permissions_1 = require("./permissions");
|
|
13
15
|
async function execute(sqlStatements, opts) {
|
|
14
16
|
const logFn = opts.silent ? logger_1.logger.debug : logger_1.logger.info;
|
|
15
17
|
const instance = await cloudSqlAdminClient.getInstance(opts.projectId, opts.instanceId);
|
|
@@ -74,52 +76,61 @@ async function execute(sqlStatements, opts) {
|
|
|
74
76
|
connector.close();
|
|
75
77
|
}
|
|
76
78
|
exports.execute = execute;
|
|
77
|
-
async function
|
|
79
|
+
async function executeSqlCmdsAsIamUser(options, instanceId, databaseId, cmds, silent = false) {
|
|
78
80
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
81
|
+
const { user: iamUser } = await getIAMUser(options);
|
|
82
|
+
return await execute(cmds, {
|
|
83
|
+
projectId,
|
|
84
|
+
instanceId,
|
|
85
|
+
databaseId,
|
|
86
|
+
username: iamUser,
|
|
87
|
+
silent: silent,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
exports.executeSqlCmdsAsIamUser = executeSqlCmdsAsIamUser;
|
|
91
|
+
async function executeSqlCmdsAsSuperUser(options, instanceId, databaseId, cmds, silent = false) {
|
|
92
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
93
|
+
const superuser = "firebasesuperuser";
|
|
94
|
+
const temporaryPassword = utils.generateId(20);
|
|
95
|
+
await cloudSqlAdminClient.createUser(projectId, instanceId, "BUILT_IN", superuser, temporaryPassword);
|
|
96
|
+
return await execute([`SET ROLE = cloudsqlsuperuser`, ...cmds], {
|
|
97
|
+
projectId,
|
|
98
|
+
instanceId,
|
|
99
|
+
databaseId,
|
|
100
|
+
username: superuser,
|
|
101
|
+
password: temporaryPassword,
|
|
102
|
+
silent: silent,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
exports.executeSqlCmdsAsSuperUser = executeSqlCmdsAsSuperUser;
|
|
106
|
+
function getDataConnectP4SA(projectNumber) {
|
|
107
|
+
return `service-${projectNumber}@${(0, api_1.dataconnectP4SADomain)()}`;
|
|
108
|
+
}
|
|
109
|
+
exports.getDataConnectP4SA = getDataConnectP4SA;
|
|
110
|
+
async function getIAMUser(options) {
|
|
79
111
|
const account = await (0, requireAuth_1.requireAuth)(options);
|
|
80
112
|
if (!account) {
|
|
81
113
|
throw new error_1.FirebaseError("No account to set up! Run `firebase login` or set Application Default Credentials");
|
|
82
114
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
115
|
+
return toDatabaseUser(account);
|
|
116
|
+
}
|
|
117
|
+
exports.getIAMUser = getIAMUser;
|
|
118
|
+
async function setupIAMUsers(instanceId, databaseId, options) {
|
|
119
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
120
|
+
const { user, mode } = await getIAMUser(options);
|
|
87
121
|
await cloudSqlAdminClient.createUser(projectId, instanceId, mode, user);
|
|
122
|
+
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
123
|
+
const { user: fdcP4SAUser, mode: fdcP4SAmode } = toDatabaseUser(getDataConnectP4SA(projectNumber));
|
|
124
|
+
await cloudSqlAdminClient.createUser(projectId, instanceId, fdcP4SAmode, fdcP4SAUser);
|
|
125
|
+
await (0, permissions_1.setupSQLPermissions)(instanceId, databaseId, options, true);
|
|
88
126
|
const grants = [
|
|
89
|
-
`
|
|
90
|
-
|
|
91
|
-
begin
|
|
92
|
-
if not exists (select FROM pg_catalog.pg_roles
|
|
93
|
-
WHERE rolname = '${firebaseowner(databaseId)}') then
|
|
94
|
-
CREATE ROLE "${firebaseowner(databaseId)}" WITH ADMIN "${setupUser}";
|
|
95
|
-
end if;
|
|
96
|
-
end
|
|
97
|
-
$$
|
|
98
|
-
;`,
|
|
99
|
-
`GRANT ALL PRIVILEGES ON DATABASE "${databaseId}" TO "${firebaseowner(databaseId)}"`,
|
|
100
|
-
`GRANT cloudsqlsuperuser TO "${firebaseowner(databaseId)}"`,
|
|
101
|
-
`GRANT "${firebaseowner(databaseId)}" TO "${setupUser}"`,
|
|
102
|
-
`GRANT "${firebaseowner(databaseId)}" TO "${user}"`,
|
|
103
|
-
`ALTER SCHEMA public OWNER TO "${firebaseowner(databaseId)}"`,
|
|
104
|
-
`GRANT USAGE ON SCHEMA "public" TO PUBLIC`,
|
|
105
|
-
`GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA "public" TO PUBLIC`,
|
|
106
|
-
`GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA "public" TO PUBLIC`,
|
|
127
|
+
`GRANT "${(0, permissions_1.firebaseowner)(databaseId)}" TO "${user}"`,
|
|
128
|
+
`GRANT "${(0, permissions_1.firebasewriter)(databaseId)}" TO "${fdcP4SAUser}"`,
|
|
107
129
|
];
|
|
108
|
-
await
|
|
109
|
-
projectId,
|
|
110
|
-
instanceId,
|
|
111
|
-
databaseId,
|
|
112
|
-
username: setupUser,
|
|
113
|
-
password: temporaryPassword,
|
|
114
|
-
silent: true,
|
|
115
|
-
});
|
|
130
|
+
await executeSqlCmdsAsSuperUser(options, instanceId, databaseId, grants, true);
|
|
116
131
|
return user;
|
|
117
132
|
}
|
|
118
|
-
exports.
|
|
119
|
-
function firebaseowner(databaseId) {
|
|
120
|
-
return `firebaseowner_${databaseId}_public`;
|
|
121
|
-
}
|
|
122
|
-
exports.firebaseowner = firebaseowner;
|
|
133
|
+
exports.setupIAMUsers = setupIAMUsers;
|
|
123
134
|
function toDatabaseUser(account) {
|
|
124
135
|
let mode = "CLOUD_IAM_USER";
|
|
125
136
|
let user = account;
|
|
@@ -129,3 +140,4 @@ function toDatabaseUser(account) {
|
|
|
129
140
|
}
|
|
130
141
|
return { user, mode };
|
|
131
142
|
}
|
|
143
|
+
exports.toDatabaseUser = toDatabaseUser;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setupSQLPermissions = exports.iamUserIsCSQLAdmin = exports.checkSQLRoleIsGranted = exports.firebasewriter = exports.firebasereader = exports.firebaseowner = void 0;
|
|
4
|
+
const projectUtils_1 = require("../../projectUtils");
|
|
5
|
+
const connect_1 = require("./connect");
|
|
6
|
+
const iam_1 = require("../iam");
|
|
7
|
+
const logger_1 = require("../../logger");
|
|
8
|
+
const lodash_1 = require("lodash");
|
|
9
|
+
const error_1 = require("../../error");
|
|
10
|
+
function firebaseowner(databaseId) {
|
|
11
|
+
return `firebaseowner_${databaseId}_public`;
|
|
12
|
+
}
|
|
13
|
+
exports.firebaseowner = firebaseowner;
|
|
14
|
+
function firebasereader(databaseId) {
|
|
15
|
+
return `firebasereader_${databaseId}_public`;
|
|
16
|
+
}
|
|
17
|
+
exports.firebasereader = firebasereader;
|
|
18
|
+
function firebasewriter(databaseId) {
|
|
19
|
+
return `firebasewriter_${databaseId}_public`;
|
|
20
|
+
}
|
|
21
|
+
exports.firebasewriter = firebasewriter;
|
|
22
|
+
async function checkSQLRoleIsGranted(options, instanceId, databaseId, grantedRole, granteeRole) {
|
|
23
|
+
const checkCmd = `
|
|
24
|
+
DO $$
|
|
25
|
+
DECLARE
|
|
26
|
+
role_count INTEGER;
|
|
27
|
+
BEGIN
|
|
28
|
+
-- Count the number of rows matching the criteria
|
|
29
|
+
SELECT COUNT(*)
|
|
30
|
+
INTO role_count
|
|
31
|
+
FROM
|
|
32
|
+
pg_auth_members m
|
|
33
|
+
JOIN
|
|
34
|
+
pg_roles grantee ON grantee.oid = m.member
|
|
35
|
+
JOIN
|
|
36
|
+
pg_roles granted ON granted.oid = m.roleid
|
|
37
|
+
JOIN
|
|
38
|
+
pg_roles grantor ON grantor.oid = m.grantor
|
|
39
|
+
WHERE
|
|
40
|
+
granted.rolname = '${grantedRole}'
|
|
41
|
+
AND grantee.rolname = '${granteeRole}';
|
|
42
|
+
|
|
43
|
+
-- If no rows were found, raise an exception
|
|
44
|
+
IF role_count = 0 THEN
|
|
45
|
+
RAISE EXCEPTION 'Role "%", is not granted to role "%".', '${grantedRole}', '${granteeRole}';
|
|
46
|
+
END IF;
|
|
47
|
+
END $$;
|
|
48
|
+
`;
|
|
49
|
+
try {
|
|
50
|
+
await (0, connect_1.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, [checkCmd], true);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
if (e instanceof error_1.FirebaseError && e.message.includes("not granted to role")) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
logger_1.logger.error(`Role Check Failed: ${e}`);
|
|
58
|
+
throw e;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.checkSQLRoleIsGranted = checkSQLRoleIsGranted;
|
|
62
|
+
async function iamUserIsCSQLAdmin(options) {
|
|
63
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
64
|
+
const requiredPermissions = [
|
|
65
|
+
"cloudsql.instances.connect",
|
|
66
|
+
"cloudsql.instances.get",
|
|
67
|
+
"cloudsql.users.create",
|
|
68
|
+
"cloudsql.users.update",
|
|
69
|
+
];
|
|
70
|
+
try {
|
|
71
|
+
const iamResult = await (0, iam_1.testIamPermissions)(projectId, requiredPermissions);
|
|
72
|
+
return iamResult.passed;
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
logger_1.logger.debug(`[iam] error while checking permissions, command may fail: ${err}`);
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exports.iamUserIsCSQLAdmin = iamUserIsCSQLAdmin;
|
|
80
|
+
function ownerRolePermissions(databaseId, superuser, schema) {
|
|
81
|
+
const firebaseOwnerRole = firebaseowner(databaseId);
|
|
82
|
+
return [
|
|
83
|
+
`do
|
|
84
|
+
$$
|
|
85
|
+
begin
|
|
86
|
+
if not exists (select FROM pg_catalog.pg_roles
|
|
87
|
+
WHERE rolname = '${firebaseOwnerRole}') then
|
|
88
|
+
CREATE ROLE "${firebaseOwnerRole}" WITH ADMIN "${superuser}";
|
|
89
|
+
end if;
|
|
90
|
+
end
|
|
91
|
+
$$
|
|
92
|
+
;`,
|
|
93
|
+
`GRANT "${firebaseOwnerRole}" TO "cloudsqlsuperuser"`,
|
|
94
|
+
`ALTER SCHEMA "${schema}" OWNER TO "${firebaseOwnerRole}"`,
|
|
95
|
+
`GRANT USAGE ON SCHEMA "${schema}" TO "${firebaseOwnerRole}"`,
|
|
96
|
+
`GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA "${schema}" TO "${firebaseOwnerRole}"`,
|
|
97
|
+
`GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA "${schema}" TO "${firebaseOwnerRole}"`,
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
function writerRolePermissions(databaseId, superuser, schema) {
|
|
101
|
+
const firebaseWriterRole = firebasewriter(databaseId);
|
|
102
|
+
return [
|
|
103
|
+
`do
|
|
104
|
+
$$
|
|
105
|
+
begin
|
|
106
|
+
if not exists (select FROM pg_catalog.pg_roles
|
|
107
|
+
WHERE rolname = '${firebaseWriterRole}') then
|
|
108
|
+
CREATE ROLE "${firebaseWriterRole}" WITH ADMIN "${superuser}";
|
|
109
|
+
end if;
|
|
110
|
+
end
|
|
111
|
+
$$
|
|
112
|
+
;`,
|
|
113
|
+
`GRANT "${firebaseWriterRole}" TO "cloudsqlsuperuser"`,
|
|
114
|
+
`GRANT USAGE ON SCHEMA "${schema}" TO "${firebaseWriterRole}"`,
|
|
115
|
+
`GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE ON ALL TABLES IN SCHEMA "${schema}" TO "${firebaseWriterRole}"`,
|
|
116
|
+
`GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA "${schema}" TO "${firebaseWriterRole}"`,
|
|
117
|
+
`GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA "${schema}" TO "${firebaseWriterRole}"`,
|
|
118
|
+
`SET ROLE = '${firebaseowner(databaseId)}';`,
|
|
119
|
+
`ALTER DEFAULT PRIVILEGES IN SCHEMA "${schema}" GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE ON TABLES TO "${firebaseWriterRole}";`,
|
|
120
|
+
`ALTER DEFAULT PRIVILEGES IN SCHEMA "${schema}" GRANT USAGE ON SEQUENCES TO "${firebaseWriterRole}";`,
|
|
121
|
+
`ALTER DEFAULT PRIVILEGES IN SCHEMA "${schema}" GRANT EXECUTE ON FUNCTIONS TO "${firebaseWriterRole}"`,
|
|
122
|
+
`SET ROLE = cloudsqlsuperuser`,
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
function readerRolePermissions(databaseId, superuser, schema) {
|
|
126
|
+
const firebaseReaderRole = firebasereader(databaseId);
|
|
127
|
+
return [
|
|
128
|
+
`do
|
|
129
|
+
$$
|
|
130
|
+
begin
|
|
131
|
+
if not exists (select FROM pg_catalog.pg_roles
|
|
132
|
+
WHERE rolname = '${firebaseReaderRole}') then
|
|
133
|
+
CREATE ROLE "${firebaseReaderRole}" WITH ADMIN "${superuser}";
|
|
134
|
+
end if;
|
|
135
|
+
end
|
|
136
|
+
$$
|
|
137
|
+
;`,
|
|
138
|
+
`GRANT "${firebaseReaderRole}" TO "cloudsqlsuperuser"`,
|
|
139
|
+
`GRANT USAGE ON SCHEMA "${schema}" TO "${firebaseReaderRole}"`,
|
|
140
|
+
`GRANT SELECT ON ALL TABLES IN SCHEMA "${schema}" TO "${firebaseReaderRole}"`,
|
|
141
|
+
`GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA "${schema}" TO "${firebaseReaderRole}"`,
|
|
142
|
+
`GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA "${schema}" TO "${firebaseReaderRole}"`,
|
|
143
|
+
`SET ROLE = '${firebaseowner(databaseId)}';`,
|
|
144
|
+
`ALTER DEFAULT PRIVILEGES IN SCHEMA "${schema}" GRANT SELECT ON TABLES TO "${firebaseReaderRole}";`,
|
|
145
|
+
`ALTER DEFAULT PRIVILEGES IN SCHEMA "${schema}" GRANT SELECT, USAGE ON SEQUENCES TO "${firebaseReaderRole}";`,
|
|
146
|
+
`ALTER DEFAULT PRIVILEGES IN SCHEMA "${schema}" GRANT EXECUTE ON FUNCTIONS TO "${firebaseReaderRole}"`,
|
|
147
|
+
`SET ROLE = cloudsqlsuperuser`,
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
async function setupSQLPermissions(instanceId, databaseId, options, silent = false) {
|
|
151
|
+
const superuser = "firebasesuperuser";
|
|
152
|
+
const revokes = [];
|
|
153
|
+
if (await checkSQLRoleIsGranted(options, instanceId, databaseId, "cloudsqlsuperuser", firebaseowner(databaseId))) {
|
|
154
|
+
logger_1.logger.warn("Detected cloudsqlsuperuser was previously given to firebase owner, revoking to improve database security.");
|
|
155
|
+
revokes.push(`REVOKE "cloudsqlsuperuser" FROM "${firebaseowner(databaseId)}"`);
|
|
156
|
+
}
|
|
157
|
+
const sqlRoleSetupCmds = (0, lodash_1.concat)(revokes, [`CREATE SCHEMA IF NOT EXISTS "public"`], ownerRolePermissions(databaseId, superuser, "public"), writerRolePermissions(databaseId, superuser, "public"), readerRolePermissions(databaseId, superuser, "public"));
|
|
158
|
+
return (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, sqlRoleSetupCmds, silent);
|
|
159
|
+
}
|
|
160
|
+
exports.setupSQLPermissions = setupSQLPermissions;
|
|
@@ -179,13 +179,16 @@ async function promptForService(setup, info) {
|
|
|
179
179
|
info.schemaGql = choice.schema.source.files;
|
|
180
180
|
}
|
|
181
181
|
info.cloudSqlDatabase = (_d = (_c = choice.schema.primaryDatasource.postgresql) === null || _c === void 0 ? void 0 : _c.database) !== null && _d !== void 0 ? _d : "";
|
|
182
|
-
const connectors = await (0, client_1.listConnectors)(choice.service.name
|
|
182
|
+
const connectors = await (0, client_1.listConnectors)(choice.service.name, [
|
|
183
|
+
"connectors.name",
|
|
184
|
+
"connectors.source.files",
|
|
185
|
+
]);
|
|
183
186
|
if (connectors.length) {
|
|
184
187
|
info.connectors = connectors.map((c) => {
|
|
185
188
|
const id = c.name.split("/").pop();
|
|
186
189
|
return {
|
|
187
190
|
id,
|
|
188
|
-
path: `./${id}`,
|
|
191
|
+
path: connectors.length === 1 ? "./connector" : `./${id}`,
|
|
189
192
|
files: c.source.files || [],
|
|
190
193
|
};
|
|
191
194
|
});
|
package/lib/utils.js
CHANGED
|
@@ -589,6 +589,14 @@ function readSecretValue(prompt, dataFile) {
|
|
|
589
589
|
if (dataFile && dataFile !== "-") {
|
|
590
590
|
input = dataFile;
|
|
591
591
|
}
|
|
592
|
-
|
|
592
|
+
try {
|
|
593
|
+
return Promise.resolve(fs.readFileSync(input, "utf-8"));
|
|
594
|
+
}
|
|
595
|
+
catch (e) {
|
|
596
|
+
if (e.code === "ENOENT") {
|
|
597
|
+
throw new error_1.FirebaseError(`File not found: ${input}`, { original: e });
|
|
598
|
+
}
|
|
599
|
+
throw e;
|
|
600
|
+
}
|
|
593
601
|
}
|
|
594
602
|
exports.readSecretValue = readSecretValue;
|
package/package.json
CHANGED
|
@@ -357,6 +357,18 @@
|
|
|
357
357
|
"emulators": {
|
|
358
358
|
"additionalProperties": false,
|
|
359
359
|
"properties": {
|
|
360
|
+
"apphosting": {
|
|
361
|
+
"additionalProperties": false,
|
|
362
|
+
"properties": {
|
|
363
|
+
"host": {
|
|
364
|
+
"type": "string"
|
|
365
|
+
},
|
|
366
|
+
"port": {
|
|
367
|
+
"type": "number"
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
"type": "object"
|
|
371
|
+
},
|
|
360
372
|
"auth": {
|
|
361
373
|
"additionalProperties": false,
|
|
362
374
|
"properties": {
|
|
@@ -500,6 +512,18 @@
|
|
|
500
512
|
},
|
|
501
513
|
"type": "object"
|
|
502
514
|
},
|
|
515
|
+
"tasks": {
|
|
516
|
+
"additionalProperties": false,
|
|
517
|
+
"properties": {
|
|
518
|
+
"host": {
|
|
519
|
+
"type": "string"
|
|
520
|
+
},
|
|
521
|
+
"port": {
|
|
522
|
+
"type": "number"
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
"type": "object"
|
|
526
|
+
},
|
|
503
527
|
"ui": {
|
|
504
528
|
"additionalProperties": false,
|
|
505
529
|
"properties": {
|