feat: fully functioning ALB

This commit is contained in:
Zakaria
2026-09-26 16:14:24 -04:00
parent 093afb9b2b
commit 8470b8d17a
44 changed files with 2076 additions and 132 deletions
+38
View File
@@ -15,6 +15,7 @@ export const apiEndpointKeys = {
status: "clouds.status.get",
serviceStatus: "clouds.services.status.get",
schema: "clouds.services.schema.get",
dependencies: "clouds.services.dependencies.get",
resources: {
list: "clouds.services.resources.list",
get: "clouds.services.resources.get",
@@ -113,6 +114,11 @@ export const apiEndpointKeys = {
list: "clouds.services.childCollections.items.list",
},
},
flatItems: {
list: "clouds.services.flatItems.list",
create: "clouds.services.flatItems.create",
delete: "clouds.services.flatItems.delete",
},
configuration: {
environments: {
list: "clouds.services.configuration.environments.list",
@@ -295,6 +301,14 @@ export const endpointRegistry: EndpointRegistry = new Map([
telemetry: { service: "cloud-proxy" },
},
],
[
apiEndpointKeys.clouds.dependencies,
{
path: "/clouds/:cloud/services/:service/dependencies",
method: "GET",
telemetry: { service: "cloud-proxy" },
},
],
[
apiEndpointKeys.clouds.resources.list,
{
@@ -890,6 +904,30 @@ export const endpointRegistry: EndpointRegistry = new Map([
telemetry: { service: "cloud-proxy" },
},
],
[
apiEndpointKeys.clouds.flatItems.list,
{
path: "/clouds/:cloud/services/:service/resources/:id/items",
method: "GET",
telemetry: { service: "cloud-proxy" },
},
],
[
apiEndpointKeys.clouds.flatItems.create,
{
path: "/clouds/:cloud/services/:service/resources/:id/items",
method: "POST",
telemetry: { service: "cloud-proxy" },
},
],
[
apiEndpointKeys.clouds.flatItems.delete,
{
path: "/clouds/:cloud/services/:service/resources/:id/items/:itemId",
method: "DELETE",
telemetry: { service: "cloud-proxy" },
},
],
// AWS EKS
[
apiEndpointKeys.aws.eks.clusters.list,
+43 -1
View File
@@ -1,5 +1,11 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createEc2SecurityGroup } from "./ec2.api";
import {
authorizeEc2SecurityGroupEgress,
authorizeEc2SecurityGroupIngress,
createEc2SecurityGroup,
revokeEc2SecurityGroupEgress,
revokeEc2SecurityGroupIngress,
} from "./ec2.api";
describe("createEc2SecurityGroup", () => {
const fetchMock = vi.fn<typeof fetch>();
@@ -42,3 +48,39 @@ describe("createEc2SecurityGroup", () => {
},
);
});
describe("EC2 security-group rule requests", () => {
const fetchMock = vi.fn<typeof fetch>();
beforeEach(() => {
fetchMock.mockResolvedValue(
new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { "Content-Type": "application/json" },
}),
);
vi.stubGlobal("fetch", fetchMock);
});
afterEach(() => {
vi.unstubAllGlobals();
fetchMock.mockReset();
});
it.each([
["authorize ingress", authorizeEc2SecurityGroupIngress],
["revoke ingress", revokeEc2SecurityGroupIngress],
["authorize egress", authorizeEc2SecurityGroupEgress],
["revoke egress", revokeEc2SecurityGroupEgress],
] as const)("sends one JSON permission object for %s", async (_action, request) => {
const permission = { protocol: "tcp", fromPort: 8080, toPort: 8080, cidr: "0.0.0.0/0" };
await request("sg-web", permission);
expect(fetchMock).toHaveBeenCalledTimes(1);
const [url, init] = fetchMock.mock.calls[0];
expect(String(url)).toContain("/api/ec2/security-groups/sg-web");
expect(JSON.parse(String(init?.body))).toEqual(permission);
expect(new Headers(init?.headers).get("content-type")).toBe("application/json");
});
});
+17 -4
View File
@@ -12,6 +12,18 @@ export type Ec2SecurityGroupRef = {
name?: string;
};
export type Ec2NetworkInterface = {
networkInterfaceId: string;
deviceIndex?: number;
status?: string;
privateIpAddress?: string;
publicIpAddress?: string;
privateDnsName?: string;
subnetId?: string;
vpcId?: string;
securityGroups: Ec2SecurityGroupRef[];
};
export type Ec2IpPermission = {
protocol: string;
fromPort: number | null;
@@ -61,6 +73,7 @@ export type Ec2Instance = {
architecture?: string;
platform?: string;
securityGroups: Ec2SecurityGroupRef[];
networkInterfaces: Ec2NetworkInterface[];
tags: Ec2Tag[];
};
@@ -391,7 +404,7 @@ export async function authorizeEc2SecurityGroupIngress(
): Promise<void> {
await apiClient.call(
apiEndpointKeys.aws.ec2.securityGroups.authorize,
{ body: JSON.stringify(permission), headers: { "content-type": "application/json" } },
{ body: permission },
{ groupId },
);
}
@@ -402,7 +415,7 @@ export async function revokeEc2SecurityGroupIngress(
): Promise<void> {
await apiClient.call(
apiEndpointKeys.aws.ec2.securityGroups.revoke,
{ body: JSON.stringify(permission), headers: { "content-type": "application/json" } },
{ body: permission },
{ groupId },
);
}
@@ -413,7 +426,7 @@ export async function authorizeEc2SecurityGroupEgress(
): Promise<void> {
await apiClient.call(
apiEndpointKeys.aws.ec2.securityGroups.authorizeEgress,
{ body: JSON.stringify(permission), headers: { "content-type": "application/json" } },
{ body: permission },
{ groupId },
);
}
@@ -424,7 +437,7 @@ export async function revokeEc2SecurityGroupEgress(
): Promise<void> {
await apiClient.call(
apiEndpointKeys.aws.ec2.securityGroups.revokeEgress,
{ body: JSON.stringify(permission), headers: { "content-type": "application/json" } },
{ body: permission },
{ groupId },
);
}
+63 -1
View File
@@ -108,15 +108,34 @@ export async function getServiceSchema(
return res.data;
}
export type LoadBalancingDependencies = {
vpcs: Array<{id: string; cidrBlock: string}>;
subnets: Array<{id: string; vpcId: string; availabilityZone: string; cidrBlock: string}>;
securityGroups: Array<{id: string; vpcId: string; name: string; description: string}>;
};
export async function getLoadBalancingDependencies(
service: "loadbalancing" | "targetgroups" = "loadbalancing",
signal?: AbortSignal,
): Promise<LoadBalancingDependencies & {instances?: Array<{id: string; name: string; privateIp: string | null}>}> {
const res = await apiClient.call<LoadBalancingDependencies>(
apiEndpointKeys.clouds.dependencies,
requestOptions("aws", service, {signal}),
{cloud: "aws", service},
);
return res.data;
}
export async function listCloudResources(
cloud: CloudProvider,
service: CloudServiceType,
search?: string,
signal?: AbortSignal,
state?: string,
): Promise<CloudResource[]> {
const res = await apiClient.call<CloudResource[]>(
apiEndpointKeys.clouds.resources.list,
requestOptions(cloud, service, { signal, params: { search } }),
requestOptions(cloud, service, { signal, params: { search, state } }),
{ cloud, service },
);
return res.data;
@@ -905,6 +924,49 @@ export async function listCollectionItems(
return res.data;
}
export async function listFlatItems(
cloud: CloudProvider,
service: CloudServiceType,
resourceId: string,
cursor?: string,
signal?: AbortSignal,
): Promise<CollectionPage<ChildItem>> {
const res = await apiClient.call<CollectionPage<ChildItem>>(
apiEndpointKeys.clouds.flatItems.list,
requestOptions(cloud, service, { signal, params: cursor ? { cursor } : undefined }),
{ cloud, service, id: resourceId },
);
return res.data;
}
export async function deleteFlatItem(
cloud: CloudProvider,
service: CloudServiceType,
resourceId: string,
itemId: string,
signal?: AbortSignal,
): Promise<void> {
await apiClient.call<void>(
apiEndpointKeys.clouds.flatItems.delete,
requestOptions(cloud, service, { signal }),
{ cloud, service, id: resourceId, itemId },
);
}
export async function putFlatItem(
cloud: CloudProvider,
service: CloudServiceType,
resourceId: string,
body: Record<string, unknown>,
): Promise<ChildItem> {
const res = await apiClient.call<ChildItem, Record<string, unknown>>(
apiEndpointKeys.clouds.flatItems.create,
requestOptions(cloud, service, {body}),
{cloud, service, id: resourceId},
);
return res.data;
}
export async function listSqlDatabases(
cloud: CloudProvider,
serverId: string,