@crossdelta/infrastructure 0.8.6 → 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 CHANGED
@@ -3,17 +3,58 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@crossdelta/infrastructure.svg)](https://www.npmjs.com/package/@crossdelta/infrastructure)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- Opinionated Pulumi abstractions for deploying microservices to Kubernetes. Turns per-service config objects into Deployments, Services, Ingress, probes, and secrets.
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
- ## Installation
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
- ## Usage
20
+ ## End-to-End Example
15
21
 
16
- ### Service config
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
- replicas: 1,
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
- ### Deploy services
78
+ See `K8sServiceConfig` type for all available fields (replicas, volumes, strategy, containerEnv, etc.).
43
79
 
44
- ```typescript
45
- import { deployK8sServices, discoverServiceConfigs } from '@crossdelta/infrastructure'
46
-
47
- const serviceConfigs = discoverServiceConfigs('services')
80
+ ## Shared Cluster (Multi-Stack)
48
81
 
49
- deployK8sServices({
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
- import { deployRuntime } from '@crossdelta/infrastructure'
61
-
62
- const runtime = deployRuntime(provider, namespace, {
63
- nats: { enabled: true, config: { replicas: 1, jetstream: { enabled: true } } },
64
- ingress: {
65
- enabled: true,
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
@@ -1240,6 +1240,7 @@ var normalizeK8sConfig = (config) => {
1240
1240
  };
1241
1241
  var DEFAULTS = {
1242
1242
  replicas: 1,
1243
+ progressDeadlineSeconds: 120,
1243
1244
  resources: {
1244
1245
  requests: { cpu: "100m", memory: "128Mi" },
1245
1246
  limits: { cpu: "500m", memory: "512Mi" }
@@ -1451,6 +1452,7 @@ var deployK8sService = (provider, namespace, config) => {
1451
1452
  },
1452
1453
  spec: {
1453
1454
  replicas,
1455
+ progressDeadlineSeconds: normalizedConfig.progressDeadlineSeconds ?? DEFAULTS.progressDeadlineSeconds,
1454
1456
  strategy: {
1455
1457
  type: "RollingUpdate",
1456
1458
  rollingUpdate: {
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
@@ -1146,6 +1146,7 @@ var normalizeK8sConfig = (config) => {
1146
1146
  };
1147
1147
  var DEFAULTS = {
1148
1148
  replicas: 1,
1149
+ progressDeadlineSeconds: 120,
1149
1150
  resources: {
1150
1151
  requests: { cpu: "100m", memory: "128Mi" },
1151
1152
  limits: { cpu: "500m", memory: "512Mi" }
@@ -1357,6 +1358,7 @@ var deployK8sService = (provider, namespace, config) => {
1357
1358
  },
1358
1359
  spec: {
1359
1360
  replicas,
1361
+ progressDeadlineSeconds: normalizedConfig.progressDeadlineSeconds ?? DEFAULTS.progressDeadlineSeconds,
1360
1362
  strategy: {
1361
1363
  type: "RollingUpdate",
1362
1364
  rollingUpdate: {
@@ -28,8 +28,10 @@ export type Region = digitalocean.Region;
28
28
  * ```
29
29
  */
30
30
  export interface DOKSClusterConfig {
31
- /** Cluster name (will be suffixed with stack name) */
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
- /** VPC name (will be suffixed with stack name) */
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 */
@@ -268,6 +272,11 @@ export interface K8sServiceConfig {
268
272
  /** Max unavailable pods during rollout (default: 1) */
269
273
  maxUnavailable?: number;
270
274
  };
275
+ /**
276
+ * Max seconds for a deployment to make progress before it's considered failed.
277
+ * Defaults to 120. Kubernetes default is 600.
278
+ */
279
+ progressDeadlineSeconds?: number;
271
280
  /** Volume mounts for persistent storage */
272
281
  volumes?: K8sVolumeMount[];
273
282
  /** Command to run (overrides container entrypoint) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossdelta/infrastructure",
3
- "version": "0.8.6",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "publishConfig": {