@ossy/platform 3.0.8 → 3.1.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/README.md +12 -0
- package/package.json +13 -13
- package/src/Definition.js +1 -1
- package/src/health.js +24 -0
- package/src/health.spec.js +53 -0
- package/src/platform-config.schema.js +21 -28
- package/src/runtime.js +3 -0
- package/src/server.js +3 -0
package/README.md
CHANGED
|
@@ -53,6 +53,8 @@ The server reads configuration from:
|
|
|
53
53
|
- `build/manifest.json` — produced by `@ossy/app build`.
|
|
54
54
|
- `process.env` — used by integrations for their credentials and by startup hooks.
|
|
55
55
|
|
|
56
|
+
Platform integrations are **boot-time and process-global** via `IntegrationService`. Per-workspace credentials are a separate design — see [WORKSPACE-INTEGRATION-SECRETS.md](../../docs/concepts/WORKSPACE-INTEGRATION-SECRETS.md) (SPEC only; not implemented).
|
|
57
|
+
|
|
56
58
|
Optional environment variables used by the platform itself:
|
|
57
59
|
|
|
58
60
|
| Variable | Description |
|
|
@@ -61,6 +63,16 @@ Optional environment variables used by the platform itself:
|
|
|
61
63
|
| `API_URL` + `OSSY_API_KEY` | SDK configuration. When set, tasks receive a pre-configured SDK instance. |
|
|
62
64
|
| `PORT` | HTTP port. |
|
|
63
65
|
|
|
66
|
+
## Health check
|
|
67
|
+
|
|
68
|
+
Both `startServer` (website / app images) and `startRuntime` (CMS multi-tenant image) expose an unauthenticated liveness probe:
|
|
69
|
+
|
|
70
|
+
| Method | Path | Response |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `GET` / `HEAD` | `/health` | `200` with `{ ok: true, status: "ok", service }` |
|
|
73
|
+
|
|
74
|
+
The route is mounted before auth and before CMS site loading, so ALB target groups and ECS container health checks can use path `/health` without cookies, API tokens, or a resolvable hostname.
|
|
75
|
+
|
|
64
76
|
## Exported API
|
|
65
77
|
|
|
66
78
|
```js
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/platform",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Ossy application server runtime",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -44,17 +44,17 @@
|
|
|
44
44
|
"@aws-sdk/util-create-request": "^3.972.26",
|
|
45
45
|
"@aws-sdk/util-format-url": "^3.972.17",
|
|
46
46
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
47
|
-
"@ossy/config": "^3.0.
|
|
48
|
-
"@ossy/event-store": "^3.0.
|
|
49
|
-
"@ossy/locale": "^3.0.
|
|
50
|
-
"@ossy/manifest": "^3.0.
|
|
51
|
-
"@ossy/observability": "^3.0.
|
|
52
|
-
"@ossy/policies": "^3.0.
|
|
53
|
-
"@ossy/schema": "^3.0.
|
|
54
|
-
"@ossy/sdk": "^3.0.
|
|
55
|
-
"@ossy/tokens": "^3.0.
|
|
56
|
-
"@ossy/users": "^3.0.
|
|
57
|
-
"@ossy/workspaces": "^3.0
|
|
47
|
+
"@ossy/config": "^3.0.9",
|
|
48
|
+
"@ossy/event-store": "^3.0.9",
|
|
49
|
+
"@ossy/locale": "^3.0.9",
|
|
50
|
+
"@ossy/manifest": "^3.0.9",
|
|
51
|
+
"@ossy/observability": "^3.0.9",
|
|
52
|
+
"@ossy/policies": "^3.0.9",
|
|
53
|
+
"@ossy/schema": "^3.0.9",
|
|
54
|
+
"@ossy/sdk": "^3.0.9",
|
|
55
|
+
"@ossy/tokens": "^3.0.9",
|
|
56
|
+
"@ossy/users": "^3.0.9",
|
|
57
|
+
"@ossy/workspaces": "^3.1.0",
|
|
58
58
|
"cookie-parser": "^1.4.7",
|
|
59
59
|
"dotenv": ">=16.0.0 <18.0.0",
|
|
60
60
|
"express": ">=5.0.0 <6.0.0",
|
|
@@ -74,5 +74,5 @@
|
|
|
74
74
|
"src",
|
|
75
75
|
"Dockerfile"
|
|
76
76
|
],
|
|
77
|
-
"gitHead": "
|
|
77
|
+
"gitHead": "9b146fb6fd9769f800863806c5bd16016508b218"
|
|
78
78
|
}
|
package/src/Definition.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const Definition = {
|
|
2
2
|
id: 'platform',
|
|
3
3
|
title: 'Platform',
|
|
4
|
-
description: 'Deployment platform configuration aligned with deployment-tools
|
|
4
|
+
description: 'Deployment platform configuration aligned with deployment-tools platforms.json / PlatformConfig.',
|
|
5
5
|
icon: 'controller',
|
|
6
6
|
status: ['beta'],
|
|
7
7
|
entitlementRequired: false,
|
package/src/health.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable liveness probe for ALB / ECS health checks.
|
|
3
|
+
*
|
|
4
|
+
* Mounted before auth and site loading so probes succeed whenever the
|
|
5
|
+
* process can accept HTTP traffic. No authentication required.
|
|
6
|
+
*/
|
|
7
|
+
export const HEALTH_PATH = '/health'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {import('express').Express} app
|
|
11
|
+
* @param {{ service?: string }} [options]
|
|
12
|
+
*/
|
|
13
|
+
export function mountHealthEndpoint (app, { service = 'ossy' } = {}) {
|
|
14
|
+
const handler = (_req, res) => {
|
|
15
|
+
res.status(200).json({
|
|
16
|
+
ok: true,
|
|
17
|
+
status: 'ok',
|
|
18
|
+
service,
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
app.get(HEALTH_PATH, handler)
|
|
23
|
+
app.head(HEALTH_PATH, handler)
|
|
24
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, it, jest } from '@jest/globals'
|
|
2
|
+
import express from 'express'
|
|
3
|
+
import { HEALTH_PATH, mountHealthEndpoint } from './health.js'
|
|
4
|
+
|
|
5
|
+
function createMockRes () {
|
|
6
|
+
const res = {
|
|
7
|
+
statusCode: 200,
|
|
8
|
+
body: undefined,
|
|
9
|
+
status (code) {
|
|
10
|
+
this.statusCode = code
|
|
11
|
+
return this
|
|
12
|
+
},
|
|
13
|
+
json (body) {
|
|
14
|
+
this.body = body
|
|
15
|
+
return this
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
return res
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('mountHealthEndpoint', () => {
|
|
22
|
+
it('registers GET /health that returns 200 without auth', () => {
|
|
23
|
+
const app = express()
|
|
24
|
+
const get = jest.spyOn(app, 'get')
|
|
25
|
+
const head = jest.spyOn(app, 'head')
|
|
26
|
+
|
|
27
|
+
mountHealthEndpoint(app, { service: 'platform-runtime' })
|
|
28
|
+
|
|
29
|
+
expect(get).toHaveBeenCalledWith(HEALTH_PATH, expect.any(Function))
|
|
30
|
+
expect(head).toHaveBeenCalledWith(HEALTH_PATH, expect.any(Function))
|
|
31
|
+
|
|
32
|
+
const handler = get.mock.calls[0][1]
|
|
33
|
+
const res = createMockRes()
|
|
34
|
+
handler({}, res)
|
|
35
|
+
|
|
36
|
+
expect(res.statusCode).toBe(200)
|
|
37
|
+
expect(res.body).toEqual({
|
|
38
|
+
ok: true,
|
|
39
|
+
status: 'ok',
|
|
40
|
+
service: 'platform-runtime',
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('defaults service name to ossy', () => {
|
|
45
|
+
const app = express()
|
|
46
|
+
const get = jest.spyOn(app, 'get')
|
|
47
|
+
mountHealthEndpoint(app)
|
|
48
|
+
const handler = get.mock.calls[0][1]
|
|
49
|
+
const res = createMockRes()
|
|
50
|
+
handler({}, res)
|
|
51
|
+
expect(res.body.service).toBe('ossy')
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Matches the
|
|
3
|
-
* (`BucketDeployment` → `Source.jsonData('platform-config.json', { ...config, awsRoleToAssume: undefined })`).
|
|
2
|
+
* Matches the `platforms.json` / PlatformConfig shape used by `@ossy/deployment-tools`.
|
|
4
3
|
*
|
|
5
4
|
* @see ossy/packages/deployment-tools/src/config/platform-config.js (PlatformConfig typedef)
|
|
6
|
-
* @see ossy/packages/deployment-tools/src/infrastructure/container-deployment-target/container-deployment-target.js
|
|
7
5
|
*
|
|
8
|
-
* Note: `
|
|
9
|
-
* Optional `sesDomains` / `dnsRecords` are JSON text fields
|
|
6
|
+
* Note: `env` secret values live in `platforms.json` and Secrets Manager; do not store them here.
|
|
7
|
+
* Optional `domains` / `services` / `sesDomains` / `dnsRecords` / `githubDeployRepos` are JSON text fields.
|
|
10
8
|
*/
|
|
11
9
|
export default {
|
|
12
10
|
name: 'Platform config',
|
|
@@ -34,29 +32,29 @@ export default {
|
|
|
34
32
|
type: 'text',
|
|
35
33
|
},
|
|
36
34
|
{
|
|
37
|
-
name: '
|
|
38
|
-
label: '
|
|
39
|
-
type: '
|
|
35
|
+
name: 'domains',
|
|
36
|
+
label: 'domains (JSON array — Route53 → CloudFront + ACM)',
|
|
37
|
+
type: 'textarea',
|
|
40
38
|
},
|
|
41
39
|
{
|
|
42
|
-
name: '
|
|
43
|
-
label: '
|
|
44
|
-
type: '
|
|
40
|
+
name: 'services',
|
|
41
|
+
label: 'services (JSON array — HTTP → ECS/ALB; TCP ignored)',
|
|
42
|
+
type: 'textarea',
|
|
45
43
|
},
|
|
46
44
|
{
|
|
47
|
-
name: '
|
|
48
|
-
label: '
|
|
49
|
-
type: '
|
|
45
|
+
name: 'githubDeployRepos',
|
|
46
|
+
label: 'githubDeployRepos (JSON array of owner/repo for OIDC)',
|
|
47
|
+
type: 'textarea',
|
|
50
48
|
},
|
|
51
49
|
{
|
|
52
|
-
name: '
|
|
53
|
-
label: '
|
|
54
|
-
type: '
|
|
50
|
+
name: 'sesDomains',
|
|
51
|
+
label: 'sesDomains (JSON array of strings, optional)',
|
|
52
|
+
type: 'textarea',
|
|
55
53
|
},
|
|
56
54
|
{
|
|
57
|
-
name: '
|
|
58
|
-
label: '
|
|
59
|
-
type: '
|
|
55
|
+
name: 'dnsRecords',
|
|
56
|
+
label: 'dnsRecords (JSON object, optional)',
|
|
57
|
+
type: 'textarea',
|
|
60
58
|
},
|
|
61
59
|
{
|
|
62
60
|
name: 'ciDockerNetworkName',
|
|
@@ -64,14 +62,9 @@ export default {
|
|
|
64
62
|
type: 'text',
|
|
65
63
|
},
|
|
66
64
|
{
|
|
67
|
-
name: '
|
|
68
|
-
label: '
|
|
69
|
-
type: '
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
name: 'dnsRecords',
|
|
73
|
-
label: 'dnsRecords (JSON object, optional)',
|
|
74
|
-
type: 'textarea',
|
|
65
|
+
name: 'awsKeyPairName',
|
|
66
|
+
label: 'awsKeyPairName (unused after EC2 decommission)',
|
|
67
|
+
type: 'text',
|
|
75
68
|
},
|
|
76
69
|
{
|
|
77
70
|
name: 'Notes',
|
package/src/runtime.js
CHANGED
|
@@ -10,6 +10,7 @@ import { buildManifestSummary } from '@ossy/manifest/build-manifest-summary'
|
|
|
10
10
|
import { ProxyInternal } from './proxy-internal.js'
|
|
11
11
|
import { loadSite, invalidateSite } from './site-loader.js'
|
|
12
12
|
import { createLogger } from '@ossy/observability'
|
|
13
|
+
import { mountHealthEndpoint } from './health.js'
|
|
13
14
|
|
|
14
15
|
const log = createLogger('platform')
|
|
15
16
|
|
|
@@ -76,6 +77,8 @@ export async function startRuntime ({ port } = {}) {
|
|
|
76
77
|
const resolvedPort = port ?? resolvePort()
|
|
77
78
|
|
|
78
79
|
const app = express()
|
|
80
|
+
// Liveness for ALB/ECS — before site loading so probes never depend on CMS/domain.
|
|
81
|
+
mountHealthEndpoint(app, { service: 'platform-runtime' })
|
|
79
82
|
app.use(morgan('tiny'))
|
|
80
83
|
app.use(express.json({ strict: false }))
|
|
81
84
|
app.use(cookieParser(process.env.OSSY_COOKIE_SECRET || 'default_secret'))
|
package/src/server.js
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
createWorkspaceLoader,
|
|
28
28
|
} from './entitlements/action-entitlement.js'
|
|
29
29
|
import { closePushSseConnections, mountPushSse } from './push/mount-push-sse.js'
|
|
30
|
+
import { mountHealthEndpoint } from './health.js'
|
|
30
31
|
import {
|
|
31
32
|
createSlowRequestLogger,
|
|
32
33
|
createTimedOperation,
|
|
@@ -320,6 +321,8 @@ export async function startServer (options = {}) {
|
|
|
320
321
|
}
|
|
321
322
|
|
|
322
323
|
const app = express()
|
|
324
|
+
// Liveness for ALB/ECS — before auth so probes never depend on cookies/Mongo.
|
|
325
|
+
mountHealthEndpoint(app, { service: 'platform' })
|
|
323
326
|
app.use(morgan('tiny'))
|
|
324
327
|
app.use(express.json({ strict: false }))
|
|
325
328
|
app.use(cookieParser(ConfigService.TokenSecret))
|