Skip to content

Commit 510905f

Browse files
GCP CLI new, list and remove packet mirroring (#112)
1 parent 6cd7be8 commit 510905f

File tree

6 files changed

+518
-45
lines changed

6 files changed

+518
-45
lines changed

cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@metlo/cli",
3-
"version": "0.0.7",
3+
"version": "0.0.8",
44
"license": "MIT",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

cli/src/gcp/delete.ts

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import AsyncRetry from "async-retry"
2+
import { v4 as uuidv4, validate } from "uuid"
3+
import fs from "fs"
4+
import { prompt } from "enquirer"
5+
import { GCP_REGIONS_SUPPORTED, wait_for_global_operation, wait_for_regional_operation, wait_for_zonal_operation } from "./gcpUtils"
6+
import { GCP_CONN } from "./gcp_apis"
7+
import chalk from "chalk"
8+
import ora from "ora";
9+
import { google } from "@google-cloud/compute/build/protos/protos"
10+
11+
const spinner = ora()
12+
13+
const verifyAccountDetails = async () => {
14+
const gcp_regions = GCP_REGIONS_SUPPORTED.map(e => ({
15+
name: e,
16+
}))
17+
const resp = await prompt([
18+
{
19+
type: "input",
20+
name: "_projectName",
21+
message: "GCP Project Name",
22+
}, {
23+
type: "input",
24+
initial: "default",
25+
name: "_networkName",
26+
message: "GCP Network to mirror",
27+
}, {
28+
type: "autocomplete",
29+
name: "_zoneName",
30+
message: "Select your GCP zone",
31+
initial: 1,
32+
choices: gcp_regions,
33+
}, {
34+
type: "input",
35+
name: "_keyPath",
36+
message: "Path to GCP key file",
37+
validate: (path: string) => {
38+
if (fs.existsSync(path)) {
39+
return true
40+
} else {
41+
// @ts-ignore
42+
let text = chalk.redBright(`GCP Key file not found at ${path}`)
43+
return text
44+
}
45+
}
46+
}
47+
])
48+
49+
spinner.text = "Validating account details"
50+
spinner.start()
51+
// @ts-ignore Destructuring is improperly done
52+
const { _projectName: project, _networkName: network, _zoneName: zone, _keyPath: keyFilePath } = resp;
53+
54+
const key = (fs.readFileSync(keyFilePath)).toString("utf-8");
55+
56+
let conn = new GCP_CONN(key, zone, project)
57+
await conn.test_connection()
58+
await conn.get_zone({ zone })
59+
spinner.succeed("Validated account details")
60+
spinner.stop()
61+
spinner.clear()
62+
return { project, network, zone, key }
63+
}
64+
65+
66+
const deletePacketMirroringResources = async (
67+
conn: GCP_CONN,
68+
mirroring: google.cloud.compute.v1.IPacketMirroring[]
69+
) => {
70+
if (mirroring.length == 0) {
71+
throw new Error("No existing packet mirroring instances found")
72+
}
73+
74+
const instanceChoices = mirroring.flatMap(
75+
(mirror) => mirror.mirroredResources.instances.map(
76+
inst => {
77+
const splits = inst.url.split("/");
78+
return splits[splits.length - 1]
79+
}
80+
)
81+
)
82+
const subnetChoices = mirroring.flatMap(
83+
(mirror) => mirror.mirroredResources.subnetworks.map(
84+
inst => {
85+
const splits = inst.url.split("/");
86+
return splits[splits.length - 1]
87+
}
88+
)
89+
)
90+
const tagChoices = mirroring.flatMap(
91+
(mirror) => mirror.mirroredResources.tags
92+
)
93+
94+
const availableChoices = []
95+
if (instanceChoices.length > 0) {
96+
availableChoices.push("INSTANCE")
97+
}
98+
if (subnetChoices.length > 0) {
99+
availableChoices.push("SUBNET")
100+
}
101+
if (tagChoices.length > 0) {
102+
availableChoices.push("TAG")
103+
}
104+
105+
const sourceTypeResp = await prompt([
106+
{
107+
type: "autocomplete",
108+
name: "_packetMirrorName",
109+
message: "Select Packet Mirroring instance",
110+
initial: 0,
111+
choices: mirroring.filter((inst) => inst.name.startsWith("metlo")).map((inst) => inst.name)
112+
}, {
113+
type: "select",
114+
name: "_sourceType",
115+
message: "Select your mirror source type",
116+
initial: 0,
117+
choices: availableChoices,
118+
},
119+
])
120+
let sourceType = sourceTypeResp["_sourceType"]
121+
let packetMirrorName = sourceTypeResp["_packetMirrorName"]
122+
123+
if (sourceType === "INSTANCE") {
124+
const instanceNameResp = await prompt([
125+
{
126+
type: "autocomplete",
127+
name: "_name",
128+
message: "Enter the mirror source instance name to remove",
129+
choices: instanceChoices
130+
131+
}
132+
])
133+
spinner.start("Verifying mirror source details")
134+
const instanceName = instanceNameResp['_name'].trim()
135+
136+
const resources = mirroring.find((mirror) => mirror.name === packetMirrorName).mirroredResources
137+
resources.instances = resources.instances.filter((inst) => !inst.url.includes(instanceName))
138+
resources.instances = resources.instances.length > 0 ? resources.instances : null
139+
140+
let resp = await conn.remove_packet_mirroring_resources({ packetMirrorName, newMirroredResources: resources })
141+
} else if (sourceType === "SUBNET") {
142+
const subnetNameResp = await prompt([
143+
{
144+
type: "autocomplete",
145+
name: "_name",
146+
message: "Enter the mirror source subnet name to remove",
147+
choices: subnetChoices
148+
}
149+
])
150+
spinner.start("Verifying mirror source details")
151+
const subnetName = subnetNameResp['_name'].trim()
152+
153+
const resources = mirroring.find((mirror) => mirror.name === packetMirrorName).mirroredResources
154+
resources.subnetworks = resources.subnetworks.filter((inst) => !inst.url.includes(subnetName))
155+
resources.subnetworks = resources.subnetworks.length > 0 ? resources.subnetworks : null
156+
157+
let resp = await conn.remove_packet_mirroring_resources({ packetMirrorName, newMirroredResources: resources })
158+
} else if (sourceType === "TAG") {
159+
const tagNameResp = await prompt([
160+
{
161+
type: "autocomplete",
162+
name: "_name",
163+
message: "Enter the mirror source tag name to remove",
164+
choices: tagChoices
165+
}
166+
])
167+
spinner.start("Verifying mirror source details")
168+
let tagName = tagNameResp["_name"].trim()
169+
170+
const resources = mirroring.find((mirror) => mirror.name === packetMirrorName).mirroredResources
171+
resources.tags = resources.tags.filter((inst) => !inst.includes(tagName))
172+
173+
let resp = await conn.remove_packet_mirroring_resources({ packetMirrorName, newMirroredResources: resources })
174+
}
175+
spinner.succeed("Deleted resource from packet mirroring Succesfully")
176+
spinner.stop()
177+
spinner.clear()
178+
return {}
179+
}
180+
181+
export const gcpTrafficMirrorDelete = async () => {
182+
const id = uuidv4()
183+
const data = {}
184+
try {
185+
const { project, zone, network, key } = await verifyAccountDetails()
186+
const networkUrl = `https://www.googleapis.com/compute/v1/projects/${project}/global/networks/${network}`
187+
const conn = new GCP_CONN(key, zone, project);
188+
data["zone"] = zone
189+
data["project"] = project
190+
191+
const [packetMirrors] = await conn.list_packet_mirroring()
192+
193+
await deletePacketMirroringResources(conn, packetMirrors)
194+
} catch (e) {
195+
spinner.fail()
196+
console.log(chalk.bgRedBright("Metlo packet mirroring item removal failed. This might help in debugging it."))
197+
console.log(e)
198+
console.log(data)
199+
}
200+
}

cli/src/gcp/gcp_apis.ts

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
ZoneOperationsClient,
2020
MachineTypesClient,
2121
} from "@google-cloud/compute"
22+
import { google } from "@google-cloud/compute/build/protos/protos"
23+
import { wait_for_regional_operation } from "./gcpUtils"
2224

