@crossdelta/infrastructure 0.11.0 → 0.11.2
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 +59 -18
- package/dist/index.cjs +2 -1
- package/dist/index.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,23 +3,52 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@crossdelta/infrastructure)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
Pulumi
|
|
6
|
+
Opinionated Pulumi library for deploying microservices to **DigitalOcean Kubernetes (DOKS)**. You describe each service as a typed config object, the package generates Deployments, Services, Ingress, Secrets, health probes, and rolling update strategies.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
8
|
+
Built for teams running TypeScript microservices on DOKS with NATS JetStream, GHCR, and Pulumi. If that's your stack, this saves real boilerplate. If not, you're probably better off with plain Pulumi or Helm.
|
|
9
|
+
|
|
10
|
+
## When to use this
|
|
11
|
+
|
|
12
|
+
- You deploy multiple TypeScript services to a **DigitalOcean** Kubernetes cluster
|
|
13
|
+
- You use **Pulumi** (not Terraform, not Helm-only) for infrastructure
|
|
14
|
+
- You want one config object per service instead of ~150 lines of K8s resource declarations
|
|
15
|
+
- You need NATS JetStream, NGINX Ingress, cert-manager, or Caddy as a reverse proxy
|
|
16
|
+
|
|
17
|
+
## When NOT to use this
|
|
18
|
+
|
|
19
|
+
- **AWS/GCP/Azure**: Only DOKS is implemented. EKS/AKS/GKE are not supported.
|
|
20
|
+
- **Terraform or plain Helm**: This is Pulumi-only.
|
|
21
|
+
- **Non-opinionated setups**: The package makes choices for you (Helm chart versions, deployment strategies, DO-specific annotations). If you need full control, use Pulumi directly.
|
|
22
|
+
- **Single-service projects**: The overhead isn't worth it for one service.
|
|
13
23
|
|
|
14
24
|
## Install
|
|
15
25
|
|
|
16
26
|
```bash
|
|
17
27
|
npm install @crossdelta/infrastructure @pulumi/pulumi @pulumi/kubernetes
|
|
28
|
+
# For DOKS cluster creation:
|
|
29
|
+
npm install @pulumi/digitalocean
|
|
30
|
+
# For NATS stream deployment (optional):
|
|
31
|
+
npm install @crossdelta/cloudevents
|
|
18
32
|
```
|
|
19
33
|
|
|
20
|
-
##
|
|
34
|
+
## What's included
|
|
35
|
+
|
|
36
|
+
| Module | What it does |
|
|
37
|
+
|--------|-------------|
|
|
38
|
+
| **Cluster** | `createDOKSCluster()`, `createVPC()`, `createK8sProviderFromKubeconfig()` |
|
|
39
|
+
| **Workloads** | `deployK8sService()`, `deployK8sServices()`, `createNamespace()`, `createImagePullSecret()` |
|
|
40
|
+
| **NGINX Ingress** | `deployNginxIngress()` via Helm (chart v4.11.3) |
|
|
41
|
+
| **cert-manager** | `deployCertManager()` via Helm (chart v1.16.2) |
|
|
42
|
+
| **NATS** | `deployNats()` via Helm (chart v1.2.6), `buildNatsUrl()` |
|
|
43
|
+
| **Caddy** | `deployCaddy()`, `generateCaddyfile()` for reverse proxy with on-demand TLS |
|
|
44
|
+
| **Streams** | `collectStreamDefinitions()`, `deployStreams()`, `materializeStreams()` (NATS JetStream) |
|
|
45
|
+
| **Service Discovery** | `discoverServiceConfigs()` auto-discovers configs from `infra/services/*.ts` |
|
|
46
|
+
| **Local Dev** | `generateComposeYaml()`, k3d cluster scripts |
|
|
47
|
+
| **CLI** | `generate-env` generates `.env.local` from Pulumi config + service discovery |
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
21
50
|
|
|
22
|
-
One `index.ts` that provisions a cluster, runtime, and all services:
|
|
51
|
+
One `index.ts` that provisions a cluster, runtime components, and all services:
|
|
23
52
|
|
|
24
53
|
```typescript
|
|
25
54
|
import {
|
|
@@ -31,7 +60,6 @@ import {
|
|
|
31
60
|
discoverServiceConfigs,
|
|
32
61
|
} from '@crossdelta/infrastructure'
|
|
33
62
|
|
|
34
|
-
// 1. Cluster
|
|
35
63
|
const vpc = createVPC({ name: 'my-vpc', region: 'fra1' })
|
|
36
64
|
const { provider } = createDOKSCluster({
|
|
37
65
|
name: 'my-cluster',
|
|
@@ -40,21 +68,19 @@ const { provider } = createDOKSCluster({
|
|
|
40
68
|
})
|
|
41
69
|
createNamespace(provider, 'my-namespace')
|
|
42
70
|
|
|
43
|
-
// 2. Runtime (toggle what you need)
|
|
44
71
|
const runtime = deployRuntime(provider, 'my-namespace', {
|
|
45
72
|
nats: { enabled: true, config: { replicas: 1, jetstream: { enabled: true } } },
|
|
46
73
|
ingress: { enabled: true },
|
|
47
74
|
certManager: { enabled: true, config: { email: 'ops@example.com' } },
|
|
48
75
|
})
|
|
49
76
|
|
|
50
|
-
// 3. Services (auto-discovered from infra/services/*.ts)
|
|
51
77
|
const configs = discoverServiceConfigs('services')
|
|
52
78
|
deployK8sServices(provider, 'my-namespace', configs)
|
|
53
79
|
```
|
|
54
80
|
|
|
55
81
|
## Service Config
|
|
56
82
|
|
|
57
|
-
Each file in `infra/services/` exports one config. The package derives
|
|
83
|
+
Each file in `infra/services/` exports one config object. The package derives all K8s resources from it:
|
|
58
84
|
|
|
59
85
|
```typescript
|
|
60
86
|
import { ports, type K8sServiceConfig } from '@crossdelta/infrastructure'
|
|
@@ -75,14 +101,14 @@ const config: K8sServiceConfig = {
|
|
|
75
101
|
export default config
|
|
76
102
|
```
|
|
77
103
|
|
|
78
|
-
See `K8sServiceConfig`
|
|
104
|
+
See `K8sServiceConfig` for all fields: replicas, volumes, strategy, containerEnv, labels, annotations, serviceType, etc.
|
|
79
105
|
|
|
80
106
|
## Shared Cluster (Multi-Stack)
|
|
81
107
|
|
|
82
|
-
|
|
108
|
+
Pin DigitalOcean resource names with `clusterName`/`vpcName` to share a cluster across Pulumi stacks:
|
|
83
109
|
|
|
84
110
|
```typescript
|
|
85
|
-
//
|
|
111
|
+
// Stage stack owns the cluster
|
|
86
112
|
const { provider, kubeconfig } = createDOKSCluster({
|
|
87
113
|
name: 'my-cluster',
|
|
88
114
|
clusterName: 'my-cluster',
|
|
@@ -91,7 +117,7 @@ const { provider, kubeconfig } = createDOKSCluster({
|
|
|
91
117
|
})
|
|
92
118
|
export const clusterKubeconfig = kubeconfig
|
|
93
119
|
|
|
94
|
-
//
|
|
120
|
+
// Production stack references stage
|
|
95
121
|
const stageStack = new StackReference('org/project/stage')
|
|
96
122
|
const provider = createK8sProviderFromKubeconfig(
|
|
97
123
|
'production',
|
|
@@ -99,9 +125,24 @@ const provider = createK8sProviderFromKubeconfig(
|
|
|
99
125
|
)
|
|
100
126
|
```
|
|
101
127
|
|
|
102
|
-
## generate-env
|
|
128
|
+
## generate-env CLI
|
|
129
|
+
|
|
130
|
+
Generates `.env.local` from Pulumi secrets and discovered service configs:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npx generate-env # defaults to stage stack
|
|
134
|
+
npx generate-env --stack=production
|
|
135
|
+
npx generate-env --no-pulumi # skip Pulumi, only service discovery
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Limitations
|
|
139
|
+
|
|
140
|
+
- **DOKS only.** No other cloud provider is implemented.
|
|
141
|
+
- **Helm chart versions are pinned.** NGINX v4.11.3, cert-manager v1.16.2, NATS v1.2.6. No override option yet.
|
|
142
|
+
- **Service discovery assumes a convention:** `infra/services/*.ts` files with `export default config`.
|
|
143
|
+
- **`@crossdelta/cloudevents` is a hard dependency** even if you don't use NATS streams https://github.com/orderboss/platform/issues/TBD.
|
|
144
|
+
- **Caddy deployment uses Recreate strategy** (RWO PVC incompatible with rolling updates).
|
|
103
145
|
|
|
104
|
-
Generates `.env.local` from Pulumi secrets (default: `stage` stack) and discovered services. Override with `--stack=production` or `--no-pulumi`.
|
|
105
146
|
|
|
106
147
|
## License
|
|
107
148
|
|
package/dist/index.cjs
CHANGED
|
@@ -1149,7 +1149,8 @@ var indent = (text, level) => {
|
|
|
1149
1149
|
`).map((line) => line.trim() === "" ? "" : `${prefix}${line}`).join(`
|
|
1150
1150
|
`);
|
|
1151
1151
|
};
|
|
1152
|
-
var
|
|
1152
|
+
var encodeHash = (hash) => hash.startsWith("$") ? Buffer.from(hash).toString("base64") : hash;
|
|
1153
|
+
var basicAuthLines = (basicAuth) => basicAuth ? ["basic_auth {", ` ${basicAuth.user} ${encodeHash(basicAuth.hash)}`, "}"] : [];
|
|
1153
1154
|
var generateHandleBlock = (handle, level, basicAuth) => {
|
|
1154
1155
|
const hasPath = handle.path != null;
|
|
1155
1156
|
const header = hasPath ? `handle ${handle.path}* {` : "handle {";
|
package/dist/index.js
CHANGED
|
@@ -1053,7 +1053,8 @@ var indent = (text, level) => {
|
|
|
1053
1053
|
`).map((line) => line.trim() === "" ? "" : `${prefix}${line}`).join(`
|
|
1054
1054
|
`);
|
|
1055
1055
|
};
|
|
1056
|
-
var
|
|
1056
|
+
var encodeHash = (hash) => hash.startsWith("$") ? Buffer.from(hash).toString("base64") : hash;
|
|
1057
|
+
var basicAuthLines = (basicAuth) => basicAuth ? ["basic_auth {", ` ${basicAuth.user} ${encodeHash(basicAuth.hash)}`, "}"] : [];
|
|
1057
1058
|
var generateHandleBlock = (handle, level, basicAuth) => {
|
|
1058
1059
|
const hasPath = handle.path != null;
|
|
1059
1060
|
const header = hasPath ? `handle ${handle.path}* {` : "handle {";
|