@cyc-seattle/platform 0.4.0 → 0.5.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
@@ -92,7 +92,8 @@ GitHub environment that holds its identity and state bucket:
92
92
  | Push to `main` | `ci`, `dev`, then `prod-preview`, then `prod`, which waits for the `maintainers` approval |
93
93
 
94
94
  `prod-preview` runs `mise run preview` as `deploy-runner` and writes the diff to the job summary.
95
- `prod` runs `mise run deploy` on the same commit once approved. A PR from a fork gets no OIDC token,
95
+ `prod` runs `mise run deploy` on the same commit once approved. It runs only when the preview shows
96
+ changes in `cloud` or `cluster`, so a merge that changes nothing in prod asks for no approval. A PR from a fork gets no OIDC token,
96
97
  so its `dev` job is skipped.
97
98
 
98
99
  `PULUMI_STACK` picks what the tasks run (`scripts/each-stack`): `dev` is `smoke`, and `prod` is
@@ -51,6 +51,12 @@ export interface WorkloadArgs extends PodArgs {
51
51
  startupProbe?: Probe;
52
52
  /** Defaults to 1. */
53
53
  replicas?: number;
54
+ /** Annotations on each pod, such as `cluster-autoscaler.kubernetes.io/safe-to-evict`. */
55
+ podAnnotations?: Record<string, string>;
56
+ /** Prefers pods in different zones. A preference, so one zone's capacity can still schedule them all. */
57
+ spreadAcrossZones?: boolean;
58
+ /** Adds a PodDisruptionBudget that keeps this many pods up through drains. Must be below `replicas`. */
59
+ minAvailable?: number;
54
60
  }
55
61
  /** The pods' Workload Identity principal, for IAM grants. */
56
62
  export declare function workloadMember(platform: pulumi.Output<PlatformOutputs>, serviceAccountName: pulumi.Input<string>): pulumi.Output<string>;
package/dist/workload.js CHANGED
@@ -128,23 +128,43 @@ function deployment(parent, name, args, http) {
128
128
  // The API rejects two ports with one name, and only after the preview.
129
129
  throw new Error(`${name}: "ports" can't name a port "http"; a WebService's own port has that name.`);
130
130
  }
131
+ const replicas = args.replicas ?? 1;
132
+ if (args.minAvailable !== undefined && args.minAvailable >= replicas) {
133
+ // Such a budget blocks every drain, so node upgrades stall on it.
134
+ throw new Error(`${name}: "minAvailable" (${args.minAvailable}) must be below "replicas" (${replicas}).`);
135
+ }
131
136
  const ports = [...(http !== undefined ? [{ name: "http", port: http }] : []), ...(args.ports ?? [])];
132
137
  new k8s.apps.v1.Deployment(name, {
133
138
  metadata,
134
139
  spec: {
135
- replicas: args.replicas ?? 1,
140
+ replicas,
136
141
  selector: { matchLabels: labels },
137
142
  template: {
138
- metadata: { labels },
143
+ metadata: { labels, ...(args.podAnnotations && { annotations: args.podAnnotations }) },
139
144
  spec: podSpec(name, args, account.metadata.name, {
140
145
  ...(ports.length > 0 && { ports: ports.map((p) => ({ name: p.name, containerPort: p.port })) }),
141
146
  ...(args.readinessProbe && { readinessProbe: args.readinessProbe }),
142
147
  ...(args.livenessProbe && { livenessProbe: args.livenessProbe }),
143
148
  ...(args.startupProbe && { startupProbe: args.startupProbe }),
144
- }),
149
+ }).apply((spec) => ({
150
+ ...spec,
151
+ ...(args.spreadAcrossZones && {
152
+ topologySpreadConstraints: [
153
+ {
154
+ maxSkew: 1,
155
+ topologyKey: "topology.kubernetes.io/zone",
156
+ whenUnsatisfiable: "ScheduleAnyway",
157
+ labelSelector: { matchLabels: labels },
158
+ },
159
+ ],
160
+ }),
161
+ })),
145
162
  },
146
163
  },
147
164
  }, child);
165
+ if (args.minAvailable !== undefined) {
166
+ new k8s.policy.v1.PodDisruptionBudget(name, { metadata, spec: { minAvailable: args.minAvailable, selector: { matchLabels: labels } } }, child);
167
+ }
148
168
  if (ports.length === 0) {
149
169
  return { serviceAccount: account };
150
170
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyc-seattle/platform",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Pulumi components for CYC's shared Google Cloud platform: WebService, the platform outputs reader, and the stacks that build the platform",
5
5
  "author": "CYC Community Sailing Center",
6
6
  "license": "Apache-2.0",
@@ -18,7 +18,7 @@
18
18
  "engines": {
19
19
  "node": ">=22"
20
20
  },
21
- "packageManager": "pnpm@10.34.6",
21
+ "packageManager": "pnpm@10.34.7",
22
22
  "prettier": "@cyc-seattle/ts-config/prettier",
23
23
  "exports": {
24
24
  ".": {
@@ -46,11 +46,11 @@
46
46
  "@amiceli/vitest-cucumber": "7.0.0",
47
47
  "@cyc-seattle/ts-config": "^0.1.2",
48
48
  "@pulumi/command": "1.2.1",
49
- "@pulumi/gcp": "10.1.1",
49
+ "@pulumi/gcp": "10.2.0",
50
50
  "@pulumi/github": "6.15.0",
51
51
  "@pulumi/kubernetes": "4.34.2",
52
- "@pulumi/pulumi": "3.264.0",
53
- "@pulumi/random": "4.21.3",
52
+ "@pulumi/pulumi": "3.270.0",
53
+ "@pulumi/random": "4.21.4",
54
54
  "@types/js-yaml": "4.0.9",
55
55
  "@types/node": "^22.19.1",
56
56
  "eslint": "^9.39.5",