@cyc-seattle/platform 1.1.0 → 1.3.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
@@ -6,19 +6,19 @@ app owns. The apps that run on it live in their own repos, such as
6
6
 
7
7
  ## Stacks
8
8
 
9
- | Stack | Holds | Applied by |
10
- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
11
- | [`bootstrap`](stacks/bootstrap) | Service accounts and project IAM, the Pulumi state buckets and KMS keys, and the org's GitHub repos and settings | A human with project Owner and `gh` `admin:org`. Never CI. |
12
- | [`cloud`](stacks/cloud) | The VPC, the cluster subnet and its Cloud NAT on a static address, the private services peering, the prod and dev Cloud SQL instances, the DNS zones, and Artifact Registry | `deploy-runner`, from Actions after approval. A deployer, with `mise run deploy`, when breaking glass. |
13
- | [`cluster`](stacks/cluster) | The GKE Autopilot cluster, the shared Gateway, its address, certificates, and dev's DNS records, the `dev` and `prod` namespaces with their quotas and network policies, dev's RBAC, the prod Job alerts, and each environment's `platform/outputs.json` | `deploy-runner`, from Actions after approval. A deployer, with `mise run deploy`, when breaking glass. |
14
- | [`smoke`](stacks/smoke) | One `WebService` in dev, at `smoke.dev.cycsail.team`, for the integration tests | `deploy-dev`, from Actions on every PR. A deployer, with `PULUMI_STACK=dev mise run deploy`, when breaking glass. |
9
+ | Stack | Holds | Applied by |
10
+ | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
11
+ | [`bootstrap`](stacks/bootstrap) | Service accounts and project IAM, the Pulumi state buckets and KMS keys, and the org's GitHub repos and settings | A human with project Owner and `gh` `admin:org`. Never CI. |
12
+ | [`infra`](stacks/infra) | The VPC, the cluster subnet and its Cloud NAT on a static address, the private services peering, the prod and dev Cloud SQL instances, the DNS zones, Artifact Registry, the GKE Autopilot cluster, the shared Gateway, its address, certificates, and dev's DNS records, the `dev` and `prod` namespaces with their quotas and network policies, dev's RBAC, the prod Job alerts, and each environment's `platform/outputs.json` | `deploy-runner`, from Actions after approval. A deployer, with `mise run deploy`, when breaking glass. |
13
+ | [`smoke`](stacks/smoke) | One `WebService` in dev, at `smoke.dev.cycsail.team`, for the integration tests | `deploy-dev`, from Actions on every PR. A deployer, with `PULUMI_STACK=dev mise run deploy`, when breaking glass. |
14
+ | [`backend`](stacks/backend) | Directus, with the schema apply from `schemas/`, its roles, policies, and permission rules, the sync machine users, its OIDC client, and MCP, and the clubspot-sync and gsuite-sync CronJobs, with dev's Clubspot and Workspace fakes | `deploy-dev`, from Actions on every PR. Dev only, until prod cuts over (cyc-seattle/workplace#6 step 6). |
15
15
 
16
16
  ## @cyc-seattle/platform
17
17
 
18
18
  `src/` is the `@cyc-seattle/platform` package, which the stacks here and the apps' stacks import:
19
19
 
20
20
  - `getPlatformOutputs()` reads an environment's `platform/outputs.json` from its state bucket. The
21
- `cluster` stack writes it. It holds the environment's namespace and domain, the Gateway and its
21
+ `infra` stack writes it. It holds the environment's namespace and domain, the Gateway and its
22
22
  address (`gatewayAddress`, for an app's own DNS records), and the Cloud SQL instance's connection
23
23
  name and private IP (`sqlHost`). Dev stacks can't read prod's backend, so this replaces a
24
24
  StackReference for platform values (cyc-seattle/isthmia#215).
@@ -47,7 +47,9 @@ releases each merge to `main` with semantic-release, versioned by the convention
47
47
 
48
48
  Every app runs on the one GKE Autopilot cluster, in its environment's namespace. Prod runs on
49
49
  regular capacity, and dev on Spot pods. isthmia's apps (Authentik, Directus, the portal, and the
50
- two syncs as CronJobs) run in both, and dev adds Mailpit and the Clubspot and Workspace fakes.
50
+ two syncs as CronJobs) run in both, and dev adds Mailpit and the Clubspot and Workspace fakes. In dev,
51
+ platform's `backend` deploys Directus, the syncs, and the fakes, and dev's sync CronJobs are
52
+ suspended: the integration features and `mise run reset` run them as Jobs (cyc-seattle/isthmia#271).
51
53
 
52
54
  | Stack | Namespace | Domain | Deploy identity |
53
55
  | ------ | --------- | ------------------ | --------------- |
@@ -56,19 +58,19 @@ two syncs as CronJobs) run in both, and dev adds Mailpit and the Clubspot and Wo
56
58
 
