@crossdelta/infrastructure 0.9.0 → 0.10.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 +63 -101
- package/dist/index.cjs +2 -2
- package/dist/index.js +2 -2
- package/dist/runtimes/doks/types.d.ts +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,17 +3,58 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@crossdelta/infrastructure)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Pulumi abstractions that turn per-service config objects into complete Kubernetes deployments. You describe **what** each service needs (ports, env, secrets, health checks), the package handles **how** (Deployments, Services, Ingress, TLS, probes, pull secrets).
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
**What you skip writing:**
|
|
9
|
+
- Boilerplate K8s YAML / Pulumi resource declarations per service
|
|
10
|
+
- NATS JetStream + cert-manager + NGINX Ingress setup
|
|
11
|
+
- Docker registry secrets, health probes, rolling update strategies
|
|
12
|
+
- Multi-environment concerns (shared cluster, per-stack namespaces)
|
|
13
|
+
|
|
14
|
+
## Install
|
|
9
15
|
|
|
10
16
|
```bash
|
|
11
17
|
npm install @crossdelta/infrastructure @pulumi/pulumi @pulumi/kubernetes
|
|
12
18
|
```
|
|
13
19
|
|
|
14
|
-
##
|
|
20
|
+
## End-to-End Example
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
One `index.ts` that provisions a cluster, runtime, and all services:
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import {
|
|
26
|
+
createDOKSCluster,
|
|
27
|
+
createVPC,
|
|
28
|
+
createNamespace,
|
|
29
|
+
deployRuntime,
|
|
30
|
+
deployK8sServices,
|
|
31
|
+
discoverServiceConfigs,
|
|
32
|
+
} from '@crossdelta/infrastructure'
|
|
33
|
+
|
|
34
|
+
// 1. Cluster
|
|
35
|
+
const vpc = createVPC({ name: 'my-vpc', region: 'fra1' })
|
|
36
|
+
const { provider } = createDOKSCluster({
|
|
37
|
+
name: 'my-cluster',
|
|
38
|
+
vpcUuid: vpc.id,
|
|
39
|
+
nodePool: { name: 'default', size: 's-4vcpu-8gb', nodeCount: 1 },
|
|
40
|
+
})
|
|
41
|
+
createNamespace(provider, 'my-namespace')
|
|
42
|
+
|
|
43
|
+
// 2. Runtime (toggle what you need)
|
|
44
|
+
const runtime = deployRuntime(provider, 'my-namespace', {
|
|
45
|
+
nats: { enabled: true, config: { replicas: 1, jetstream: { enabled: true } } },
|
|
46
|
+
ingress: { enabled: true },
|
|
47
|
+
certManager: { enabled: true, config: { email: 'ops@example.com' } },
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// 3. Services (auto-discovered from infra/services/*.ts)
|
|
51
|
+
const configs = discoverServiceConfigs('services')
|
|
52
|
+
deployK8sServices(provider, 'my-namespace', configs)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Service Config
|
|
56
|
+
|
|
57
|
+
Each file in `infra/services/` exports one config. The package derives Deployment, Service, Ingress, Secret, and probes from it:
|
|
17
58
|
|
|
18
59
|
```typescript
|
|
19
60
|
import { ports, type K8sServiceConfig } from '@crossdelta/infrastructure'
|
|
@@ -21,14 +62,9 @@ import { ports, type K8sServiceConfig } from '@crossdelta/infrastructure'
|
|
|
21
62
|
const config: K8sServiceConfig = {
|
|
22
63
|
name: 'my-service',
|
|
23
64
|
ports: ports().http(4000).public().build(),
|
|
24
|
-
|
|
25
|
-
healthCheck: {
|
|
26
|
-
httpPath: '/health',
|
|
27
|
-
readinessPath: '/health/ready',
|
|
28
|
-
},
|
|
65
|
+
healthCheck: { httpPath: '/health' },
|
|
29
66
|
ingress: { path: '/api', host: 'example.com' },
|
|
30
67
|
env: { DATABASE_URL: dbUrl },
|
|
31
|
-
containerEnv: { NODE_OPTIONS: '--max-old-space-size=384' },
|
|
32
68
|
secrets: { API_KEY: apiKey },
|
|
33
69
|
resources: {
|
|
34
70
|
requests: { cpu: '50m', memory: '64Mi' },
|
|
@@ -39,104 +75,30 @@ const config: K8sServiceConfig = {
|
|
|
39
75
|
export default config
|
|
40
76
|
```
|
|
41
77
|
|
|
42
|
-
|
|
78
|
+
See `K8sServiceConfig` type for all available fields (replicas, volumes, strategy, containerEnv, etc.).
|
|
43
79
|
|
|
44
|
-
|
|
45
|
-
import { deployK8sServices, discoverServiceConfigs } from '@crossdelta/infrastructure'
|
|
46
|
-
|
|
47
|
-
const serviceConfigs = discoverServiceConfigs('services')
|
|
80
|
+
## Shared Cluster (Multi-Stack)
|
|
48
81
|
|
|
49
|
-
|
|
50
|
-
provider,
|
|
51
|
-
namespace,
|
|
52
|
-
serviceConfigs,
|
|
53
|
-
env: { NATS_URL: natsUrl },
|
|
54
|
-
})
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### Deploy runtime components
|
|
82
|
+
When multiple Pulumi stacks share one cluster, use `clusterName`/`vpcName` to pin the DigitalOcean resource name (default appends the stack name):
|
|
58
83
|
|
|
59
84
|
```typescript
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
config: {
|
|
67
|
-
nginxConfig: {
|
|
68
|
-
'proxy-buffer-size': '16k', // Increase for large auth cookies/JWTs
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
certManager: { enabled: true, config: { email: 'ops@example.com' } },
|
|
85
|
+
// Stack A (stage) — owns the cluster
|
|
86
|
+
const { provider, kubeconfig } = createDOKSCluster({
|
|
87
|
+
name: 'my-cluster',
|
|
88
|
+
clusterName: 'my-cluster',
|
|
89
|
+
vpcUuid: vpc.id,
|
|
90
|
+
nodePool: { name: 'default', size: 's-4vcpu-8gb', nodeCount: 1 },
|
|
73
91
|
})
|
|
92
|
+
export const clusterKubeconfig = kubeconfig
|
|
93
|
+
|
|
94
|
+
// Stack B (production) — references the shared cluster
|
|
95
|
+
const stageStack = new StackReference('org/project/stage')
|
|
96
|
+
const provider = createK8sProviderFromKubeconfig(
|
|
97
|
+
'production',
|
|
98
|
+
stageStack.getOutput('clusterKubeconfig'),
|
|
99
|
+
)
|
|
74
100
|
```
|
|
75
101
|
|
|
76
|
-
## K8sServiceConfig
|
|
77
|
-
|
|
78
|
-
```typescript
|
|
79
|
-
interface K8sServiceConfig {
|
|
80
|
-
name: string
|
|
81
|
-
ports: PortConfig
|
|
82
|
-
replicas?: number
|
|
83
|
-
resources?: {
|
|
84
|
-
requests?: { cpu: string; memory: string }
|
|
85
|
-
limits?: { cpu: string; memory: string }
|
|
86
|
-
}
|
|
87
|
-
healthCheck?: {
|
|
88
|
-
httpPath: string
|
|
89
|
-
readinessPath?: string // Falls back to httpPath
|
|
90
|
-
initialDelaySeconds?: number // Default: 10
|
|
91
|
-
periodSeconds?: number // Default: 10
|
|
92
|
-
failureThreshold?: number // Default: 3
|
|
93
|
-
}
|
|
94
|
-
ingress?: {
|
|
95
|
-
path: string
|
|
96
|
-
host?: string
|
|
97
|
-
}
|
|
98
|
-
env?: Record<string, pulumi.Input<string>>
|
|
99
|
-
containerEnv?: Record<string, pulumi.Input<string>>
|
|
100
|
-
secrets?: Record<string, pulumi.Input<string>>
|
|
101
|
-
image?: string
|
|
102
|
-
skip?: boolean
|
|
103
|
-
}
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## Fluent Ports API
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
import { ports } from '@crossdelta/infrastructure'
|
|
110
|
-
|
|
111
|
-
ports().http(4000).build() // Simple HTTP
|
|
112
|
-
ports().http(4000).public().build() // Public (ingress)
|
|
113
|
-
ports().grpc(50051).build() // gRPC
|
|
114
|
-
ports().http(4000).addHttp(8080, 'metrics').build() // Multiple ports
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
| Method | Description |
|
|
118
|
-
|--------|-------------|
|
|
119
|
-
| `.http(port)` | HTTP primary port |
|
|
120
|
-
| `.grpc(port)` | gRPC primary port |
|
|
121
|
-
| `.primary(port, name?)` | Custom primary port |
|
|
122
|
-
| `.add(port, name?, protocol?)` | Additional port |
|
|
123
|
-
| `.addHttp(port, name?)` | Additional HTTP port |
|
|
124
|
-
| `.addGrpc(port, name?)` | Additional gRPC port |
|
|
125
|
-
| `.public()` | Expose via ingress |
|
|
126
|
-
| `.protocol(type)` | Set protocol (TCP, UDP, SCTP) |
|
|
127
|
-
| `.build()` | Build config |
|
|
128
|
-
|
|
129
|
-
## API Reference
|
|
130
|
-
|
|
131
|
-
| Function | Description |
|
|
132
|
-
|----------|-------------|
|
|
133
|
-
| `deployRuntime(provider, ns, config)` | Deploy runtime components |
|
|
134
|
-
| `discoverServiceConfigs(dir, opts?)` | Auto-discover service configs from filesystem |
|
|
135
|
-
| `deployK8sServices(opts)` | Deploy services to cluster |
|
|
136
|
-
| `createNamespace(provider, name)` | Create k8s namespace |
|
|
137
|
-
| `createImagePullSecret(...)` | Create registry pull secret |
|
|
138
|
-
| `ports()` | Fluent port builder |
|
|
139
|
-
|
|
140
102
|
## License
|
|
141
103
|
|
|
142
104
|
MIT © [crossdelta](https://crossdelta.de)
|
package/dist/index.cjs
CHANGED
|
@@ -1139,7 +1139,7 @@ function createDOKSCluster(config) {
|
|
|
1139
1139
|
const stack = pulumi4.getStack();
|
|
1140
1140
|
const region = config.region ?? "fra1";
|
|
1141
1141
|
const cluster = new digitalocean.KubernetesCluster(config.name, {
|
|
1142
|
-
name: `${config.name}-${stack}`,
|
|
1142
|
+
name: config.clusterName ?? `${config.name}-${stack}`,
|
|
1143
1143
|
region,
|
|
1144
1144
|
version: config.version ?? "1.32.10-do.0",
|
|
1145
1145
|
vpcUuid: config.vpcUuid,
|
|
@@ -1190,7 +1190,7 @@ function createVPC(config) {
|
|
|
1190
1190
|
const stack = pulumi5.getStack();
|
|
1191
1191
|
const region = config.region ?? "fra1";
|
|
1192
1192
|
return new digitalocean2.Vpc(config.name, {
|
|
1193
|
-
name: `${config.name}-${stack}`,
|
|
1193
|
+
name: config.vpcName ?? `${config.name}-${stack}`,
|
|
1194
1194
|
region,
|
|
1195
1195
|
description: config.description ?? `VPC for ${config.name}`,
|
|
1196
1196
|
ipRange: config.ipRange
|
package/dist/index.js
CHANGED
|
@@ -1045,7 +1045,7 @@ function createDOKSCluster(config) {
|
|
|
1045
1045
|
const stack = pulumi4.getStack();
|
|
1046
1046
|
const region = config.region ?? "fra1";
|
|
1047
1047
|
const cluster = new digitalocean.KubernetesCluster(config.name, {
|
|
1048
|
-
name: `${config.name}-${stack}`,
|
|
1048
|
+
name: config.clusterName ?? `${config.name}-${stack}`,
|
|
1049
1049
|
region,
|
|
1050
1050
|
version: config.version ?? "1.32.10-do.0",
|
|
1051
1051
|
vpcUuid: config.vpcUuid,
|
|
@@ -1096,7 +1096,7 @@ function createVPC(config) {
|
|
|
1096
1096
|
const stack = pulumi5.getStack();
|
|
1097
1097
|
const region = config.region ?? "fra1";
|
|
1098
1098
|
return new digitalocean2.Vpc(config.name, {
|
|
1099
|
-
name: `${config.name}-${stack}`,
|
|
1099
|
+
name: config.vpcName ?? `${config.name}-${stack}`,
|
|
1100
1100
|
region,
|
|
1101
1101
|
description: config.description ?? `VPC for ${config.name}`,
|
|
1102
1102
|
ipRange: config.ipRange
|
|
@@ -28,8 +28,10 @@ export type Region = digitalocean.Region;
|
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
30
|
export interface DOKSClusterConfig {
|
|
31
|
-
/**
|
|
31
|
+
/** Pulumi resource name (logical, for state tracking) */
|
|
32
32
|
name: string;
|
|
33
|
+
/** Actual cluster name in DigitalOcean. Defaults to `${name}-${stack}` */
|
|
34
|
+
clusterName?: string;
|
|
33
35
|
/** DigitalOcean region (defaults to 'fra1') */
|
|
34
36
|
region?: digitalocean.Region | string;
|
|
35
37
|
/** Kubernetes version - use `doctl kubernetes options versions` to list available */
|
|
@@ -65,8 +67,10 @@ export interface DOKSClusterConfig {
|
|
|
65
67
|
* Simplified VPC configuration with stack-aware naming.
|
|
66
68
|
*/
|
|
67
69
|
export interface VPCConfig {
|
|
68
|
-
/**
|
|
70
|
+
/** Pulumi resource name (logical, for state tracking) */
|
|
69
71
|
name: string;
|
|
72
|
+
/** Actual VPC name in DigitalOcean. Defaults to `${name}-${stack}` */
|
|
73
|
+
vpcName?: string;
|
|
70
74
|
/** DigitalOcean region (defaults to 'fra1') */
|
|
71
75
|
region?: digitalocean.Region | string;
|
|
72
76
|
/** Optional description */
|