2325
const PREFIX_LENGTH = 24
2426
const METLO_DATA_COLLECTOR_TAG = "metlo-capture"
@@ -209,7 +211,8 @@ export class GCP_CONN {
209211
},
210212
allowed: [
211213
{
212-
IPProtocol: "all",
214+
IPProtocol: "UDP",
215+
ports: ["4789"]
213216
},
214217
],
215218
},
@@ -523,4 +526,87 @@ export class GCP_CONN {
523526
packetMirroring: packetMirroringURL,
524527
})
525528
}
529+
530+
public async list_packet_mirroring() {
531+
let conn = new PacketMirroringsClient({ credentials: this.keyfile })
532+
return conn.list({
533+
project: this.project,
534+
region: this.region
535+
})
536+
}
537+
538+
public async get_packet_mirroring({ packetMirrorName }) {
539+
let conn = new PacketMirroringsClient({ credentials: this.keyfile })
540+
return conn.get({
541+
project: this.project,
542+
region: this.region,
543+
packetMirroring: packetMirrorName
544+
})
545+
}
546+
547+
public async update_packet_mirroring({ packetMirrorName, updateInstance, updateTag, updateSubnet }: updatePacketMirroringInterface) {
548+
let conn = new PacketMirroringsClient({ credentials: this.keyfile })
549+
let [mirrorInfo, ,] = await this.get_packet_mirroring({ packetMirrorName })
550+
let updatedMirrorInfo: google.cloud.compute.v1.IPacketMirroring = {
551+
...mirrorInfo, mirroredResources: {
552+
tags: updateTag ? [updateTag, ...mirrorInfo.mirroredResources.tags] : mirrorInfo.mirroredResources.tags,
553+
instances: updateInstance ? [updateInstance, ...mirrorInfo.mirroredResources.instances] : mirrorInfo.mirroredResources.instances,
554+
subnetworks: updateSubnet ? [updateSubnet, ...mirrorInfo.mirroredResources.subnetworks] : mirrorInfo.mirroredResources.subnetworks,
555+
}
556+
}
557+
return conn.patch({
558+
project: this.project,
559+
region: this.region,
560+
packetMirroring: packetMirrorName,
561+
packetMirroringResource: updatedMirrorInfo
562+
})
563+
}
564+
565+
public async remove_packet_mirroring_resources({ packetMirrorName, newMirroredResources }: deletePacketMirroringResourcesInterface) {
566+
let conn = new PacketMirroringsClient({ credentials: this.keyfile })
567+
let [mirrorInfo, ,] = await this.get_packet_mirroring({ packetMirrorName })
568+
569+
const [delOpRegional, ,] = await conn.delete({ project: this.project, region: this.region, packetMirroring: packetMirrorName })
570+
await wait_for_regional_operation(delOpRegional.latestResponse.name, this)
571+
if (
572+
newMirroredResources.instances.length == 0 &&
573+
newMirroredResources.subnetworks.length == 0 &&
574+
newMirroredResources.tags.length == 0
575+
) {
576+
const [createOpRegional, ,] = await this.start_packet_mirroring({
577+
name: mirrorInfo.name,
578+
networkURL: mirrorInfo.network.url,
579+
mirroredInstanceURLs: [],
580+
mirroredSubnetURLS: [],
581+
mirroredTagURLs: ["metlo-provided-placeholder-network-tag"],
582+
loadBalancerURL: mirrorInfo.collectorIlb.url
583+
})
584+
await wait_for_regional_operation(createOpRegional.latestResponse.name, this)
585+
} else {
586+
const [createOpRegional, ,] = await this.start_packet_mirroring({
587+
name: mirrorInfo.name,
588+
networkURL: mirrorInfo.network.url,
589+
mirroredInstanceURLs: newMirroredResources.instances.map((inst) => inst.url),
590+
mirroredSubnetURLS: newMirroredResources.subnetworks.map((inst) => inst.url),
591+
mirroredTagURLs: newMirroredResources.tags,
592+
loadBalancerURL: mirrorInfo.collectorIlb.url
593+
})
594+
await wait_for_regional_operation(createOpRegional.latestResponse.name, this)
595+
}
596+
597+
return
598+
}
599+
526600
}
601+
602+
interface updatePacketMirroringInterface {
603+
packetMirrorName: string;
604+
updateTag?: string;
605+
updateInstance?: google.cloud.compute.v1.IPacketMirroringMirroredResourceInfoInstanceInfo;
606+
updateSubnet?: google.cloud.compute.v1.IPacketMirroringMirroredResourceInfoSubnetInfo;
607+
}
608+
609+
interface deletePacketMirroringResourcesInterface {
610+
packetMirrorName: string
611+
newMirroredResources: google.cloud.compute.v1.IPacketMirroringMirroredResourceInfo
612+
}

0 commit comments

Comments
 (0)