57
59
  Each namespace denies ingress from other namespaces except the load balancer's. Dev also admits the
58
60
  Kubernetes API's service proxy, which the integration tests use, and denies egress to the mail ports
59
- (25, 465, 587) outside the cluster. `cluster` writes dev's A records; each prod app writes its own,
61
+ (25, 465, 587) outside the cluster. `infra` writes dev's A records; each prod app writes its own,
60
62
  pointing at `gatewayAddress`.
61
63
 
62
64
  `deploy-dev` reads Secret Manager secrets whose IDs start with `dev-`. Read them with
63
65
  `gcp.secretmanager.getSecretVersionAccess`, which needs only the accessor role.
64
66
 
65
- `cloud` creates each app's dev database, user, and password secret (`dev-db-<name>-password`) on
66
- `cyc-dev`, for each name in `cloud:devDatabases`. To add an app's dev database, add its name there in a
67
+ `infra` creates each app's dev database, user, and password secret (`dev-db-<name>-password`) on
68
+ `cyc-dev`, for each name in `infra:devDatabases`. To add an app's dev database, add its name there in a
67
69
  platform PR: Cloud SQL ignores IAM conditions on its user and database calls, so `deploy-dev` can't
68
70
  hold a role scoped to `cyc-dev`.
69
71
 
70
72
  A Job that fails in `prod`, or a CronJob there with no successful Job in 3 hours, raises an alert.
71
- It emails each address in `cluster:alertEmails`, a secret in the stack's config, which holds the
73
+ It emails each address in `infra:alertEmails`, a secret in the stack's config, which holds the
72
74
  maintainer's address (#20).
73
75
 
74
76
  ## Setup
@@ -100,11 +102,11 @@ GitHub environment that holds its identity and state bucket:
100
102
 
101
103
  `prod-preview` runs `mise run preview` as `deploy-runner` and writes the diff to the job summary.
102
104
  `prod` runs `mise run deploy` on the same commit once approved. It runs only when the preview shows
103
- 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,
105
+ changes in `infra`, so a merge that changes nothing in prod asks for no approval. A PR from a fork gets no OIDC token,
104
106
  so its `dev` job is skipped.
105
107
 
106
- `PULUMI_STACK` picks what the tasks run (`scripts/each-stack`): `dev` is `smoke`, and `prod` is
107
- `cloud`, then `cluster`. `integration` is dev only.
108
+ `PULUMI_STACK` picks what the tasks run (`scripts/each-stack`): `dev` is `smoke`, then `backend`, and
109
+ `prod` is `infra`. `integration` is dev only.
108
110
 
