@fabrika/provider-zerops 0.4.2 → 0.4.3
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/package.json +8 -7
- package/src/api/client.ts +11 -14
- package/src/api/polling.ts +42 -0
- package/src/api/readers.ts +9 -2
- package/src/api/service-env-limit.ts +29 -0
- package/src/api/service-env-response.ts +34 -2
- package/src/api/types.ts +39 -9
- package/src/api.ts +10 -1
- package/src/control.ts +23 -8
- package/src/execution.ts +41 -3
- package/src/index.ts +5 -0
- package/src/installation/admin.ts +4 -2
- package/src/installation/checker-release.ts +44 -0
- package/src/installation/deploy.ts +66 -22
- package/src/installation/generated/platform-console-schema.ts +18 -2
- package/src/installation/generated/platform-proxy-manifest.ts +20 -0
- package/src/installation/guide.md +164 -52
- package/src/installation/index.ts +22 -15
- package/src/installation/init-options.ts +2 -2
- package/src/installation/init.ts +8 -2
- package/src/installation/install-collaborators.ts +4 -0
- package/src/installation/install-environment.ts +52 -30
- package/src/installation/install-options.ts +20 -19
- package/src/installation/install-project.ts +23 -15
- package/src/installation/install-provisioning.ts +12 -12
- package/src/installation/install-secrets.ts +89 -0
- package/src/installation/install-token.ts +37 -0
- package/src/installation/install.ts +34 -21
- package/src/installation/machine-key.ts +3 -2
- package/src/installation/manifest.ts +29 -12
- package/src/installation/platform-archive.ts +87 -0
- package/src/installation/platform-upload.ts +2 -2
- package/src/installation/secrets.ts +3 -4
- package/src/installation/topology/setups.ts +15 -27
- package/src/installation/topology/topology.ts +105 -104
- package/src/managed-env.ts +11 -0
- package/src/manifest.ts +25 -3
- package/src/namespace/codec.ts +6 -1
- package/src/namespace/errors.ts +3 -1
- package/src/namespace/provisioning.ts +18 -5
- package/src/namespace/proxy-attempt.ts +56 -14
- package/src/namespace/update.ts +50 -8
- package/src/provider.ts +57 -13
- package/src/proxy-manifest-carrier.ts +32 -0
- package/src/source/port.ts +9 -1
|
@@ -35,10 +35,14 @@
|
|
|
35
35
|
// readiness gate.
|
|
36
36
|
|
|
37
37
|
import { FABRIKA_SERVICE_TABLE_DIGEST, readEnvironmentName } from '@fabrika/auth-core'
|
|
38
|
+
import { IAM_APP_ID, IAM_APP_SCHEMA } from '@fabrika/iam-contract'
|
|
38
39
|
import type { SchemaReconciler } from '@fabrika/provider-contract'
|
|
39
40
|
import { encodeProxyManifestJson, FABRIKA_PROXY_MANIFEST_JSON } from '@fabrika/proxy-core/manifest'
|
|
41
|
+
import { assertServiceEnvValueFits } from '../api'
|
|
40
42
|
import {
|
|
43
|
+
enableSubdomainAccessWhenHttp,
|
|
41
44
|
prepareZeropsDescriptor,
|
|
45
|
+
readableContent,
|
|
42
46
|
type Sleeper,
|
|
43
47
|
ZEROPS_ACTIVE,
|
|
44
48
|
ZEROPS_SERVICE_NOT_HTTP,
|
|
@@ -47,7 +51,10 @@ import {
|
|
|
47
51
|
ZeropsApiError,
|
|
48
52
|
type ZeropsNamespaceProxySource,
|
|
49
53
|
type ZeropsService,
|
|
54
|
+
type ZeropsServiceEnv,
|
|
50
55
|
} from '../index'
|
|
56
|
+
import { ZEROPS_PROXY_MANIFEST_PATH } from '../proxy-manifest-carrier'
|
|
57
|
+
import { assertCheckerPublished, type CheckerPublication } from './checker-release'
|
|
51
58
|
import type { PlatformDeployInput } from './deploy-options'
|
|
52
59
|
import { PLATFORM_CONSOLE_APP_SCHEMA } from './generated/platform-console-schema'
|
|
53
60
|
import { PLATFORM_PROXY_MANIFEST_TEMPLATE } from './generated/platform-proxy-manifest'
|
|
@@ -63,6 +70,7 @@ import {
|
|
|
63
70
|
import type { DeployLog } from './log'
|
|
64
71
|
import { mergePlatformProxyManifest, readLiveProxyManifest } from './manifest'
|
|
65
72
|
import { type IamOAuthConfig, platformIamOAuthConfig } from './mcp'
|
|
73
|
+
import { archiveWithDataFile } from './platform-archive'
|
|
66
74
|
import type { PreparedPlatformSource } from './platform-source'
|
|
67
75
|
import { type PlatformUploadFetch, uploadPlatformArchive } from './platform-upload'
|
|
68
76
|
import { platformProxyAppFor, type PlatformProxyComposition, platformServiceTableDigest, resolvePlatformProxyManifest } from './proxy-manifest'
|
|
@@ -139,6 +147,7 @@ export interface PlatformServiceDeployCollaborators {
|
|
|
139
147
|
/** Everything `deployPlatform` reaches the outside world through. */
|
|
140
148
|
export interface PlatformDeployCollaborators extends PlatformServiceDeployCollaborators {
|
|
141
149
|
readonly prepareSource: PlatformSourcePreparer
|
|
150
|
+
readonly checkerPublished: CheckerPublication
|
|
142
151
|
readonly reconcileSchema: SchemaReconciler
|
|
143
152
|
}
|
|
144
153
|
|
|
@@ -146,6 +155,16 @@ export interface PlatformDeployCollaborators extends PlatformServiceDeployCollab
|
|
|
146
155
|
export interface PlatformServiceDeployInput {
|
|
147
156
|
readonly source: PreparedPlatformSource
|
|
148
157
|
readonly dryRun: boolean
|
|
158
|
+
/** The proxy's manifest, which its archive carries as the one data file (ADR-0045); the proxy is refused without it. */
|
|
159
|
+
readonly proxyManifest?: string
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** The archive one service builds from: the snapshot, and for the proxy the snapshot plus its manifest. */
|
|
163
|
+
const serviceArchive = async (service: ResolvedService, input: PlatformServiceDeployInput): Promise<Blob> => {
|
|
164
|
+
const snapshot = Bun.file(input.source.archivePath)
|
|
165
|
+
if (service.hostname !== PLATFORM_PROXY_SERVICE) return snapshot
|
|
166
|
+
if (input.proxyManifest === undefined) throw new Error('the proxy is built only with the manifest its archive carries')
|
|
167
|
+
return new Blob([archiveWithDataFile(await snapshot.bytes(), { path: ZEROPS_PROXY_MANIFEST_PATH, contents: input.proxyManifest })])
|
|
149
168
|
}
|
|
150
169
|
|
|
151
170
|
/** One resolved platform service. */
|
|
@@ -237,13 +256,16 @@ export const serviceOf = (services: ReadonlyMap<PlatformDeployService, ResolvedS
|
|
|
237
256
|
return service
|
|
238
257
|
}
|
|
239
258
|
|
|
240
|
-
/**
|
|
259
|
+
/** One service's environment by key. A sensitive record carries no value. Values are never logged. */
|
|
260
|
+
export type LiveServiceEnv = ReadonlyMap<string, ZeropsServiceEnv>
|
|
261
|
+
|
|
262
|
+
/** Read one service's environment by key. */
|
|
241
263
|
export const readServiceEnv = async (
|
|
242
264
|
serviceId: string,
|
|
243
265
|
{ api, signal }: Pick<PlatformServiceDeployCollaborators, 'api' | 'signal'>,
|
|
244
|
-
): Promise<
|
|
266
|
+
): Promise<LiveServiceEnv> => {
|
|
245
267
|
const variables = await api.listServiceEnv({ serviceId, signal })
|
|
246
|
-
return new Map(variables.map((variable) => [variable.key, variable
|
|
268
|
+
return new Map(variables.map((variable) => [variable.key, variable]))
|
|
247
269
|
}
|
|
248
270
|
|
|
249
271
|
/**
|
|
@@ -254,11 +276,11 @@ export const readServiceEnv = async (
|
|
|
254
276
|
* `custom-domain` installation, where the domains are bound to the project balancer out of band and
|
|
255
277
|
* publishing a subdomain would add a public address nobody asked for.
|
|
256
278
|
*/
|
|
257
|
-
const resolvePlacement = (input: PlatformDeployInput, proxyEnv:
|
|
279
|
+
const resolvePlacement = (input: PlatformDeployInput, proxyEnv: LiveServiceEnv): PlatformPlacement => {
|
|
258
280
|
if (input.hosts !== undefined) {
|
|
259
281
|
return { hosts: input.hosts, scheme: input.scheme, zeropsSubdomain: false }
|
|
260
282
|
}
|
|
261
|
-
const subdomains = proxyEnv.get(ZEROPS_SUBDOMAIN_VARIABLE)
|
|
283
|
+
const subdomains = readableContent(proxyEnv.get(ZEROPS_SUBDOMAIN_VARIABLE))
|
|
262
284
|
if (subdomains === undefined || subdomains.trim() === '') {
|
|
263
285
|
throw new Error(
|
|
264
286
|
'the proxy service publishes no Zerops subdomain — deploy it once so it has HTTP ports, or name every host '
|
|
@@ -281,7 +303,6 @@ const IAM_OAUTH_CONFIG = 'FABRIKA_IAM_OAUTH_CONFIG'
|
|
|
281
303
|
const desiredServiceEnv = (
|
|
282
304
|
environment: string,
|
|
283
305
|
placement: PlatformPlacement,
|
|
284
|
-
manifestJson: string,
|
|
285
306
|
proxySource: ZeropsNamespaceProxySource,
|
|
286
307
|
oauthConfig: IamOAuthConfig | undefined,
|
|
287
308
|
controlTableDigest: string,
|
|
@@ -314,7 +335,6 @@ const desiredServiceEnv = (
|
|
|
314
335
|
// Compared byte-for-byte against a token's `iss`, so this is the PUBLIC issuer and not the
|
|
315
336
|
// private `iam:3000` the manifest dials — `readProxyEnv` canonicalizes it to an origin.
|
|
316
337
|
['FABRIKA_IAM_ISSUER', iamOrigin],
|
|
317
|
-
[FABRIKA_PROXY_MANIFEST_JSON, manifestJson],
|
|
318
338
|
['FABRIKA_PROXY_SESSION_OWNER_JSON', JSON.stringify({ issuer: iamOrigin, upstream: PRIVATE_IAM_UPSTREAM })],
|
|
319
339
|
]),
|
|
320
340
|
],
|
|
@@ -338,25 +358,32 @@ const desiredServiceEnv = (
|
|
|
338
358
|
])
|
|
339
359
|
}
|
|
340
360
|
|
|
341
|
-
/**
|
|
361
|
+
/**
|
|
362
|
+
* Write only what differs. Returns the KEYS written — never a value, not even in a debug line.
|
|
363
|
+
*
|
|
364
|
+
* Nothing here is a credential, so everything is written readable. A record an older release wrote
|
|
365
|
+
* sensitive reads as different and is rewritten readable, which is what lets the next deploy compare it.
|
|
366
|
+
*/
|
|
342
367
|
const applyServiceEnv = async (
|
|
343
368
|
service: ResolvedService,
|
|
344
369
|
desired: ReadonlyMap<string, string>,
|
|
345
|
-
live:
|
|
370
|
+
live: LiveServiceEnv,
|
|
346
371
|
input: PlatformDeployInput,
|
|
347
372
|
collaborators: PlatformDeployCollaborators,
|
|
348
373
|
): Promise<string[]> => {
|
|
349
374
|
const written: string[] = []
|
|
350
375
|
for (const [key, value] of desired) {
|
|
351
|
-
if (live.get(key) === value) {
|
|
376
|
+
if (readableContent(live.get(key)) === value) {
|
|
352
377
|
continue
|
|
353
378
|
}
|
|
354
379
|
written.push(key)
|
|
380
|
+
// The client refuses it too; checking here lets a dry run report what the real write would refuse.
|
|
381
|
+
assertServiceEnvValueFits(key, value)
|
|
355
382
|
if (input.dryRun) {
|
|
356
383
|
continue
|
|
357
384
|
}
|
|
358
385
|
assertRunning(collaborators.signal)
|
|
359
|
-
await collaborators.api.putServiceEnv({ serviceId: service.id, key, value, signal: collaborators.signal })
|
|
386
|
+
await collaborators.api.putServiceEnv({ serviceId: service.id, key, value, sensitive: false, signal: collaborators.signal })
|
|
360
387
|
}
|
|
361
388
|
return written
|
|
362
389
|
}
|
|
@@ -434,11 +461,12 @@ export const deployPlatformService = async (
|
|
|
434
461
|
return
|
|
435
462
|
}
|
|
436
463
|
prepareZeropsDescriptor(input.source.descriptor.contents, service.hostname)
|
|
464
|
+
const archive = await serviceArchive(service, input)
|
|
437
465
|
const reserved = await api.createAppVersion({ serviceId: service.id, name: input.source.commitSha, signal })
|
|
438
466
|
const appVersionId = reserved.id
|
|
439
467
|
log.info(`${service.hostname}: app-version ${appVersionId}, commit ${input.source.commitSha}, setup ${service.hostname}`)
|
|
440
468
|
try {
|
|
441
|
-
await uploadPlatformArchive(reserved.uploadUrl,
|
|
469
|
+
await uploadPlatformArchive(reserved.uploadUrl, archive, signal, collaborators.uploadFetch)
|
|
442
470
|
assertRunning(signal)
|
|
443
471
|
} catch {
|
|
444
472
|
await cleanupReservation(service, appVersionId, collaborators)
|
|
@@ -502,7 +530,8 @@ const deployConcurrently = async (
|
|
|
502
530
|
* The decision is the READ-BACK and never the call returning: on an already-published service the
|
|
503
531
|
* platform answers 2xx and then fails the process it hands back, and a freshly-enabled one can still
|
|
504
532
|
* read `false` on the next read. Both live-verified. Runs on every deploy, so a subdomain someone
|
|
505
|
-
* turned off in the GUI comes back, and throws rather than leaving the installation unreachable.
|
|
533
|
+
* turned off in the GUI comes back, and throws rather than leaving the installation unreachable. The
|
|
534
|
+
* HTTP ports lag a fresh ACTIVE version, so `serviceStackIsNotHttp` is retried for a bounded window first.
|
|
506
535
|
*/
|
|
507
536
|
const ensureSubdomainAccess = async (
|
|
508
537
|
proxy: ResolvedService,
|
|
@@ -519,11 +548,11 @@ const ensureSubdomainAccess = async (
|
|
|
519
548
|
return
|
|
520
549
|
}
|
|
521
550
|
try {
|
|
522
|
-
await
|
|
551
|
+
await enableSubdomainAccessWhenHttp({ api, serviceId: proxy.id, sleep, signal })
|
|
523
552
|
} catch (error) {
|
|
524
553
|
if (error instanceof ZeropsApiError && error.code === ZEROPS_SERVICE_NOT_HTTP) {
|
|
525
554
|
throw new Error(
|
|
526
|
-
`the proxy cannot be published on a zerops.app subdomain: it exposes no deployed HTTP port (${ZEROPS_SERVICE_NOT_HTTP})`,
|
|
555
|
+
`the proxy cannot be published on a zerops.app subdomain: it still exposes no deployed HTTP port (${ZEROPS_SERVICE_NOT_HTTP}) minutes after its deploy`,
|
|
527
556
|
)
|
|
528
557
|
}
|
|
529
558
|
throw error
|
|
@@ -569,6 +598,7 @@ export const deployPlatform = async (
|
|
|
569
598
|
const source = await collaborators.prepareSource(input.sourceDir, collaborators.signal)
|
|
570
599
|
try {
|
|
571
600
|
validateInstallationSetups(source, PLATFORM_DEPLOY_ORDER)
|
|
601
|
+
await assertCheckerPublished(collaborators.checkerPublished, collaborators.signal)
|
|
572
602
|
await deployPreparedPlatform(input, collaborators, source)
|
|
573
603
|
} finally {
|
|
574
604
|
await source.dispose()
|
|
@@ -617,6 +647,7 @@ export const deployPreparedPlatform = async (
|
|
|
617
647
|
? 'MCP authoring: off'
|
|
618
648
|
: `MCP authoring: on, ${oauthConfig.clients.length} client(s), resource(s) ${oauthConfig.resources.map((entry) => entry.resource).join(', ')}`,
|
|
619
649
|
)
|
|
650
|
+
// The variable is the application entries the platform proxy carries, held by the operator; nothing here writes it.
|
|
620
651
|
const merged = mergePlatformProxyManifest(platformManifest, readLiveProxyManifest(proxyEnv.get(FABRIKA_PROXY_MANIFEST_JSON)))
|
|
621
652
|
log.info(`${merged.manifest.apps.length} app(s): ${merged.manifest.apps.map((app) => app.id).join(', ')}`)
|
|
622
653
|
if (merged.carried.length > 0) {
|
|
@@ -631,12 +662,14 @@ export const deployPreparedPlatform = async (
|
|
|
631
662
|
|
|
632
663
|
log.step('Write the service environment')
|
|
633
664
|
const controlTableDigest = platformServiceTableDigest(platformManifest, platformProxyAppFor(PLATFORM_PROXY_MANIFEST_TEMPLATE, 'control').id)
|
|
634
|
-
const desired = desiredServiceEnv(environment, placement,
|
|
665
|
+
const desired = desiredServiceEnv(environment, placement, proxySource, oauthConfig, controlTableDigest)
|
|
635
666
|
for (const hostname of PLATFORM_DEPLOY_ORDER) {
|
|
636
667
|
const service = serviceOf(services, hostname)
|
|
637
668
|
const live = hostname === PLATFORM_PROXY_SERVICE ? proxyEnv : await readServiceEnv(service.id, collaborators)
|
|
638
669
|
const keys = desired.get(hostname) ?? new Map<string, string>()
|
|
639
|
-
|
|
670
|
+
const liveOAuthConfig = live.get(IAM_OAUTH_CONFIG)
|
|
671
|
+
const oauthStillSet = liveOAuthConfig !== undefined && (liveOAuthConfig.sensitive || liveOAuthConfig.content.trim() !== '')
|
|
672
|
+
if (hostname === 'iam' && oauthConfig === undefined && oauthStillSet) {
|
|
640
673
|
log.warn(`iam: MCP authoring is off but ${IAM_OAUTH_CONFIG} is still set; IAM keeps issuing MCP tokens until it is removed`)
|
|
641
674
|
}
|
|
642
675
|
const written = await applyServiceEnv(service, keys, live, input, collaborators)
|
|
@@ -647,11 +680,12 @@ export const deployPreparedPlatform = async (
|
|
|
647
680
|
)
|
|
648
681
|
}
|
|
649
682
|
|
|
650
|
-
// EVERY deploy is after EVERY write
|
|
651
|
-
//
|
|
652
|
-
//
|
|
683
|
+
// EVERY deploy is after EVERY write, and the manifest rides in the proxy's own archive. That is what makes
|
|
684
|
+
// the sequence fail closed: a proxy build that does not go ACTIVE leaves the previous manifest in front of
|
|
685
|
+
// the previous code, because control builds only after it — and `ENVIRONMENT` is in place before any
|
|
686
|
+
// service that refuses `local` restarts.
|
|
653
687
|
log.step(`Deploy ${PLATFORM_CONCURRENT_DEPLOY.join(' + ')} together, then ${PLATFORM_SEQUENTIAL_DEPLOY.join(' → ')}`)
|
|
654
|
-
const buildInput = { source, dryRun: input.dryRun }
|
|
688
|
+
const buildInput = { source, dryRun: input.dryRun, proxyManifest: encodeProxyManifestJson(merged.manifest) }
|
|
655
689
|
await deployConcurrently(services, buildInput, collaborators)
|
|
656
690
|
for (const hostname of PLATFORM_SEQUENTIAL_DEPLOY) {
|
|
657
691
|
await deployPlatformService(serviceOf(services, hostname), buildInput, collaborators)
|
|
@@ -669,11 +703,12 @@ export const deployPreparedPlatform = async (
|
|
|
669
703
|
log.info('custom-domain installation: the operator binds each domain to the project balancer; no subdomain is published')
|
|
670
704
|
}
|
|
671
705
|
|
|
672
|
-
log.step('Register the console with IAM')
|
|
706
|
+
log.step('Register the console and IAM with IAM')
|
|
673
707
|
const consoleApp = platformProxyAppFor(PLATFORM_PROXY_MANIFEST_TEMPLATE, 'control')
|
|
674
708
|
const consoleOrigin = platformOrigin(placement.scheme, placement.hosts.control)
|
|
675
709
|
if (input.dryRun) {
|
|
676
710
|
log.info(`would reconcile \`${consoleApp.id}\` and register its return origin ${consoleOrigin}`)
|
|
711
|
+
log.info(`would reconcile \`${IAM_APP_ID}\`'s own vocabulary`)
|
|
677
712
|
} else {
|
|
678
713
|
// The schema PUT is what REGISTERS the app, and `apps.setReturnOrigins` 404s for an app IAM has
|
|
679
714
|
// never heard of — so both travel in this one call, in that order (ADR-0022/ADR-0023).
|
|
@@ -686,6 +721,15 @@ export const deployPreparedPlatform = async (
|
|
|
686
721
|
signal,
|
|
687
722
|
})
|
|
688
723
|
log.info(`\`${consoleApp.id}\` reconciled; return origin ${consoleOrigin}`)
|
|
724
|
+
// IAM's own vocabulary, which `user-admin` is granted from (ADR-0058). It has no return origin.
|
|
725
|
+
await collaborators.reconcileSchema({
|
|
726
|
+
url: platformOrigin(placement.scheme, placement.hosts.iam),
|
|
727
|
+
app: IAM_APP_ID,
|
|
728
|
+
schema: IAM_APP_SCHEMA,
|
|
729
|
+
adminKey: input.iamAdminKey,
|
|
730
|
+
signal,
|
|
731
|
+
})
|
|
732
|
+
log.info(`\`${IAM_APP_ID}\` reconciled`)
|
|
689
733
|
}
|
|
690
734
|
|
|
691
735
|
log.step(input.dryRun ? 'Dry run complete — nothing was written' : 'Installation deployed')
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import type { AppSchema } from '@fabrika/auth-core'
|
|
12
12
|
|
|
13
13
|
export const PLATFORM_CONSOLE_APP_SCHEMA: AppSchema = {
|
|
14
|
+
displayName: 'Console',
|
|
14
15
|
scopes: [
|
|
15
16
|
{ type: 'app', label: 'App' },
|
|
16
17
|
{ type: 'environment', label: 'Environment' },
|
|
@@ -31,14 +32,29 @@ export const PLATFORM_CONSOLE_APP_SCHEMA: AppSchema = {
|
|
|
31
32
|
{ action: 'operations.manage', description: 'Manage Operations sources, alerts, and retention' },
|
|
32
33
|
],
|
|
33
34
|
roles: {
|
|
35
|
+
observer: {
|
|
36
|
+
name: 'Observer',
|
|
37
|
+
description: 'Read Operations errors and activity.',
|
|
38
|
+
permissions: ['operations.read'],
|
|
39
|
+
},
|
|
40
|
+
triager: {
|
|
41
|
+
name: 'Triager',
|
|
42
|
+
description: 'Read and triage Operations errors.',
|
|
43
|
+
permissions: ['operations.read', 'operations.triage'],
|
|
44
|
+
},
|
|
45
|
+
deployer: {
|
|
46
|
+
name: 'Deployer',
|
|
47
|
+
description: 'Trigger, read and cancel deploys (no registry, namespace or secret management).',
|
|
48
|
+
permissions: ['deploy.*'],
|
|
49
|
+
},
|
|
34
50
|
operator: {
|
|
35
51
|
name: 'Operator',
|
|
36
|
-
description: '
|
|
52
|
+
description: 'Deploy, and read, triage and manage Operations (no registry or secret management).',
|
|
37
53
|
permissions: ['deploy.*', 'operations.*'],
|
|
38
54
|
},
|
|
39
55
|
admin: {
|
|
40
56
|
name: 'Admin',
|
|
41
|
-
description: '
|
|
57
|
+
description: 'Every console action in every scope.',
|
|
42
58
|
permissions: ['*'],
|
|
43
59
|
},
|
|
44
60
|
},
|
|
@@ -278,6 +278,16 @@ export const PLATFORM_PROXY_MANIFEST_TEMPLATE: PlatformProxyManifestTemplate = {
|
|
|
278
278
|
admission: null,
|
|
279
279
|
oauth: null,
|
|
280
280
|
},
|
|
281
|
+
{
|
|
282
|
+
id: 'github-setup',
|
|
283
|
+
kind: 'service',
|
|
284
|
+
method: 'GET',
|
|
285
|
+
path: '/api/source/github/setup/:connectionId',
|
|
286
|
+
callers: ['human'],
|
|
287
|
+
requirement: { kind: 'authenticated' },
|
|
288
|
+
admission: null,
|
|
289
|
+
oauth: null,
|
|
290
|
+
},
|
|
281
291
|
{
|
|
282
292
|
id: 'rpc',
|
|
283
293
|
kind: 'service',
|
|
@@ -453,6 +463,16 @@ export const PLATFORM_PROXY_MANIFEST_TEMPLATE: PlatformProxyManifestTemplate = {
|
|
|
453
463
|
admission: null,
|
|
454
464
|
oauth: null,
|
|
455
465
|
},
|
|
466
|
+
{
|
|
467
|
+
id: 'github-setup',
|
|
468
|
+
kind: 'service',
|
|
469
|
+
method: 'GET',
|
|
470
|
+
path: '/api/source/github/setup/:connectionId',
|
|
471
|
+
callers: ['human'],
|
|
472
|
+
requirement: { kind: 'authenticated' },
|
|
473
|
+
admission: null,
|
|
474
|
+
oauth: null,
|
|
475
|
+
},
|
|
456
476
|
{
|
|
457
477
|
id: 'rpc',
|
|
458
478
|
kind: 'service',
|