@vellumai/cli 0.1.1 → 0.1.2

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/src/lib/gcp.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { spawn } from "child_process";
2
+
1
3
  import { exec, execOutput } from "./step-runner";
2
4
 
3
5
  export async function getActiveProject(): Promise<string> {
@@ -39,16 +41,19 @@ interface FirewallRuleState {
39
41
  async function describeFirewallRule(
40
42
  ruleName: string,
41
43
  project: string,
44
+ account?: string,
42
45
  ): Promise<FirewallRuleState | null> {
43
46
  try {
44
- const output = await execOutput("gcloud", [
47
+ const args = [
45
48
  "compute",
46
49
  "firewall-rules",
47
50
  "describe",
48
51
  ruleName,
49
52
  `--project=${project}`,
50
53
  "--format=json(name,direction,allowed,sourceRanges,destinationRanges,targetTags,description)",
51
- ]);
54
+ ];
55
+ if (account) args.push(`--account=${account}`);
56
+ const output = await execOutput("gcloud", args);
52
57
  const parsed = JSON.parse(output);
53
58
  const allowed = (parsed.allowed ?? [])
54
59
  .map((a: { IPProtocol: string; ports?: string[] }) => {
@@ -85,7 +90,7 @@ function ruleNeedsUpdate(spec: FirewallRuleSpec, state: FirewallRuleState): bool
85
90
  );
86
91
  }
87
92
 
88
- async function createFirewallRule(spec: FirewallRuleSpec, project: string): Promise<void> {
93
+ async function createFirewallRule(spec: FirewallRuleSpec, project: string, account?: string): Promise<void> {
89
94
  const args = [
90
95
  "compute",
91
96
  "firewall-rules",
@@ -104,35 +109,41 @@ async function createFirewallRule(spec: FirewallRuleSpec, project: string): Prom
104
109
  if (spec.destinationRanges) {
105
110
  args.push(`--destination-ranges=${spec.destinationRanges}`);
106
111
  }
112
+ if (account) args.push(`--account=${account}`);
107
113
  await exec("gcloud", args);
108
114
  }
109
115
 
110
- async function deleteFirewallRule(ruleName: string, project: string): Promise<void> {
111
- await exec("gcloud", [
116
+ async function deleteFirewallRule(ruleName: string, project: string, account?: string): Promise<void> {
117
+ const args = [
112
118
  "compute",
113
119
  "firewall-rules",
114
120
  "delete",
115
121
  ruleName,
116
122
  `--project=${project}`,
117
123
  "--quiet",
118
- ]);
124
+ ];
125
+ if (account) args.push(`--account=${account}`);
126
+ await exec("gcloud", args);
119
127
  }
120
128
 
121
129
  export async function syncFirewallRules(
122
130
  desiredRules: FirewallRuleSpec[],
123
131
  project: string,
124
132
  tag: string,
133
+ account?: string,
125
134
  ): Promise<void> {
126
135
  let existingNames: string[];
127
136
  try {
128
- const output = await execOutput("gcloud", [
137
+ const listArgs = [
129
138
  "compute",
130
139
  "firewall-rules",
131
140
  "list",
132
141
  `--project=${project}`,
133
142
  `--filter=targetTags:${tag}`,
134
143
  "--format=value(name)",
135
- ]);
144
+ ];
145
+ if (account) listArgs.push(`--account=${account}`);
146
+ const output = await execOutput("gcloud", listArgs);
136
147
  existingNames = output
137
148
  .split("\n")
138
149
  .map((s) => s.trim())
@@ -146,23 +157,23 @@ export async function syncFirewallRules(
146
157
  for (const existingName of existingNames) {
147
158
  if (!desiredNames.has(existingName)) {
148
159
  console.log(` 🗑️ Deleting stale firewall rule: ${existingName}`);
149
- await deleteFirewallRule(existingName, project);
160
+ await deleteFirewallRule(existingName, project, account);
150
161
  }
151
162
  }
152
163
 
153
164
  for (const spec of desiredRules) {
154
- const state = await describeFirewallRule(spec.name, project);
165
+ const state = await describeFirewallRule(spec.name, project, account);
155
166
 
156
167
  if (!state) {
157
168
  console.log(` ➕ Creating firewall rule: ${spec.name}`);
158
- await createFirewallRule(spec, project);
169
+ await createFirewallRule(spec, project, account);
159
170
  continue;
160
171
  }
161
172
 
162
173
  if (ruleNeedsUpdate(spec, state)) {
163
174
  console.log(` 🔄 Updating firewall rule: ${spec.name}`);
164
- await deleteFirewallRule(spec.name, project);
165
- await createFirewallRule(spec, project);
175
+ await deleteFirewallRule(spec.name, project, account);
176
+ await createFirewallRule(spec, project, account);
166
177
  continue;
167
178
  }
168
179
 
@@ -222,9 +233,10 @@ export async function instanceExists(
222
233
  instanceName: string,
223
234
  project: string,
224
235
  zone: string,
236
+ account?: string,
225
237
  ): Promise<boolean> {
226
238
  try {
227
- await execOutput("gcloud", [
239
+ const args = [
228
240
  "compute",
229
241
  "instances",
230
242
  "describe",
@@ -232,7 +244,9 @@ export async function instanceExists(
232
244
  `--project=${project}`,
233
245
  `--zone=${zone}`,
234
246
  "--format=get(name)",
235
- ]);
247
+ ];
248
+ if (account) args.push(`--account=${account}`);
249
+ await execOutput("gcloud", args);
236
250
  return true;
237
251
  } catch {
238
252
  return false;
@@ -259,3 +273,45 @@ export async function sshCommand(
259
273
  `--command=${command}`,
260
274
  ]);
261
275
  }
276
+
277
+ export async function retireInstance(
278
+ name: string,
279
+ project: string,
280
+ zone: string,
281
+ ): Promise<void> {
282
+ const exists = await instanceExists(name, project, zone);
283
+ if (!exists) {
284
+ console.warn(
285
+ `\u26a0\ufe0f Instance ${name} not found in GCP (project=${project}, zone=${zone}).`,
286
+ );
287
+ return;
288
+ }
289
+
290
+ console.log(`\u{1F5D1}\ufe0f Deleting GCP instance ${name}\n`);
291
+
292
+ const child = spawn(
293
+ "gcloud",
294
+ [
295
+ "compute",
296
+ "instances",
297
+ "delete",
298
+ name,
299
+ `--project=${project}`,
300
+ `--zone=${zone}`,
301
+ ],
302
+ { stdio: "inherit" },
303
+ );
304
+
305
+ await new Promise<void>((resolve, reject) => {
306
+ child.on("close", (code) => {
307
+ if (code === 0) {
308
+ resolve();
309
+ } else {
310
+ reject(new Error(`gcloud instance delete exited with code ${code}`));
311
+ }
312
+ });
313
+ child.on("error", reject);
314
+ });
315
+
316
+ console.log(`\u2705 Instance ${name} deleted.`);
317
+ }