1import * as GCP from "@/GCP";
2import * as Effect from "effect/Effect";
3import { DEFAULT_NETWORK } from "../../networkQuota.ts";
4
5export const REGION = "us-central1";
6export const ZONE = "us-central1-a";
7export const MARKER = "alchemy-gcp-instance-smoke";
8export const DATA_DEVICE = "smoke-data";
9export const WEB_TAG = "alchemy-smoke-web";
10
11/**
12 * Boots a tiny HTTP server on :80 serving `/index.html`, which carries the
13 * marker, the hostname, and whether the extra data disk is visible to the
14 * guest (`/dev/disk/by-id/google-{DATA_DEVICE}`).
15 */
16const STARTUP_SCRIPT = `#!/bin/bash
17set -eu
18mkdir -p /srv/www
19if [ -e /dev/disk/by-id/google-${DATA_DEVICE} ]; then DISK=present; else DISK=missing; fi
20printf '%s host=%s disk=%s\\n' '${MARKER}' "$(hostname)" "$DISK" > /srv/www/index.html
21cd /srv/www
22nohup python3 -m http.server 80 >/var/log/smoke-http.log 2>&1 &
23`;
24
25/**
26 * A tcp:80 firewall on the project's `default` network, a static external address, an extra
27 * data disk, and an e2-micro Debian VM that uses all of them. `phase` drives
28 * the label/metadata update step of the smoke.
29 */
30export const webVm = (phase: "initial" | "updated") =>
31 Effect.gen(function* () {
32 const firewall = yield* GCP.Compute.Firewall("SmokeAllowHttp", {
33 network: DEFAULT_NETWORK,
34 allowed: [{ protocol: "tcp", ports: ["80"] }],
35 sourceRanges: ["0.0.0.0/0"],
36 targetTags: [WEB_TAG],
37 });
38 const address = yield* GCP.Compute.Address("SmokeAddress", {
39 region: REGION,
40 });
41 const disk = yield* GCP.Compute.Disk("SmokeData", {
42 zone: ZONE,
43 sizeGb: 10,
44 });
45 const instance = yield* GCP.Compute.Instance("SmokeVm", {
46 zone: ZONE,
47 machineType: "e2-micro",
48 sourceImage: "projects/debian-cloud/global/images/family/debian-12",
49 network: `global/networks/${DEFAULT_NETWORK}`,
50 // The auto-mode `default` network's subnet in REGION is also `default`.
51 subnetwork: `regions/${REGION}/subnetworks/${DEFAULT_NETWORK}`,
52 natIP: address.address.as<string>(),
53 attachedDisks: [
54 {
55 source: disk.selfLink.as<string>(),
56 deviceName: DATA_DEVICE,
57 },
58 ],
59 tags: [WEB_TAG],
60 labels:
61 phase === "initial"
62 ? { smoke: "instance" }
63 : { smoke: "instance", phase: "updated" },
64 metadata: {
65 "startup-script": STARTUP_SCRIPT,
66 "smoke-phase": phase,
67 },
68 });
69 return {
70 instanceName: instance.instanceName,
71 instanceId: instance.instanceId,
72 status: instance.status,
73 natIP: instance.natIP,
74 labels: instance.labels,
75 metadata: instance.metadata,
76 attachedDisks: instance.attachedDisks,
77 addressName: address.addressName,
78 address: address.address,
79 diskName: disk.diskName,
80 firewallName: firewall.firewallName,
81 };
82 });