109
111
  `bootstrap` is never deployed from Actions (cyc-seattle/isthmia#118). It holds the identities that
110
112
  CI assumes, and a human with project Owner and `gh` `admin:org` applies it with the tasks below.
@@ -121,8 +123,8 @@ mise run deploy
121
123
  PULUMI_STACK=dev mise run deploy
122
124
  ```
123
125
 
124
- `mise run deploy` applies `cloud`, then `cluster`. Prod stacks read `cloud`'s outputs through a
125
- StackReference. Dev stacks can't read prod state, so `cluster` writes each environment's platform
126
+ `mise run deploy` applies `infra`. isthmia's prod stacks read its outputs through a StackReference.
127
+ Dev stacks can't read prod state, so `infra` writes each environment's platform
126
128
  values to `platform/outputs.json` in that environment's state bucket.
127
129
 
128
130
  `bootstrap`'s GitHub provider uses `gh auth token`, so log `gh` in as a cyc-seattle org owner with
@@ -11,7 +11,7 @@ export interface ClusterProviderArgs {
11
11
  /**
12
12
  * A Kubernetes provider for the environment's cluster. Its kubeconfig runs gke-gcloud-auth-plugin
13
13
  * with Application Default Credentials, so it is the same on every run, unlike one that embeds an
14
- * access token. The plugin must be on PATH: `mise run install` adds it. The cluster stack passes
14
+ * access token. The plugin must be on PATH: `mise run install` adds it. The infra stack passes
15
15
  * only the endpoint, because it writes the rest.
16
16
  */
17
17
  export declare function clusterProvider(platform: pulumi.Input<{
@@ -3,7 +3,7 @@ import * as pulumi from "@pulumi/pulumi";
3
3
  /**
4
4
  * A Kubernetes provider for the environment's cluster. Its kubeconfig runs gke-gcloud-auth-plugin
5
5
  * with Application Default Credentials, so it is the same on every run, unlike one that embeds an
6
- * access token. The plugin must be on PATH: `mise run install` adds it. The cluster stack passes
6
+ * access token. The plugin must be on PATH: `mise run install` adds it. The infra stack passes
7
7
  * only the endpoint, because it writes the rest.
8
8
  */
9
9
  export function clusterProvider(platform, name = "cluster", opts, args = {}) {
package/dist/outputs.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as gcp from "@pulumi/gcp";
2
2
  import * as pulumi from "@pulumi/pulumi";
3
3
  import { z } from "zod";
4
- // The cluster stack writes this object to each environment's state bucket, because a StackReference
4
+ // The infra stack writes this object to each environment's state bucket, because a StackReference
5
5
  // can't cross from a dev stack into prod's backend (cyc-seattle/isthmia#215).
6
6
  export const platformOutputsSchema = z.object({
7
7
  project: z.string().min(1),
@@ -49,10 +49,10 @@ export async function readPlatformOutputs(args = {}) {
49
49
  ({ content } = await gcp.storage.getBucketObjectContent({ bucket, name: outputsObject }));
50
50
  }
51
51
  catch (e) {
52
- throw new Error(`Can't read ${source}. The cluster stack writes it.\n${e.message}`);
52
+ throw new Error(`Can't read ${source}. The infra stack writes it.\n${e.message}`);
53
53
  }
54
54
  if (!content) {
55
- throw new Error(`${source} is empty. The cluster stack writes it.`);
55
+ throw new Error(`${source} is empty. The infra stack writes it.`);
56
56
  }
57
57
  return parsePlatformOutputs(content, source);
58
58
  }
@@ -9,6 +9,11 @@ export interface ScheduledJobArgs extends PodArgs {
9
9
  activeDeadlineSeconds?: number;
10
10
  /** How many times a failed run retries. Defaults to 0, so the next scheduled run is the retry. */
11
11
  backoffLimit?: number;
12
+ /**
13
+ * Stops the scheduled runs, while a Job made from the CronJob still runs. Defaults to false. Unset,
14
+ * it stays out of the spec, so the CronJobs deployed before it existed show no diff.
15
+ */
16
+ suspend?: boolean;
12
17
  }
13
18
  /**
14
19
  * A CronJob with the platform's secure defaults, and a Kubernetes ServiceAccount, both named `name`.
@@ -22,6 +22,7 @@ export class ScheduledJob extends pulumi.ComponentResource {
22
22
  schedule: args.schedule,
23
23
  timeZone: args.timeZone ?? "America/Los_Angeles",
24
24
  concurrencyPolicy: "Forbid",
25
+ ...(args.suspend !== undefined && { suspend: args.suspend }),
25
26
  jobTemplate: {
26
27
  metadata: { labels },
27
28
  spec: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyc-seattle/platform",
3
- "version": "1.1.0",
3
+ "version": "1.3.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",
@@ -47,6 +47,7 @@
47
47
  "@amiceli/vitest-cucumber": "7.0.0",
48
48
  "@cyc-seattle/ts-config": "^0.1.2",
49
49
  "@pulumi/command": "1.2.1",
50
+ "@pulumi/docker-build": "0.0.22",
50
51
  "@pulumi/gcp": "10.2.0",
51
52
  "@pulumi/github": "6.15.0",
52
53
  "@pulumi/kubernetes": "4.34.2",