@platformatic/runtime 1.28.0 → 1.29.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/fixtures/management-api/platformatic.json +5 -1
- package/fixtures/management-api/services/service-1/plugin.js +7 -0
- package/fixtures/monorepo/serviceAppWithLogger/platformatic.service.json +1 -4
- package/fixtures/prom-server/platformatic.json +17 -0
- package/fixtures/prom-server/services/service-1/platformatic.json +13 -0
- package/fixtures/prom-server/services/service-1/plugin.js +8 -0
- package/fixtures/prom-server/services/service-2/platformatic.json +12 -0
- package/fixtures/prom-server/services/service-2/plugin.js +8 -0
- package/fixtures/sample-runtime/.env +9 -0
- package/fixtures/sample-runtime/package.json +16 -0
- package/fixtures/sample-runtime/platformatic.json +19 -0
- package/fixtures/sample-runtime/services/rival/package.json +16 -0
- package/fixtures/sample-runtime/services/rival/platformatic.json +33 -0
- package/fixtures/sample-runtime/services/rival/routes/root.js +7 -0
- package/lib/api-client.js +10 -1
- package/lib/api.js +4 -3
- package/lib/app.js +14 -9
- package/lib/build-server.js +3 -2
- package/lib/errors.js +4 -2
- package/lib/generator/runtime-generator.js +126 -3
- package/lib/management-api.js +10 -7
- package/lib/prom-server.js +50 -0
- package/lib/schema.js +38 -1
- package/lib/start.js +14 -6
- package/lib/worker.js +18 -1
- package/package.json +14 -13
|
@@ -5,4 +5,11 @@ module.exports = async function (app) {
|
|
|
5
5
|
app.get('/hello', async () => {
|
|
6
6
|
return { service: 'service-2' }
|
|
7
7
|
})
|
|
8
|
+
|
|
9
|
+
app.get('/large-logs', async (req) => {
|
|
10
|
+
const largeLog = 'a'.repeat(5 * 1024 * 1024)
|
|
11
|
+
for (let i = 0; i < 10; i++) {
|
|
12
|
+
app.log.trace(largeLog)
|
|
13
|
+
}
|
|
14
|
+
})
|
|
8
15
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://platformatic.dev/schemas/v1.22.0/runtime",
|
|
3
|
+
"entrypoint": "service-1",
|
|
4
|
+
"allowCycles": true,
|
|
5
|
+
"hotReload": false,
|
|
6
|
+
"autoload": {
|
|
7
|
+
"path": "./services"
|
|
8
|
+
},
|
|
9
|
+
"server": {
|
|
10
|
+
"hostname": "127.0.0.1",
|
|
11
|
+
"port": 0
|
|
12
|
+
},
|
|
13
|
+
"metrics": {
|
|
14
|
+
"hostname": "127.0.0.1",
|
|
15
|
+
"port": 9090
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
PLT_SERVER_HOSTNAME=0.0.0.0
|
|
2
|
+
PORT=3042
|
|
3
|
+
PLT_SERVER_LOGGER_LEVEL=info
|
|
4
|
+
PLT_RIVAL_TYPESCRIPT=true
|
|
5
|
+
PLT_RIVAL_FST_PLUGIN_OAUTH2_NAME=googleOAuth2
|
|
6
|
+
PLT_RIVAL_FST_PLUGIN_OAUTH2_CREDENTIALS_CLIENT_ID=sample_client_id
|
|
7
|
+
PLT_RIVAL_FST_PLUGIN_OAUTH2_CREDENTIALS_CLIENT_SECRET=sample_client_secret
|
|
8
|
+
PLT_RIVAL_FST_PLUGIN_OAUTH2_REDIRECT_PATH=/login/google
|
|
9
|
+
PLT_RIVAL_FST_PLUGIN_OAUTH2_CALLBACK_URI=http://localhost:3000/login/google/callback
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scripts": {
|
|
3
|
+
"start": "platformatic start",
|
|
4
|
+
"test": "node --test test/*/*.test.js"
|
|
5
|
+
},
|
|
6
|
+
"devDependencies": {
|
|
7
|
+
"fastify": "^4.26.0"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"platformatic": "^1.25.0",
|
|
11
|
+
"@fastify/oauth2": "7.8.0"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": "^18.8.0 || >=20.6.0"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://platformatic.dev/schemas/v1.25.0/runtime",
|
|
3
|
+
"entrypoint": "rival",
|
|
4
|
+
"allowCycles": false,
|
|
5
|
+
"hotReload": true,
|
|
6
|
+
"autoload": {
|
|
7
|
+
"path": "services",
|
|
8
|
+
"exclude": [
|
|
9
|
+
"docs"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
"server": {
|
|
13
|
+
"hostname": "{PLT_SERVER_HOSTNAME}",
|
|
14
|
+
"port": "{PORT}",
|
|
15
|
+
"logger": {
|
|
16
|
+
"level": "{PLT_SERVER_LOGGER_LEVEL}"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scripts": {
|
|
3
|
+
"start": "platformatic start",
|
|
4
|
+
"test": "node --test test/**"
|
|
5
|
+
},
|
|
6
|
+
"devDependencies": {
|
|
7
|
+
"fastify": "^4.26.0"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"platformatic": "^1.25.0",
|
|
11
|
+
"@platformatic/service": "^1.25.0"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": "^18.8.0 || >=20.6.0"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://platformatic.dev/schemas/v1.25.0/service",
|
|
3
|
+
"service": {
|
|
4
|
+
"openapi": true
|
|
5
|
+
},
|
|
6
|
+
"watch": true,
|
|
7
|
+
"plugins": {
|
|
8
|
+
"paths": [
|
|
9
|
+
{
|
|
10
|
+
"path": "./plugins",
|
|
11
|
+
"encapsulate": false
|
|
12
|
+
},
|
|
13
|
+
"./routes"
|
|
14
|
+
],
|
|
15
|
+
"typescript": "{PLT_RIVAL_TYPESCRIPT}",
|
|
16
|
+
"packages": [
|
|
17
|
+
{
|
|
18
|
+
"name": "@fastify/oauth2",
|
|
19
|
+
"options": {
|
|
20
|
+
"name": "{PLT_RIVAL_FST_PLUGIN_OAUTH2_NAME}",
|
|
21
|
+
"credentials": {
|
|
22
|
+
"client": {
|
|
23
|
+
"id": "{PLT_RIVAL_FST_PLUGIN_OAUTH2_CREDENTIALS_CLIENT_ID}",
|
|
24
|
+
"secret": "{PLT_RIVAL_FST_PLUGIN_OAUTH2_CREDENTIALS_CLIENT_SECRET}"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"startRedirectPath": "{PLT_RIVAL_FST_PLUGIN_OAUTH2_REDIRECT_PATH}",
|
|
28
|
+
"callbackUri": "{PLT_RIVAL_FST_PLUGIN_OAUTH2_CALLBACK_URI}"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
}
|
package/lib/api-client.js
CHANGED
|
@@ -29,7 +29,9 @@ class RuntimeApiClient extends EventEmitter {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
async start () {
|
|
32
|
-
|
|
32
|
+
const address = await this.#sendCommand('plt:start-services')
|
|
33
|
+
this.emit('start', address)
|
|
34
|
+
return address
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
async close () {
|
|
@@ -134,6 +136,7 @@ class RuntimeApiClient extends EventEmitter {
|
|
|
134
136
|
(metric) => metric.name === 'nodejs_eventloop_utilization'
|
|
135
137
|
)
|
|
136
138
|
|
|
139
|
+
let p50Value = 0
|
|
137
140
|
let p90Value = 0
|
|
138
141
|
let p95Value = 0
|
|
139
142
|
let p99Value = 0
|
|
@@ -142,6 +145,9 @@ class RuntimeApiClient extends EventEmitter {
|
|
|
142
145
|
const metricName = entrypointMetricsPrefix + 'http_request_all_summary_seconds'
|
|
143
146
|
const httpLatencyMetrics = metrics.find((metric) => metric.name === metricName)
|
|
144
147
|
|
|
148
|
+
p50Value = httpLatencyMetrics.values.find(
|
|
149
|
+
(value) => value.labels.quantile === 0.5
|
|
150
|
+
).value || 0
|
|
145
151
|
p90Value = httpLatencyMetrics.values.find(
|
|
146
152
|
(value) => value.labels.quantile === 0.9
|
|
147
153
|
).value || 0
|
|
@@ -152,6 +158,7 @@ class RuntimeApiClient extends EventEmitter {
|
|
|
152
158
|
(value) => value.labels.quantile === 0.99
|
|
153
159
|
).value || 0
|
|
154
160
|
|
|
161
|
+
p50Value = Math.round(p50Value * 1000)
|
|
155
162
|
p90Value = Math.round(p90Value * 1000)
|
|
156
163
|
p95Value = Math.round(p95Value * 1000)
|
|
157
164
|
p99Value = Math.round(p99Value * 1000)
|
|
@@ -177,6 +184,7 @@ class RuntimeApiClient extends EventEmitter {
|
|
|
177
184
|
oldSpaceSize,
|
|
178
185
|
entrypoint: {
|
|
179
186
|
latency: {
|
|
187
|
+
p50: p50Value,
|
|
180
188
|
p90: p90Value,
|
|
181
189
|
p95: p95Value,
|
|
182
190
|
p99: p99Value
|
|
@@ -232,6 +240,7 @@ class RuntimeApiClient extends EventEmitter {
|
|
|
232
240
|
return once(this.worker, 'exit').then((msg) => {
|
|
233
241
|
clearInterval(this.#metricsTimeout)
|
|
234
242
|
this.#exitCode = msg[0]
|
|
243
|
+
this.emit('close')
|
|
235
244
|
return msg
|
|
236
245
|
})
|
|
237
246
|
}
|
package/lib/api.js
CHANGED
|
@@ -33,7 +33,7 @@ class RuntimeApi {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const app = new PlatformaticApp(service, loaderPort, logger, serviceTelemetryConfig, serverConfig)
|
|
36
|
+
const app = new PlatformaticApp(service, loaderPort, logger, serviceTelemetryConfig, serverConfig, !!config.managementApi)
|
|
37
37
|
|
|
38
38
|
this.#services.set(service.id, app)
|
|
39
39
|
}
|
|
@@ -211,7 +211,8 @@ class RuntimeApi {
|
|
|
211
211
|
const service = this.#services.get(id)
|
|
212
212
|
|
|
213
213
|
if (!service) {
|
|
214
|
-
|
|
214
|
+
const listOfServices = this.#getServices().services.map(svc => svc.id).join(', ')
|
|
215
|
+
throw new errors.ServiceNotFoundError(listOfServices)
|
|
215
216
|
}
|
|
216
217
|
|
|
217
218
|
return service
|
|
@@ -221,7 +222,7 @@ class RuntimeApi {
|
|
|
221
222
|
const service = this.#getServiceById(id)
|
|
222
223
|
const status = service.getStatus()
|
|
223
224
|
|
|
224
|
-
const type = service.config
|
|
225
|
+
const type = service.config?.configType
|
|
225
226
|
const { entrypoint, dependencies, localUrl } = service.appConfig
|
|
226
227
|
const serviceDetails = { id, type, status, localUrl, entrypoint, dependencies }
|
|
227
228
|
|
package/lib/app.js
CHANGED
|
@@ -20,8 +20,9 @@ class PlatformaticApp {
|
|
|
20
20
|
#telemetryConfig
|
|
21
21
|
#serverConfig
|
|
22
22
|
#debouncedRestart
|
|
23
|
+
#hasManagementApi
|
|
23
24
|
|
|
24
|
-
constructor (appConfig, loaderPort, logger, telemetryConfig, serverConfig) {
|
|
25
|
+
constructor (appConfig, loaderPort, logger, telemetryConfig, serverConfig, hasManagementApi) {
|
|
25
26
|
this.appConfig = appConfig
|
|
26
27
|
this.config = null
|
|
27
28
|
this.#hotReload = false
|
|
@@ -31,6 +32,7 @@ class PlatformaticApp {
|
|
|
31
32
|
this.#started = false
|
|
32
33
|
this.#originalWatch = null
|
|
33
34
|
this.#fileWatcher = null
|
|
35
|
+
this.#hasManagementApi = !!hasManagementApi
|
|
34
36
|
this.#logger = logger.child({
|
|
35
37
|
name: this.appConfig.id
|
|
36
38
|
})
|
|
@@ -107,14 +109,17 @@ class PlatformaticApp {
|
|
|
107
109
|
})
|
|
108
110
|
}
|
|
109
111
|
|
|
110
|
-
configManager.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
112
|
+
if (this.#hasManagementApi || configManager.current.metrics) {
|
|
113
|
+
configManager.update({
|
|
114
|
+
...configManager.current,
|
|
115
|
+
metrics: {
|
|
116
|
+
server: 'parent',
|
|
117
|
+
defaultMetrics: { enabled: this.appConfig.entrypoint },
|
|
118
|
+
prefix: snakeCase(this.appConfig.id) + '_',
|
|
119
|
+
...configManager.current.metrics
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
}
|
|
118
123
|
|
|
119
124
|
if (!this.appConfig.entrypoint) {
|
|
120
125
|
configManager.update({
|
package/lib/build-server.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
+
|
|
2
3
|
const ConfigManager = require('@platformatic/config')
|
|
3
4
|
const { platformaticRuntime } = require('./config')
|
|
4
|
-
const {
|
|
5
|
+
const { buildRuntime } = require('./start')
|
|
5
6
|
const { buildServer: buildServerService } = require('@platformatic/service')
|
|
6
7
|
const { loadConfig } = require('./load-config')
|
|
7
8
|
|
|
@@ -35,7 +36,7 @@ async function buildServerRuntime (options = {}) {
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
return
|
|
39
|
+
return buildRuntime(options.configManager)
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
async function buildServer (options) {
|
package/lib/errors.js
CHANGED
|
@@ -7,7 +7,7 @@ const ERROR_PREFIX = 'PLT_RUNTIME'
|
|
|
7
7
|
module.exports = {
|
|
8
8
|
RuntimeExitedError: createError(`${ERROR_PREFIX}_RUNTIME_EXIT`, 'The runtime exited before the operation completed'),
|
|
9
9
|
UnknownRuntimeAPICommandError: createError(`${ERROR_PREFIX}_UNKNOWN_RUNTIME_API_COMMAND`, 'Unknown Runtime API command "%s"'),
|
|
10
|
-
ServiceNotFoundError: createError(`${ERROR_PREFIX}_SERVICE_NOT_FOUND`,
|
|
10
|
+
ServiceNotFoundError: createError(`${ERROR_PREFIX}_SERVICE_NOT_FOUND`, 'Service not found. Available services are: %s'),
|
|
11
11
|
ServiceNotStartedError: createError(`${ERROR_PREFIX}_SERVICE_NOT_STARTED`, "Service with id '%s' is not started"),
|
|
12
12
|
FailedToRetrieveOpenAPISchemaError: createError(`${ERROR_PREFIX}_FAILED_TO_RETRIEVE_OPENAPI_SCHEMA`, 'Failed to retrieve OpenAPI schema for service with id "%s": %s'),
|
|
13
13
|
FailedToRetrieveGraphQLSchemaError: createError(`${ERROR_PREFIX}_FAILED_TO_RETRIEVE_GRAPHQL_SCHEMA`, 'Failed to retrieve GraphQL schema for service with id "%s": %s'),
|
|
@@ -23,5 +23,7 @@ module.exports = {
|
|
|
23
23
|
CannotMapSpecifierToAbsolutePathError: createError(`${ERROR_PREFIX}_CANNOT_MAP_SPECIFIER_TO_ABSOLUTE_PATH`, 'Cannot map "%s" to an absolute path'),
|
|
24
24
|
NodeInspectorFlagsNotSupportedError: createError(`${ERROR_PREFIX}_NODE_INSPECTOR_FLAGS_NOT_SUPPORTED`, 'The Node.js inspector flags are not supported. Please use \'platformatic start --inspect\' instead.'),
|
|
25
25
|
FailedToUnlinkManagementApiSocket: createError(`${ERROR_PREFIX}_FAILED_TO_UNLINK_MANAGEMENT_API_SOCKET`, 'Failed to unlink management API socket "%s"'),
|
|
26
|
-
LogFileNotFound: createError(`${ERROR_PREFIX}_LOG_FILE_NOT_FOUND`, 'Log file with index %s not found', 404)
|
|
26
|
+
LogFileNotFound: createError(`${ERROR_PREFIX}_LOG_FILE_NOT_FOUND`, 'Log file with index %s not found', 404),
|
|
27
|
+
CannotFindGeneratorForTemplateError: createError(`${ERROR_PREFIX}_CANNOT_FIND_GENERATOR_FOR_TEMPLATE`, 'Cannot find a generator for template "%s"'),
|
|
28
|
+
CannotRemoveServiceOnUpdateError: createError(`${ERROR_PREFIX}_CANNOT_REMOVE_SERVICE_ON_UPDATE`, 'Cannot remove service "%s" when updating a Runtime')
|
|
27
29
|
}
|
|
@@ -5,9 +5,15 @@ const { NoEntryPointError, NoServiceNamedError } = require('./errors')
|
|
|
5
5
|
const generateName = require('boring-name-generator')
|
|
6
6
|
const { join } = require('node:path')
|
|
7
7
|
const { envObjectToString } = require('@platformatic/generators/lib/utils')
|
|
8
|
-
const { readFile } = require('node:fs/promises')
|
|
8
|
+
const { readFile, readdir, stat } = require('node:fs/promises')
|
|
9
9
|
const { ConfigManager } = require('@platformatic/config')
|
|
10
10
|
const { platformaticRuntime } = require('../config')
|
|
11
|
+
const ServiceGenerator = require('@platformatic/service/lib/generator/service-generator')
|
|
12
|
+
const DBGenerator = require('@platformatic/db/lib/generator/db-generator')
|
|
13
|
+
const ComposerGenerator = require('@platformatic/composer/lib/generator/composer-generator')
|
|
14
|
+
const { CannotFindGeneratorForTemplateError, CannotRemoveServiceOnUpdateError } = require('../errors')
|
|
15
|
+
const { getServiceTemplateFromSchemaUrl } = require('@platformatic/generators/lib/utils')
|
|
16
|
+
const { DotEnvTool } = require('dotenv-tool')
|
|
11
17
|
|
|
12
18
|
class RuntimeGenerator extends BaseGenerator {
|
|
13
19
|
constructor (opts) {
|
|
@@ -200,8 +206,10 @@ class RuntimeGenerator extends BaseGenerator {
|
|
|
200
206
|
|
|
201
207
|
async writeFiles () {
|
|
202
208
|
await super.writeFiles()
|
|
203
|
-
|
|
204
|
-
|
|
209
|
+
if (!this.config.isUpdating) {
|
|
210
|
+
for (const { service } of this.services) {
|
|
211
|
+
await service.writeFiles()
|
|
212
|
+
}
|
|
205
213
|
}
|
|
206
214
|
}
|
|
207
215
|
|
|
@@ -286,6 +294,121 @@ class RuntimeGenerator extends BaseGenerator {
|
|
|
286
294
|
await service.postInstallActions()
|
|
287
295
|
}
|
|
288
296
|
}
|
|
297
|
+
|
|
298
|
+
getGeneratorForTemplate (templateName) {
|
|
299
|
+
switch (templateName) {
|
|
300
|
+
case '@platformatic/service':
|
|
301
|
+
return ServiceGenerator
|
|
302
|
+
case '@platformatic/db':
|
|
303
|
+
return DBGenerator
|
|
304
|
+
case '@platformatic/composer':
|
|
305
|
+
return ComposerGenerator
|
|
306
|
+
default:
|
|
307
|
+
throw new CannotFindGeneratorForTemplateError(templateName)
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async loadFromDir () {
|
|
312
|
+
const output = {
|
|
313
|
+
services: []
|
|
314
|
+
}
|
|
315
|
+
const runtimePkgConfigFileData = JSON.parse(await readFile(join(this.targetDirectory, 'platformatic.json'), 'utf-8'))
|
|
316
|
+
const servicesPath = join(this.targetDirectory, runtimePkgConfigFileData.autoload.path)
|
|
317
|
+
|
|
318
|
+
// load all services
|
|
319
|
+
const allServices = await readdir(servicesPath)
|
|
320
|
+
for (const s of allServices) {
|
|
321
|
+
// check is a directory
|
|
322
|
+
const currentServicePath = join(servicesPath, s)
|
|
323
|
+
const dirStat = await stat(currentServicePath)
|
|
324
|
+
if (dirStat.isDirectory()) {
|
|
325
|
+
// load the package json file
|
|
326
|
+
const servicePkgJson = JSON.parse(await readFile(join(currentServicePath, 'platformatic.json'), 'utf-8'))
|
|
327
|
+
// get generator for this module
|
|
328
|
+
const template = getServiceTemplateFromSchemaUrl(servicePkgJson.$schema)
|
|
329
|
+
const Generator = this.getGeneratorForTemplate(template)
|
|
330
|
+
const instance = new Generator()
|
|
331
|
+
this.addService(instance, s)
|
|
332
|
+
output.services.push(await instance.loadFromDir(s, this.targetDirectory))
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return output
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async update (newConfig) {
|
|
339
|
+
let allServicesDependencies = {}
|
|
340
|
+
function getDifference (a, b) {
|
|
341
|
+
return a.filter(element => {
|
|
342
|
+
return !b.includes(element)
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
this.config.isUpdating = true
|
|
346
|
+
|
|
347
|
+
// check all services are present with the same template
|
|
348
|
+
const allCurrentServicesNames = this.services.map((s) => s.name)
|
|
349
|
+
const allNewServicesNames = newConfig.services.map((s) => s.name)
|
|
350
|
+
// load dotenv tool
|
|
351
|
+
const envTool = new DotEnvTool({
|
|
352
|
+
path: join(this.targetDirectory, '.env')
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
await envTool.load()
|
|
356
|
+
|
|
357
|
+
const removedServices = getDifference(allCurrentServicesNames, allNewServicesNames)
|
|
358
|
+
if (removedServices.length > 0) {
|
|
359
|
+
throw new CannotRemoveServiceOnUpdateError(removedServices.join(', '))
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// handle new services
|
|
363
|
+
for (const newService of newConfig.services) {
|
|
364
|
+
// create generator for the service
|
|
365
|
+
const ServiceGenerator = this.getGeneratorForTemplate(newService.template)
|
|
366
|
+
const serviceInstance = new ServiceGenerator()
|
|
367
|
+
const baseConfig = {
|
|
368
|
+
isRuntimeContext: true,
|
|
369
|
+
targetDirectory: join(this.targetDirectory, 'services', newService.name),
|
|
370
|
+
serviceName: newService.name
|
|
371
|
+
}
|
|
372
|
+
if (allCurrentServicesNames.includes(newService.name)) {
|
|
373
|
+
// update existing services env values
|
|
374
|
+
// otherwise, is a new service
|
|
375
|
+
baseConfig.isUpdating = true
|
|
376
|
+
}
|
|
377
|
+
serviceInstance.setConfig(baseConfig)
|
|
378
|
+
for (const plug of newService.plugins) {
|
|
379
|
+
await serviceInstance.addPackage(plug)
|
|
380
|
+
for (const opt of plug.options) {
|
|
381
|
+
const key = `PLT_${serviceInstance.config.envPrefix}_${opt.name}`
|
|
382
|
+
const value = opt.value
|
|
383
|
+
if (envTool.hasKey(key)) {
|
|
384
|
+
envTool.updateKey(key, value)
|
|
385
|
+
} else {
|
|
386
|
+
envTool.addKey(key, value)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
allServicesDependencies = { ...allServicesDependencies, ...serviceInstance.config.dependencies }
|
|
391
|
+
await serviceInstance.prepare()
|
|
392
|
+
await serviceInstance.writeFiles()
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// update runtime package.json dependencies
|
|
396
|
+
// read current package.json file
|
|
397
|
+
const currrentPackageJson = JSON.parse(await readFile(join(this.targetDirectory, 'package.json'), 'utf-8'))
|
|
398
|
+
currrentPackageJson.dependencies = {
|
|
399
|
+
...currrentPackageJson.dependencies,
|
|
400
|
+
...allServicesDependencies
|
|
401
|
+
}
|
|
402
|
+
this.addFile({
|
|
403
|
+
path: '',
|
|
404
|
+
file: 'package.json',
|
|
405
|
+
contents: JSON.stringify(currrentPackageJson)
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
await this.writeFiles()
|
|
409
|
+
// save new env
|
|
410
|
+
await envTool.save()
|
|
411
|
+
}
|
|
289
412
|
}
|
|
290
413
|
|
|
291
414
|
module.exports = RuntimeGenerator
|
package/lib/management-api.js
CHANGED
|
@@ -4,6 +4,7 @@ const { tmpdir, platform } = require('node:os')
|
|
|
4
4
|
const { join } = require('node:path')
|
|
5
5
|
const { readFile, mkdir, unlink } = require('node:fs/promises')
|
|
6
6
|
const fastify = require('fastify')
|
|
7
|
+
const ws = require('ws')
|
|
7
8
|
const errors = require('./errors')
|
|
8
9
|
const { pipeLiveLogs, getLogFileStream, getLogIndexes } = require('./logs')
|
|
9
10
|
const platformaticVersion = require('../package.json').version
|
|
@@ -115,30 +116,30 @@ async function createManagementApi (configManager, runtimeApiClient) {
|
|
|
115
116
|
.send(res.body)
|
|
116
117
|
})
|
|
117
118
|
|
|
118
|
-
app.get('/metrics/live', { websocket: true }, async (
|
|
119
|
+
app.get('/metrics/live', { websocket: true }, async (socket) => {
|
|
119
120
|
const cachedMetrics = runtimeApiClient.getCachedMetrics()
|
|
120
121
|
const serializedMetrics = cachedMetrics
|
|
121
122
|
.map((metric) => JSON.stringify(metric))
|
|
122
123
|
.join('\n')
|
|
123
|
-
|
|
124
|
+
socket.send(serializedMetrics)
|
|
124
125
|
|
|
125
126
|
const eventHandler = (metrics) => {
|
|
126
127
|
const serializedMetrics = JSON.stringify(metrics)
|
|
127
|
-
|
|
128
|
+
socket.send(serializedMetrics)
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
runtimeApiClient.on('metrics', eventHandler)
|
|
131
132
|
|
|
132
|
-
|
|
133
|
+
socket.on('error', () => {
|
|
133
134
|
runtimeApiClient.off('metrics', eventHandler)
|
|
134
135
|
})
|
|
135
136
|
|
|
136
|
-
|
|
137
|
+
socket.on('close', () => {
|
|
137
138
|
runtimeApiClient.off('metrics', eventHandler)
|
|
138
139
|
})
|
|
139
140
|
})
|
|
140
141
|
|
|
141
|
-
app.get('/logs/live', { websocket: true }, async (
|
|
142
|
+
app.get('/logs/live', { websocket: true }, async (socket, req) => {
|
|
142
143
|
const startLogIndex = req.query.start ? parseInt(req.query.start) : null
|
|
143
144
|
|
|
144
145
|
if (startLogIndex) {
|
|
@@ -148,7 +149,9 @@ async function createManagementApi (configManager, runtimeApiClient) {
|
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
|
|
152
|
+
const stream = ws.createWebSocketStream(socket)
|
|
153
|
+
|
|
154
|
+
pipeLiveLogs(stream, req.log, startLogIndex)
|
|
152
155
|
})
|
|
153
156
|
|
|
154
157
|
app.get('/logs/indexes', async () => {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fastify = require('fastify')
|
|
4
|
+
|
|
5
|
+
async function startPrometheusServer (runtimeApiClient, opts) {
|
|
6
|
+
const host = opts.hostname ?? '0.0.0.0'
|
|
7
|
+
const port = opts.port ?? 9090
|
|
8
|
+
const metricsEndpoint = opts.endpoint ?? '/metrics'
|
|
9
|
+
const auth = opts.auth ?? null
|
|
10
|
+
|
|
11
|
+
const promServer = fastify({ name: 'Prometheus server' })
|
|
12
|
+
|
|
13
|
+
runtimeApiClient.on('close', async () => {
|
|
14
|
+
await promServer.close()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
let onRequestHook
|
|
18
|
+
if (auth) {
|
|
19
|
+
const { username, password } = auth
|
|
20
|
+
|
|
21
|
+
await promServer.register(require('@fastify/basic-auth'), {
|
|
22
|
+
validate: function (user, pass, req, reply, done) {
|
|
23
|
+
if (username !== user || password !== pass) {
|
|
24
|
+
return reply.code(401).send({ message: 'Unauthorized' })
|
|
25
|
+
}
|
|
26
|
+
return done()
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
onRequestHook = promServer.basicAuth
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
promServer.route({
|
|
33
|
+
url: metricsEndpoint,
|
|
34
|
+
method: 'GET',
|
|
35
|
+
logLevel: 'warn',
|
|
36
|
+
onRequest: onRequestHook,
|
|
37
|
+
handler: async (req, reply) => {
|
|
38
|
+
reply.type('text/plain')
|
|
39
|
+
const { metrics } = await runtimeApiClient.getMetrics('text')
|
|
40
|
+
return metrics
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
await promServer.listen({ port, host })
|
|
45
|
+
return promServer
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = {
|
|
49
|
+
startPrometheusServer
|
|
50
|
+
}
|
package/lib/schema.js
CHANGED
|
@@ -152,7 +152,44 @@ const platformaticRuntimeSchema = {
|
|
|
152
152
|
{ type: 'boolean' },
|
|
153
153
|
{
|
|
154
154
|
type: 'object',
|
|
155
|
-
properties: {
|
|
155
|
+
properties: {
|
|
156
|
+
logs: {
|
|
157
|
+
maxSize: {
|
|
158
|
+
type: 'number',
|
|
159
|
+
minimum: 5,
|
|
160
|
+
default: 200
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
additionalProperties: false
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
},
|
|
168
|
+
metrics: {
|
|
169
|
+
anyOf: [
|
|
170
|
+
{ type: 'boolean' },
|
|
171
|
+
{
|
|
172
|
+
type: 'object',
|
|
173
|
+
properties: {
|
|
174
|
+
port: {
|
|
175
|
+
anyOf: [
|
|
176
|
+
{ type: 'integer' },
|
|
177
|
+
{ type: 'string' }
|
|
178
|
+
]
|
|
179
|
+
},
|
|
180
|
+
hostname: { type: 'string' },
|
|
181
|
+
endpoint: { type: 'string' },
|
|
182
|
+
auth: {
|
|
183
|
+
type: 'object',
|
|
184
|
+
properties: {
|
|
185
|
+
username: { type: 'string' },
|
|
186
|
+
password: { type: 'string' }
|
|
187
|
+
},
|
|
188
|
+
additionalProperties: false,
|
|
189
|
+
required: ['username', 'password']
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
additionalProperties: false
|
|
156
193
|
}
|
|
157
194
|
]
|
|
158
195
|
}
|
package/lib/start.js
CHANGED
|
@@ -11,6 +11,7 @@ const { printConfigValidationErrors } = require('@platformatic/config')
|
|
|
11
11
|
const closeWithGrace = require('close-with-grace')
|
|
12
12
|
const { loadConfig } = require('./load-config')
|
|
13
13
|
const { startManagementApi } = require('./management-api')
|
|
14
|
+
const { startPrometheusServer } = require('./prom-server.js')
|
|
14
15
|
const { parseInspectorOptions, wrapConfigInRuntimeConfig } = require('./config')
|
|
15
16
|
const RuntimeApiClient = require('./api-client.js')
|
|
16
17
|
const errors = require('./errors')
|
|
@@ -24,7 +25,7 @@ const kWorkerExecArgv = [
|
|
|
24
25
|
kLoaderFile
|
|
25
26
|
]
|
|
26
27
|
|
|
27
|
-
async function
|
|
28
|
+
async function buildRuntime (configManager, env = process.env) {
|
|
28
29
|
const config = configManager.current
|
|
29
30
|
|
|
30
31
|
if (inspector.url()) {
|
|
@@ -108,7 +109,14 @@ async function startWithConfig (configManager, env = process.env) {
|
|
|
108
109
|
runtimeApiClient
|
|
109
110
|
)
|
|
110
111
|
runtimeApiClient.managementApi = managementApi
|
|
111
|
-
runtimeApiClient.
|
|
112
|
+
runtimeApiClient.on('start', () => {
|
|
113
|
+
runtimeApiClient.startCollectingMetrics()
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
if (config.metrics) {
|
|
117
|
+
runtimeApiClient.on('start', async () => {
|
|
118
|
+
await startPrometheusServer(runtimeApiClient, config.metrics)
|
|
119
|
+
})
|
|
112
120
|
}
|
|
113
121
|
|
|
114
122
|
return runtimeApiClient
|
|
@@ -119,7 +127,7 @@ async function start (args) {
|
|
|
119
127
|
|
|
120
128
|
if (config.configType === 'runtime') {
|
|
121
129
|
config.configManager.args = config.args
|
|
122
|
-
const app = await
|
|
130
|
+
const app = await buildRuntime(config.configManager)
|
|
123
131
|
await app.start()
|
|
124
132
|
return app
|
|
125
133
|
}
|
|
@@ -134,11 +142,11 @@ async function startCommand (args) {
|
|
|
134
142
|
|
|
135
143
|
if (config.configType === 'runtime') {
|
|
136
144
|
config.configManager.args = config.args
|
|
137
|
-
runtime = await
|
|
145
|
+
runtime = await buildRuntime(config.configManager)
|
|
138
146
|
} else {
|
|
139
147
|
const wrappedConfig = await wrapConfigInRuntimeConfig(config)
|
|
140
148
|
wrappedConfig.args = config.args
|
|
141
|
-
runtime = await
|
|
149
|
+
runtime = await buildRuntime(wrappedConfig)
|
|
142
150
|
}
|
|
143
151
|
|
|
144
152
|
return await runtime.start()
|
|
@@ -194,4 +202,4 @@ In alternative run "npm create platformatic@latest" to generate a basic plt serv
|
|
|
194
202
|
process.exit(1)
|
|
195
203
|
}
|
|
196
204
|
|
|
197
|
-
module.exports = {
|
|
205
|
+
module.exports = { buildRuntime, start, startCommand }
|
package/lib/worker.js
CHANGED
|
@@ -61,10 +61,27 @@ function createLogger (config) {
|
|
|
61
61
|
multiStream.add({ level: 'trace', stream: portStream })
|
|
62
62
|
}
|
|
63
63
|
if (config.managementApi) {
|
|
64
|
+
const logsFileMb = 5
|
|
65
|
+
const logsLimitMb = config.managementApi?.logs?.maxSize || 200
|
|
66
|
+
|
|
67
|
+
let logsLimitCount = Math.ceil(logsLimitMb / logsFileMb) - 1
|
|
68
|
+
if (logsLimitCount < 1) {
|
|
69
|
+
logsLimitCount = 1
|
|
70
|
+
}
|
|
71
|
+
|
|
64
72
|
const logsPath = join(PLATFORMATIC_TMP_DIR, process.pid.toString(), 'logs')
|
|
65
73
|
const pinoRoll = pino.transport({
|
|
66
74
|
target: 'pino-roll',
|
|
67
|
-
options: {
|
|
75
|
+
options: {
|
|
76
|
+
file: logsPath,
|
|
77
|
+
mode: 0o600,
|
|
78
|
+
size: logsFileMb + 'm',
|
|
79
|
+
mkdir: true,
|
|
80
|
+
fsync: true,
|
|
81
|
+
limit: {
|
|
82
|
+
count: logsLimitCount
|
|
83
|
+
}
|
|
84
|
+
}
|
|
68
85
|
})
|
|
69
86
|
multiStream.add({ level: 'trace', stream: pinoRoll })
|
|
70
87
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.29.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -33,13 +33,13 @@
|
|
|
33
33
|
"typescript": "^5.4.2",
|
|
34
34
|
"undici-oidc-interceptor": "^0.5.0",
|
|
35
35
|
"why-is-node-running": "^2.2.2",
|
|
36
|
-
"
|
|
37
|
-
"@platformatic/sql-
|
|
38
|
-
"@platformatic/sql-mapper": "1.28.0"
|
|
36
|
+
"@platformatic/sql-graphql": "1.29.0",
|
|
37
|
+
"@platformatic/sql-mapper": "1.29.0"
|
|
39
38
|
},
|
|
40
39
|
"dependencies": {
|
|
40
|
+
"ws": "^8.16.0",
|
|
41
41
|
"@fastify/error": "^3.4.1",
|
|
42
|
-
"@fastify/websocket": "^
|
|
42
|
+
"@fastify/websocket": "^10.0.0",
|
|
43
43
|
"@hapi/topo": "^6.0.2",
|
|
44
44
|
"boring-name-generator": "^1.0.3",
|
|
45
45
|
"change-case-all": "^2.1.0",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"commist": "^3.2.0",
|
|
48
48
|
"debounce": "^2.0.0",
|
|
49
49
|
"desm": "^1.3.1",
|
|
50
|
+
"dotenv-tool": "^0.0.2",
|
|
50
51
|
"es-main": "^1.3.0",
|
|
51
52
|
"fastest-levenshtein": "^1.0.16",
|
|
52
53
|
"fastify": "^4.26.2",
|
|
@@ -57,17 +58,17 @@
|
|
|
57
58
|
"pino": "^8.19.0",
|
|
58
59
|
"pino-pretty": "^10.3.1",
|
|
59
60
|
"semgrator": "^0.3.0",
|
|
60
|
-
"pino-roll": "1.0.0
|
|
61
|
+
"pino-roll": "^1.0.0",
|
|
61
62
|
"tail-file-stream": "^0.1.0",
|
|
62
63
|
"undici": "^6.9.0",
|
|
63
64
|
"why-is-node-running": "^2.2.2",
|
|
64
|
-
"@platformatic/
|
|
65
|
-
"@platformatic/config": "1.
|
|
66
|
-
"@platformatic/
|
|
67
|
-
"@platformatic/
|
|
68
|
-
"@platformatic/
|
|
69
|
-
"@platformatic/
|
|
70
|
-
"@platformatic/
|
|
65
|
+
"@platformatic/composer": "1.29.0",
|
|
66
|
+
"@platformatic/config": "1.29.0",
|
|
67
|
+
"@platformatic/db": "1.29.0",
|
|
68
|
+
"@platformatic/generators": "1.29.0",
|
|
69
|
+
"@platformatic/telemetry": "1.29.0",
|
|
70
|
+
"@platformatic/service": "1.29.0",
|
|
71
|
+
"@platformatic/utils": "1.29.0"
|
|
71
72
|
},
|
|
72
73
|
"standard": {
|
|
73
74
|
"ignore": [
|