@mcpg-dev/pulumi 0.1.0-beta.9
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/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/workflows/ci.yml +29 -0
- package/.github/workflows/release.yml +18 -0
- package/CONTRIBUTING.md +24 -0
- package/README.md +169 -0
- package/bin/cluster.d.ts +44 -0
- package/bin/cluster.js +80 -0
- package/bin/config.d.ts +30 -0
- package/bin/config.js +22 -0
- package/bin/config.test.d.ts +1 -0
- package/bin/config.test.js +36 -0
- package/bin/gateway.d.ts +52 -0
- package/bin/gateway.js +90 -0
- package/bin/index.d.ts +11 -0
- package/bin/index.js +27 -0
- package/bin/mcpgStack.d.ts +18 -0
- package/bin/mcpgStack.js +58 -0
- package/bin/oci.d.ts +8 -0
- package/bin/oci.js +41 -0
- package/bin/operator.d.ts +28 -0
- package/bin/operator.js +80 -0
- package/bin/pluginSet.d.ts +16 -0
- package/bin/pluginSet.js +55 -0
- package/bin/route.d.ts +35 -0
- package/bin/route.js +64 -0
- package/bin/tenant.d.ts +32 -0
- package/bin/tenant.js +95 -0
- package/bin/tenantCr.d.ts +32 -0
- package/bin/tenantCr.js +65 -0
- package/bin/trustBootstrap.d.ts +21 -0
- package/bin/trustBootstrap.js +56 -0
- package/package.json +22 -0
- package/src/cluster.ts +75 -0
- package/src/config.test.ts +36 -0
- package/src/config.ts +44 -0
- package/src/gateway.ts +91 -0
- package/src/index.ts +11 -0
- package/src/mcpgStack.ts +37 -0
- package/src/oci.ts +40 -0
- package/src/operator.ts +65 -0
- package/src/pluginSet.ts +33 -0
- package/src/route.ts +45 -0
- package/src/tenant.ts +79 -0
- package/src/tenantCr.ts +45 -0
- package/src/trustBootstrap.ts +40 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
blank_issues_enabled: true
|
|
2
|
+
contact_links:
|
|
3
|
+
- name: Security report
|
|
4
|
+
url: mailto:security@mcpg.dev
|
|
5
|
+
about: Please report vulnerabilities privately by email, not as a public issue.
|
|
6
|
+
- name: Pull requests
|
|
7
|
+
url: https://github.com/mcpg-dev
|
|
8
|
+
about: PRs cannot be merged here — development happens upstream. File an issue instead.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Per-push checks for the npm package. Publishing lives in release.yml.
|
|
2
|
+
name: CI
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
jobs:
|
|
9
|
+
check:
|
|
10
|
+
name: install · build · typecheck · test
|
|
11
|
+
if: vars.OSS_CI_ENABLED == 'true'
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
15
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
16
|
+
with:
|
|
17
|
+
node-version: 22
|
|
18
|
+
# Unlocked on purpose: no lockfile ships with the source, so the
|
|
19
|
+
# install resolves fresh.
|
|
20
|
+
- run: npm install --no-audit --no-fund
|
|
21
|
+
- run: npm run build
|
|
22
|
+
- name: Typecheck
|
|
23
|
+
run: |
|
|
24
|
+
set -euo pipefail
|
|
25
|
+
if jq -e '.scripts.typecheck' package.json >/dev/null; then npm run typecheck; fi
|
|
26
|
+
- name: Test
|
|
27
|
+
run: |
|
|
28
|
+
set -euo pipefail
|
|
29
|
+
if jq -e '.scripts.test' package.json >/dev/null; then npm test; fi
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Thin shim: fires on the v{version} release tag and calls the shared
|
|
2
|
+
# non-Rust release workflow with this repo's tier. Managed by the release
|
|
3
|
+
# pipeline — changes made here are overwritten.
|
|
4
|
+
name: Release
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags: ["v*"]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
permissions:
|
|
10
|
+
contents: write
|
|
11
|
+
id-token: write
|
|
12
|
+
packages: write
|
|
13
|
+
jobs:
|
|
14
|
+
release:
|
|
15
|
+
uses: mcpg-dev/.github/.github/workflows/release-nonrust.yml@v1
|
|
16
|
+
with:
|
|
17
|
+
tier: npm
|
|
18
|
+
secrets: inherit
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Development of this project happens upstream; this repository publishes
|
|
4
|
+
each release as a tagged snapshot.
|
|
5
|
+
|
|
6
|
+
## What this means
|
|
7
|
+
|
|
8
|
+
- **Pull requests cannot be merged here.** The contents are replaced
|
|
9
|
+
wholesale at every release, so a PR opened against this repository has
|
|
10
|
+
nothing durable to land on. PRs are closed automatically with a link to
|
|
11
|
+
this page.
|
|
12
|
+
- **Issues are very welcome.** Bug reports, feature requests, and questions
|
|
13
|
+
filed here are read and triaged. Please include the release version you
|
|
14
|
+
are using and a minimal reproduction.
|
|
15
|
+
- **Security reports:** please email **security@mcpg.dev** rather than
|
|
16
|
+
opening a public issue.
|
|
17
|
+
|
|
18
|
+
## Versions
|
|
19
|
+
|
|
20
|
+
Releases are git tags (`v{version}`); each tagged commit is a complete
|
|
21
|
+
snapshot of this project at that release. Dependency updates arrive with
|
|
22
|
+
the next release; this repository runs no Dependabot/Renovate.
|
|
23
|
+
|
|
24
|
+
Thanks for using MCPG.
|
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# @mcpg/pulumi
|
|
2
|
+
|
|
3
|
+
Opinionated Pulumi components for running MCPG on Kubernetes: the operator, the
|
|
4
|
+
CRDs, cluster trust, gateways, plugin sets, routes, and tenants. Each component
|
|
5
|
+
wraps a piece of the platform that is easy to get subtly wrong — CRD lifecycle,
|
|
6
|
+
gateway configuration shape, tenant isolation — behind typed TypeScript inputs.
|
|
7
|
+
Built on `@mcpg/pulumi-crds` (the generated, typed CRD SDK) and
|
|
8
|
+
`@pulumi/kubernetes`.
|
|
9
|
+
|
|
10
|
+
Reach for it when your MCPG deployment lives in a Pulumi program and you would
|
|
11
|
+
rather express "one operator, one gateway, three tenants" than assemble Helm
|
|
12
|
+
releases and raw custom resources by hand.
|
|
13
|
+
|
|
14
|
+
## Prerequisites
|
|
15
|
+
|
|
16
|
+
- Pulumi with the TypeScript runtime, and a Kubernetes provider configured for
|
|
17
|
+
the target cluster.
|
|
18
|
+
- cert-manager in the cluster, unless you turn it off. The operator's admission
|
|
19
|
+
webhook fails closed and needs real TLS, so `Operator` sets
|
|
20
|
+
`certManager.enabled=true` by default.
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { McpgStack } from "@mcpg/pulumi";
|
|
26
|
+
|
|
27
|
+
new McpgStack("prod", {
|
|
28
|
+
operator: { chartVersion: "<chart-version>" },
|
|
29
|
+
gateway: {
|
|
30
|
+
image: { repository: "ghcr.io/mcpg-dev/source-code/gateway", tag: "<tag>" },
|
|
31
|
+
governance: { audit: { sinks: [{ kind: "dev.mcpg.builtin.audit.local-file" }] } },
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`McpgStack` composes the common case: an `Operator`, a `TrustBootstrap` seeding
|
|
37
|
+
the cluster-default revocation list (set `trust: false` to skip it), and — when
|
|
38
|
+
`gateway` is given — one `Gateway` in the operator's namespace. Both of the
|
|
39
|
+
latter depend on the operator being installed first.
|
|
40
|
+
|
|
41
|
+
The package is built from this workspace:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pnpm --filter ./iac/pulumi/components run build # tsc → bin/
|
|
45
|
+
pnpm --filter ./iac/pulumi/components exec tsc -b --noEmit # tsc --noEmit
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Components
|
|
49
|
+
|
|
50
|
+
| Component | Creates |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `Operator` | The operator Helm release plus the CRDs as first-class resources. |
|
|
53
|
+
| `Gateway` | An `MCPGGateway`, with `spec.config` assembled by the config builder. |
|
|
54
|
+
| `PluginSet` | An `MCPGPluginSet` — plugin entries and optional capability grants. |
|
|
55
|
+
| `TrustBootstrap` | The cluster-default `MCPGRevocationList`, and carries the signing-key reference. |
|
|
56
|
+
| `Cluster` | An `MCPGCluster` — the coordination backend a gateway's `clusterRef` points at. |
|
|
57
|
+
| `Route` | An `MCPGRoute` — a tool subset bound into a shared gateway with identity, policy, and audit chains. |
|
|
58
|
+
| `TenantCR` | An `MCPGTenant` — declarative tenant governance. |
|
|
59
|
+
| `Tenant` | A namespace with isolation, and optionally a plugin set and a gateway. |
|
|
60
|
+
| `McpgStack` | Operator plus trust plus one gateway. |
|
|
61
|
+
|
|
62
|
+
### Operator and CRD lifecycle
|
|
63
|
+
|
|
64
|
+
Helm installs the files under a chart's `crds/` directory once and never
|
|
65
|
+
upgrades or deletes them. `Operator` sidesteps that by applying the CRDs as a
|
|
66
|
+
`ConfigGroup` it owns — server-side applied, upgraded like any other resource —
|
|
67
|
+
and telling the chart to stand back with `skipCrds` plus `crd.install=false`.
|
|
68
|
+
`crdsDir` points at the CRD YAMLs (default `../../codegen/schemas/v1alpha1/crds`,
|
|
69
|
+
resolved from the Pulumi program's directory).
|
|
70
|
+
|
|
71
|
+
The release defaults to namespace `mcpg-system`, chart `mcpg-operator` from
|
|
72
|
+
`ociChartRepo()`, and cert-manager enabled. Set `watchNamespace` to restrict the
|
|
73
|
+
operator to a single namespace. Anything in `values` is spread over those
|
|
74
|
+
defaults, so a key you supply replaces the corresponding default block.
|
|
75
|
+
|
|
76
|
+
`ociChartRepo()` composes `oci://<base>/charts` from the registry coordinates in
|
|
77
|
+
`src/oci.ts`, whose defaults mirror `tools/release/oci-registry.json`. Override
|
|
78
|
+
one chart with `chartRepo`, or a whole stack with `MCPG_OCI_HOST` /
|
|
79
|
+
`MCPG_OCI_NAMESPACE` / `MCPG_OCI_PATH` — the same environment contract the shell
|
|
80
|
+
and Node publish helpers honour. `ociImageRepo(name)` and `ociPluginRegistry()`
|
|
81
|
+
compose image and plugin references off the same base.
|
|
82
|
+
|
|
83
|
+
### Gateway readiness
|
|
84
|
+
|
|
85
|
+
`Gateway` annotates the resource with `pulumi.com/waitFor: condition=Ready`, so
|
|
86
|
+
`pulumi up` waits on the operator reporting the gateway ready rather than on the
|
|
87
|
+
custom resource merely existing. Pass `waitForReady: false` to skip the wait.
|
|
88
|
+
|
|
89
|
+
### Tenancy: two different things
|
|
90
|
+
|
|
91
|
+
`Tenant` is an imperative fan-out — it creates the namespace, a default-deny
|
|
92
|
+
cross-tenant ingress `NetworkPolicy` (unless `networkPolicy: false`), and
|
|
93
|
+
optionally a plugin set and a gateway inside it. Instantiate it in a loop to
|
|
94
|
+
build a fleet. It does **not** create an `MCPGTenant`, so it does not engage the
|
|
95
|
+
operator's tenant governance.
|
|
96
|
+
|
|
97
|
+
`TenantCR` creates the `MCPGTenant` custom resource: the plugin allowlist,
|
|
98
|
+
per-namespace quotas, replica ceilings, and exclusive namespace ownership the
|
|
99
|
+
operator enforces at admission. Use it when tenant boundaries must be enforced
|
|
100
|
+
rather than merely arranged, alongside or instead of `Tenant`.
|
|
101
|
+
|
|
102
|
+
### Cluster credentials
|
|
103
|
+
|
|
104
|
+
`Cluster` accepts `credentialRefs` naming pre-existing Secrets, and
|
|
105
|
+
`credentials`, where an entry carrying `data` makes the component create the
|
|
106
|
+
Secret in `operatorNamespace` (default `mcpg-system`) and wire the reference for
|
|
107
|
+
you. Both forms surface to the gateway as `cred://cluster/<name>`.
|
|
108
|
+
`TrustBootstrap` takes a signing-key Secret reference the same way: only the
|
|
109
|
+
reference travels, never the key bytes, so no signing material lands in Pulumi
|
|
110
|
+
state.
|
|
111
|
+
|
|
112
|
+
## Configuration
|
|
113
|
+
|
|
114
|
+
`Gateway` builds the gateway's `spec.config` from typed sections. The operator
|
|
115
|
+
passes that document through untouched, and the gateway parses it strictly —
|
|
116
|
+
unknown keys are a boot-time error, which makes the shape worth getting right.
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { Gateway } from "@mcpg/pulumi";
|
|
120
|
+
|
|
121
|
+
new Gateway("orders", {
|
|
122
|
+
namespace: "mcpg-system",
|
|
123
|
+
image: { repository: "ghcr.io/mcpg-dev/source-code/gateway", tag: "<tag>" },
|
|
124
|
+
server: { bind_address: "0.0.0.0:8080" },
|
|
125
|
+
governance: { audit: { sinks: [{ kind: "dev.mcpg.builtin.audit.local-file" }] } },
|
|
126
|
+
plugins: [
|
|
127
|
+
{ id: "dev.mcpg.backend.sql", source: { oci: "ghcr.io/mcpg-dev/source-code/plugins/backend-sql:protocol-1" } },
|
|
128
|
+
],
|
|
129
|
+
extraConfig: { mcp: { capabilities: { tools: [] } } },
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
| Field | Type | Default | Description |
|
|
134
|
+
|---|---|---|---|
|
|
135
|
+
| `server` | object | omitted | Emitted at `config.gateway.server`, under the `gateway:` umbrella — a top-level `server` key is rejected by the gateway's parser. |
|
|
136
|
+
| `governance` | object | omitted | Emitted at `config.governance`. Audit sinks are a list (`audit.sinks[]`), not a scalar. |
|
|
137
|
+
| `observability` | object | omitted | Emitted at `config.observability`. |
|
|
138
|
+
| `mcp` | object | omitted | Emitted at `config.mcp`. |
|
|
139
|
+
| `plugins` | array | omitted | Emitted at `config.plugins`, and only when non-empty. Each entry carries `id` and a `source` declaring exactly one of `path` or `oci`; the per-entry toggle is `disabled`, not `enabled`. |
|
|
140
|
+
| `extraConfig` | object | `{}` | An arbitrary configuration fragment merged last, so its top-level keys win. The escape hatch for anything the typed sections do not cover. |
|
|
141
|
+
|
|
142
|
+
The same last-wins rule applies one level up: `extraSpec` on `Gateway` is merged
|
|
143
|
+
over the assembled `spec`, after the typed passthrough blocks
|
|
144
|
+
(`acceptedRouteNamespaces`, `podAnnotations`, `podLabels`, `resources`,
|
|
145
|
+
`service`, `ingress`, `autoscaling`, `monitoring`, `networkPolicy`,
|
|
146
|
+
`podDisruptionBudget`, `workloadIdentity`, `scheduling`, `probes`,
|
|
147
|
+
`imagePullSecrets`).
|
|
148
|
+
|
|
149
|
+
The config builder's semantics are covered by unit tests that need no
|
|
150
|
+
dependencies beyond Node itself:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pnpm --filter ./iac/pulumi/components run build
|
|
154
|
+
node --test iac/pulumi/components/bin/config.test.js
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Licence
|
|
158
|
+
|
|
159
|
+
Apache-2.0.
|
|
160
|
+
|
|
161
|
+
## See also
|
|
162
|
+
|
|
163
|
+
- <https://mcpg.dev/docs/self-hosting/pulumi> — deploying MCPG with Pulumi.
|
|
164
|
+
- <https://mcpg.dev/docs/reference/operator-crds> — the custom resources these
|
|
165
|
+
components create.
|
|
166
|
+
- <https://mcpg.dev/docs/reference/configuration> — the full gateway
|
|
167
|
+
configuration schema behind `spec.config`.
|
|
168
|
+
- `iac/pulumi/crds` (the typed CRD SDK) and `iac/pulumi/policy` (the CrossGuard
|
|
169
|
+
policy pack that validates these resources at preview time).
|
package/bin/cluster.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as pulumi from "@pulumi/pulumi";
|
|
2
|
+
import { mcpg } from "@mcpg-dev/pulumi-crds";
|
|
3
|
+
export interface ClusterCredential {
|
|
4
|
+
/** cred://cluster/<name>. */
|
|
5
|
+
name: string;
|
|
6
|
+
secretName: string;
|
|
7
|
+
key?: string;
|
|
8
|
+
/** When set, the component CREATES the Secret in operatorNamespace; else it is assumed to pre-exist. */
|
|
9
|
+
data?: {
|
|
10
|
+
[key: string]: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export interface ClusterArgs {
|
|
14
|
+
/** single_node (default) | redis | nats | consul | etcd. */
|
|
15
|
+
backend?: string;
|
|
16
|
+
/** Per-backend connection config. MUST be empty for single_node, non-empty otherwise. */
|
|
17
|
+
config?: {
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
};
|
|
20
|
+
/** Optional { name } of the cluster-scoped MCPGPlugin supplying the cdylib. */
|
|
21
|
+
pluginRef?: {
|
|
22
|
+
name: string;
|
|
23
|
+
};
|
|
24
|
+
/** Optional [{ name, secretName, key? }] referencing PRE-EXISTING Secrets, surfaced as cred://cluster/<name>. */
|
|
25
|
+
credentialRefs?: any[];
|
|
26
|
+
/** Backend credentials this component manages — creates the Secret (when `data` is set) in operatorNamespace + wires the ref. */
|
|
27
|
+
credentials?: ClusterCredential[];
|
|
28
|
+
/** Namespace the operator runs in — where credential Secrets must live. */
|
|
29
|
+
operatorNamespace?: pulumi.Input<string>;
|
|
30
|
+
labels?: {
|
|
31
|
+
[key: string]: string;
|
|
32
|
+
};
|
|
33
|
+
resourceName?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* An MCPGCluster (cluster-scoped): the coordination backend a gateway's
|
|
37
|
+
* `clusterRef` points at. `single_node` takes no config; external backends
|
|
38
|
+
* require a non-empty connection block.
|
|
39
|
+
*/
|
|
40
|
+
export declare class Cluster extends pulumi.ComponentResource {
|
|
41
|
+
readonly cluster: mcpg.v1alpha1.MCPGCluster;
|
|
42
|
+
readonly name: pulumi.Output<string>;
|
|
43
|
+
constructor(name: string, args?: ClusterArgs, opts?: pulumi.ComponentResourceOptions);
|
|
44
|
+
}
|
package/bin/cluster.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.Cluster = void 0;
|
|
37
|
+
const pulumi = __importStar(require("@pulumi/pulumi"));
|
|
38
|
+
const k8s = __importStar(require("@pulumi/kubernetes"));
|
|
39
|
+
const pulumi_crds_1 = require("@mcpg-dev/pulumi-crds");
|
|
40
|
+
/**
|
|
41
|
+
* An MCPGCluster (cluster-scoped): the coordination backend a gateway's
|
|
42
|
+
* `clusterRef` points at. `single_node` takes no config; external backends
|
|
43
|
+
* require a non-empty connection block.
|
|
44
|
+
*/
|
|
45
|
+
class Cluster extends pulumi.ComponentResource {
|
|
46
|
+
constructor(name, args = {}, opts) {
|
|
47
|
+
super("mcpg:index:Cluster", name, {}, opts);
|
|
48
|
+
const cName = args.resourceName ?? name;
|
|
49
|
+
const ns = args.operatorNamespace ?? "mcpg-system";
|
|
50
|
+
// Managed credential Secrets (operator namespace) + their refs.
|
|
51
|
+
const managedRefs = [];
|
|
52
|
+
const secretDeps = [];
|
|
53
|
+
for (const c of args.credentials ?? []) {
|
|
54
|
+
if (c.data) {
|
|
55
|
+
const sec = new k8s.core.v1.Secret(`${cName}-cred-${c.name}`, {
|
|
56
|
+
metadata: { name: c.secretName, namespace: ns },
|
|
57
|
+
stringData: c.data,
|
|
58
|
+
type: "Opaque",
|
|
59
|
+
}, { parent: this });
|
|
60
|
+
secretDeps.push(sec);
|
|
61
|
+
}
|
|
62
|
+
managedRefs.push(c.key ? { name: c.name, secretName: c.secretName, key: c.key } : { name: c.name, secretName: c.secretName });
|
|
63
|
+
}
|
|
64
|
+
const credentialRefs = [...(args.credentialRefs ?? []), ...managedRefs];
|
|
65
|
+
const spec = { backend: args.backend ?? "single_node" };
|
|
66
|
+
if (args.config && Object.keys(args.config).length > 0)
|
|
67
|
+
spec.config = args.config;
|
|
68
|
+
if (args.pluginRef)
|
|
69
|
+
spec.pluginRef = args.pluginRef;
|
|
70
|
+
if (credentialRefs.length > 0)
|
|
71
|
+
spec.credentialRefs = credentialRefs;
|
|
72
|
+
this.cluster = new pulumi_crds_1.mcpg.v1alpha1.MCPGCluster(cName, {
|
|
73
|
+
metadata: { name: cName, labels: args.labels },
|
|
74
|
+
spec: spec,
|
|
75
|
+
}, { parent: this, dependsOn: secretDeps });
|
|
76
|
+
this.name = pulumi.output(cName);
|
|
77
|
+
this.registerOutputs({ name: this.name });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.Cluster = Cluster;
|
package/bin/config.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface GatewayConfigInput {
|
|
2
|
+
/**
|
|
3
|
+
* Gateway server/listener config. Emitted at config.gateway.server (the
|
|
4
|
+
* `gateway:` AppConfig umbrella holds server/admin/control_plane), NOT a
|
|
5
|
+
* top-level `server` key — that trips AppConfig's deny_unknown_fields.
|
|
6
|
+
*/
|
|
7
|
+
server?: any;
|
|
8
|
+
/**
|
|
9
|
+
* config.governance section (access / policy / approvals / audit). Audit
|
|
10
|
+
* sinks are a LIST, not a scalar:
|
|
11
|
+
* { audit: { sinks: [{ kind: "dev.mcpg.builtin.audit.local-file" }] } }.
|
|
12
|
+
* `dev.mcpg.builtin.audit.local-file` is the canonical built-in audit sink.
|
|
13
|
+
*/
|
|
14
|
+
governance?: any;
|
|
15
|
+
/** config.observability section. */
|
|
16
|
+
observability?: any;
|
|
17
|
+
/** config.mcp section (capabilities, etc.). */
|
|
18
|
+
mcp?: any;
|
|
19
|
+
/**
|
|
20
|
+
* Ordered config.plugins list (AppConfig PluginEntryConfig). Each entry:
|
|
21
|
+
* { id, source: { path | oci }, class?, kind?, config?, enforce?, disabled? }.
|
|
22
|
+
* The toggle is `disabled` (bool, default false) — there is no `enabled`.
|
|
23
|
+
* A `source` declaring exactly one of path/oci is required.
|
|
24
|
+
*/
|
|
25
|
+
plugins?: any[];
|
|
26
|
+
/** Arbitrary AppConfig fragment merged LAST into spec.config (wins). */
|
|
27
|
+
extraConfig?: Record<string, any>;
|
|
28
|
+
}
|
|
29
|
+
/** Assemble spec.config from typed sections + extraConfig (extraConfig wins). */
|
|
30
|
+
export declare function buildConfig(input: GatewayConfigInput): Record<string, any>;
|
package/bin/config.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The gateway config builder: assemble spec.config (the preserve-unknown
|
|
3
|
+
// AppConfig) from typed convenience sections plus an `extraConfig` escape hatch
|
|
4
|
+
// deep-merged LAST so it wins. Mirrors the Terraform module's config builder.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildConfig = buildConfig;
|
|
7
|
+
/** Assemble spec.config from typed sections + extraConfig (extraConfig wins). */
|
|
8
|
+
function buildConfig(input) {
|
|
9
|
+
const cfg = {};
|
|
10
|
+
// `server` lives under the `gateway:` umbrella, not at the top level.
|
|
11
|
+
if (input.server)
|
|
12
|
+
cfg.gateway = { server: input.server };
|
|
13
|
+
if (input.governance)
|
|
14
|
+
cfg.governance = input.governance;
|
|
15
|
+
if (input.observability)
|
|
16
|
+
cfg.observability = input.observability;
|
|
17
|
+
if (input.mcp)
|
|
18
|
+
cfg.mcp = input.mcp;
|
|
19
|
+
if (input.plugins && input.plugins.length > 0)
|
|
20
|
+
cfg.plugins = input.plugins;
|
|
21
|
+
return { ...cfg, ...(input.extraConfig ?? {}) };
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
// Unit tests for the config builder. Uses node's built-in test runner +
|
|
7
|
+
// assert (no extra deps). Run: `tsc && node --test bin/config.test.js`.
|
|
8
|
+
const node_test_1 = require("node:test");
|
|
9
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
10
|
+
const config_1 = require("./config");
|
|
11
|
+
(0, node_test_1.test)("typed sections are included; absent ones omitted", () => {
|
|
12
|
+
const c = (0, config_1.buildConfig)({ governance: { audit: { sinks: [{ kind: "dev.mcpg.builtin.audit.local-file" }] } } });
|
|
13
|
+
strict_1.default.equal(c.governance.audit.sinks[0].kind, "dev.mcpg.builtin.audit.local-file");
|
|
14
|
+
strict_1.default.equal("gateway" in c, false);
|
|
15
|
+
strict_1.default.equal("observability" in c, false);
|
|
16
|
+
});
|
|
17
|
+
(0, node_test_1.test)("server nests under config.gateway.server (not a top-level server key)", () => {
|
|
18
|
+
const c = (0, config_1.buildConfig)({ server: { listen_addr: "0.0.0.0:8080" } });
|
|
19
|
+
strict_1.default.equal("server" in c, false);
|
|
20
|
+
strict_1.default.deepEqual(c.gateway.server, { listen_addr: "0.0.0.0:8080" });
|
|
21
|
+
});
|
|
22
|
+
(0, node_test_1.test)("plugins included only when non-empty", () => {
|
|
23
|
+
strict_1.default.equal("plugins" in (0, config_1.buildConfig)({ plugins: [] }), false);
|
|
24
|
+
strict_1.default.deepEqual((0, config_1.buildConfig)({ plugins: [{ id: "db.read" }] }).plugins, [{ id: "db.read" }]);
|
|
25
|
+
});
|
|
26
|
+
(0, node_test_1.test)("extraConfig is merged LAST and wins", () => {
|
|
27
|
+
const c = (0, config_1.buildConfig)({
|
|
28
|
+
governance: { a: 1 },
|
|
29
|
+
extraConfig: { governance: { b: 2 }, extra: true },
|
|
30
|
+
});
|
|
31
|
+
strict_1.default.deepEqual(c.governance, { b: 2 }); // extraConfig wins
|
|
32
|
+
strict_1.default.equal(c.extra, true);
|
|
33
|
+
});
|
|
34
|
+
(0, node_test_1.test)("empty input yields empty config", () => {
|
|
35
|
+
strict_1.default.deepEqual((0, config_1.buildConfig)({}), {});
|
|
36
|
+
});
|
package/bin/gateway.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as pulumi from "@pulumi/pulumi";
|
|
2
|
+
import { mcpg } from "@mcpg-dev/pulumi-crds";
|
|
3
|
+
import { GatewayConfigInput } from "./config";
|
|
4
|
+
export interface GatewayArgs extends GatewayConfigInput {
|
|
5
|
+
namespace: pulumi.Input<string>;
|
|
6
|
+
/** Gateway image (CRD spec.image — e.g. { repository, tag }). Required. */
|
|
7
|
+
image: any;
|
|
8
|
+
replicas?: number;
|
|
9
|
+
pluginSetRef?: pulumi.Input<string>;
|
|
10
|
+
revocationListRef?: pulumi.Input<string>;
|
|
11
|
+
/** MCPGCluster coordination backend (spec.clusterRef.name) for HA gateways. */
|
|
12
|
+
clusterRef?: pulumi.Input<string>;
|
|
13
|
+
/** Soft-tenancy: namespaces whose MCPGRoutes this gateway accepts. */
|
|
14
|
+
acceptedRouteNamespaces?: string[];
|
|
15
|
+
podAnnotations?: {
|
|
16
|
+
[key: string]: string;
|
|
17
|
+
};
|
|
18
|
+
podLabels?: {
|
|
19
|
+
[key: string]: string;
|
|
20
|
+
};
|
|
21
|
+
resources?: any;
|
|
22
|
+
service?: any;
|
|
23
|
+
ingress?: any;
|
|
24
|
+
autoscaling?: any;
|
|
25
|
+
monitoring?: any;
|
|
26
|
+
networkPolicy?: any;
|
|
27
|
+
podDisruptionBudget?: any;
|
|
28
|
+
workloadIdentity?: any;
|
|
29
|
+
scheduling?: any;
|
|
30
|
+
probes?: any;
|
|
31
|
+
imagePullSecrets?: any;
|
|
32
|
+
/** Arbitrary spec-level fields merged LAST (for spec keys not yet typed). */
|
|
33
|
+
extraSpec?: {
|
|
34
|
+
[key: string]: any;
|
|
35
|
+
};
|
|
36
|
+
labels?: {
|
|
37
|
+
[key: string]: string;
|
|
38
|
+
};
|
|
39
|
+
/** Default true → annotate `pulumi.com/waitFor: condition=Ready`. */
|
|
40
|
+
waitForReady?: boolean;
|
|
41
|
+
/** MCPGGateway metadata.name (defaults to the component name). */
|
|
42
|
+
resourceName?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* An MCPGGateway built from typed inputs + the config builder. Readiness is the
|
|
46
|
+
* provider's built-in await via the `pulumi.com/waitFor` annotation.
|
|
47
|
+
*/
|
|
48
|
+
export declare class Gateway extends pulumi.ComponentResource {
|
|
49
|
+
readonly gateway: mcpg.v1alpha1.MCPGGateway;
|
|
50
|
+
readonly name: pulumi.Output<string>;
|
|
51
|
+
constructor(name: string, args: GatewayArgs, opts?: pulumi.ComponentResourceOptions);
|
|
52
|
+
}
|
package/bin/gateway.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.Gateway = void 0;
|
|
37
|
+
const pulumi = __importStar(require("@pulumi/pulumi"));
|
|
38
|
+
const pulumi_crds_1 = require("@mcpg-dev/pulumi-crds");
|
|
39
|
+
const config_1 = require("./config");
|
|
40
|
+
/**
|
|
41
|
+
* An MCPGGateway built from typed inputs + the config builder. Readiness is the
|
|
42
|
+
* provider's built-in await via the `pulumi.com/waitFor` annotation.
|
|
43
|
+
*/
|
|
44
|
+
class Gateway extends pulumi.ComponentResource {
|
|
45
|
+
constructor(name, args, opts) {
|
|
46
|
+
super("mcpg:index:Gateway", name, {}, opts);
|
|
47
|
+
const gwName = args.resourceName ?? name;
|
|
48
|
+
const spec = {
|
|
49
|
+
image: args.image,
|
|
50
|
+
config: (0, config_1.buildConfig)(args),
|
|
51
|
+
};
|
|
52
|
+
if (args.replicas !== undefined)
|
|
53
|
+
spec.replicas = args.replicas;
|
|
54
|
+
if (args.pluginSetRef)
|
|
55
|
+
spec.pluginSetRef = { name: args.pluginSetRef };
|
|
56
|
+
if (args.revocationListRef)
|
|
57
|
+
spec.revocationListRef = { name: args.revocationListRef };
|
|
58
|
+
if (args.clusterRef)
|
|
59
|
+
spec.clusterRef = { name: args.clusterRef };
|
|
60
|
+
// Typed passthrough blocks (same spec surface as the TF gateway module).
|
|
61
|
+
const optional = {
|
|
62
|
+
acceptedRouteNamespaces: args.acceptedRouteNamespaces,
|
|
63
|
+
podAnnotations: args.podAnnotations,
|
|
64
|
+
podLabels: args.podLabels,
|
|
65
|
+
resources: args.resources,
|
|
66
|
+
service: args.service,
|
|
67
|
+
ingress: args.ingress,
|
|
68
|
+
autoscaling: args.autoscaling,
|
|
69
|
+
monitoring: args.monitoring,
|
|
70
|
+
networkPolicy: args.networkPolicy,
|
|
71
|
+
podDisruptionBudget: args.podDisruptionBudget,
|
|
72
|
+
workloadIdentity: args.workloadIdentity,
|
|
73
|
+
scheduling: args.scheduling,
|
|
74
|
+
probes: args.probes,
|
|
75
|
+
imagePullSecrets: args.imagePullSecrets,
|
|
76
|
+
};
|
|
77
|
+
for (const [k, v] of Object.entries(optional))
|
|
78
|
+
if (v !== undefined)
|
|
79
|
+
spec[k] = v;
|
|
80
|
+
Object.assign(spec, args.extraSpec ?? {}); // escape hatch wins (matches TF extra_spec)
|
|
81
|
+
const annotations = args.waitForReady === false ? {} : { "pulumi.com/waitFor": "condition=Ready" };
|
|
82
|
+
this.gateway = new pulumi_crds_1.mcpg.v1alpha1.MCPGGateway(gwName, {
|
|
83
|
+
metadata: { name: gwName, namespace: args.namespace, labels: args.labels, annotations },
|
|
84
|
+
spec: spec,
|
|
85
|
+
}, { parent: this });
|
|
86
|
+
this.name = pulumi.output(gwName);
|
|
87
|
+
this.registerOutputs({ name: this.name });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
exports.Gateway = Gateway;
|
package/bin/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./config";
|
|
2
|
+
export * from "./oci";
|
|
3
|
+
export * from "./operator";
|
|
4
|
+
export * from "./gateway";
|
|
5
|
+
export * from "./pluginSet";
|
|
6
|
+
export * from "./trustBootstrap";
|
|
7
|
+
export * from "./tenant";
|
|
8
|
+
export * from "./cluster";
|
|
9
|
+
export * from "./route";
|
|
10
|
+
export * from "./tenantCr";
|
|
11
|
+
export * from "./mcpgStack";
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./config"), exports);
|
|
18
|
+
__exportStar(require("./oci"), exports);
|
|
19
|
+
__exportStar(require("./operator"), exports);
|
|
20
|
+
__exportStar(require("./gateway"), exports);
|
|
21
|
+
__exportStar(require("./pluginSet"), exports);
|
|
22
|
+
__exportStar(require("./trustBootstrap"), exports);
|
|
23
|
+
__exportStar(require("./tenant"), exports);
|
|
24
|
+
__exportStar(require("./cluster"), exports);
|
|
25
|
+
__exportStar(require("./route"), exports);
|
|
26
|
+
__exportStar(require("./tenantCr"), exports);
|
|
27
|
+
__exportStar(require("./mcpgStack"), exports);
|