Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions src/pages/instances/InstanceDetailHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import * as Yup from "yup";
import { useEventQueue } from "context/eventQueue";
import { instanceNameValidation } from "util/instances";
import { instanceLinkFromOperation } from "util/operations";
import { getInstanceName } from "util/operations";
import InstanceDetailActions from "./InstanceDetailActions";
import { useInstanceEntitlements } from "util/entitlements/instances";
import { useCurrentProject } from "context/useCurrentProject";
Expand Down Expand Up @@ -77,9 +76,7 @@ const InstanceDetailHeader: FC<Props> = ({
);
toastNotify.success(
<>
Instance{" "}
<strong>{getInstanceName(operation.metadata)}</strong> renamed
to {instanceLink}.
Instance <strong>{name}</strong> renamed to {instanceLink}.
</>,
);
formik.setFieldValue("isRenaming", false);
Expand Down
2 changes: 2 additions & 0 deletions src/types/operation.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export interface LxdOperation {
instances_snapshots?: string[];
storage_volume_snapshots?: string[];
};
entity_url: string;
original_entity_url: string;
status: LxdOperationStatus;
status_code: number;
updated_at: string;
Expand Down
74 changes: 64 additions & 10 deletions src/util/operations.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ import {
} from "./operations";
import type { LxdOperation } from "types/operation";

const craftOperation = (...url: string[]) => {
const craftOperation = (
resourceUrls: string[] = [],
entityUrl?: string,
originalEntityUrl?: string,
) => {
const images: string[] = [];
const instances: string[] = [];
const instances_snapshots: string[] = [];
const storage_volume_snapshots: string[] = [];
for (const u of url) {
for (const u of resourceUrls) {
const segments = u.split("/");
if (u.includes("snapshots") && u.includes("storage-pools")) {
storage_volume_snapshots.push(u);
Expand All @@ -34,27 +38,38 @@ const craftOperation = (...url: string[]) => {
instances.push(u);
}
}

return {
metadata: {
entity_url: entityUrl ? entityUrl : undefined,
original_entity_url: originalEntityUrl ? originalEntityUrl : undefined,
},
resources: {
images,
instances,
instances_snapshots,
storage_volume_snapshots,
},
} as LxdOperation;
} as unknown as LxdOperation;
};

describe("getInstanceName", () => {
it("identifies instance name from an instance operation", () => {
const operation = craftOperation("/1.0/instances/testInstance1");
it("identifies instance name from an instance operation using resources field", () => {
const operation = craftOperation(["/1.0/instances/testInstance1"]);
const name = getInstanceName(operation);

expect(name).toBe("testInstance1");
});

it("identifies instance name from an instance operation using entity_url field", () => {
const operation = craftOperation([], "/1.0/instances/testInstance1");
const name = getInstanceName(operation);

expect(name).toBe("testInstance1");
});

it("identifies instance name from an instance operation in a custom project", () => {
const operation = craftOperation(
[],
"/1.0/instances/testInstance2?project=project",
);
const name = getInstanceName(operation);
Expand All @@ -64,24 +79,39 @@ describe("getInstanceName", () => {

it("identifies instance name from an instance creation operation with snapshot as source", () => {
const operation = craftOperation(
[
"/1.0/instances/targetInstanceName",
"/1.0/instances/sourceInstanceName/testSnap",
],
"/1.0/instances/targetInstanceName",
"/1.0/instances/sourceInstanceName/testSnap",
);
const name = getInstanceName(operation);
expect(name).toBe("targetInstanceName");
});

it("identifies original instance name from an instance rename operation using original_entity_url field", () => {
const operation = craftOperation(
[],
undefined,
"/1.0/instances/testInstance1",
);
const name = getInstanceName(operation);

expect(name).toBe("testInstance1");
});
});

describe("getProjectName", () => {
it("identifies project name from an instance operation when no project parameter is present", () => {
const operation = craftOperation("/1.0/instances/testInstance1");
const operation = craftOperation([], "/1.0/instances/testInstance1");
const name = getProjectName(operation);

expect(name).toBe("default");
});

it("identifies project name from an instance operation in a custom project", () => {
const operation = craftOperation(
[],
"/1.0/instances/testInstance2?project=fooProject",
);
const name = getProjectName(operation);
Expand All @@ -91,6 +121,7 @@ describe("getProjectName", () => {

it("identifies project name from an instance operation in a custom project with other parameters", () => {
const operation = craftOperation(
[],
"/1.0/instances/testInstance2?foo=bar&project=barProject",
);
const name = getProjectName(operation);
Expand All @@ -100,6 +131,7 @@ describe("getProjectName", () => {

it("identifies project name from an image operation in a custom project", () => {
const operation = craftOperation(
[],
"/1.0/images/333449f566531c586d405772afaf9ced7eb9c2ca2f191d487c63b170f62b3172?project=imageProject",
);
const name = getProjectName(operation);
Expand All @@ -109,8 +141,18 @@ describe("getProjectName", () => {
});

describe("getInstanceSnapshotName", () => {
it("identifies snapshot name from an instance snapshot operation", () => {
it("identifies snapshot name from an instance snapshot operation using resources field", () => {
const operation = craftOperation([
"/1.0/instances/test-instance/snapshots/test-snapshot",
]);
const name = getInstanceSnapshotName(operation);

expect(name).toBe("test-snapshot");
});

it("identifies snapshot name from an instance snapshot operation using entity_url field", () => {
const operation = craftOperation(
[],
"/1.0/instances/test-instance/snapshots/test-snapshot",
);
const name = getInstanceSnapshotName(operation);
Expand All @@ -120,6 +162,7 @@ describe("getInstanceSnapshotName", () => {

it("identifies snapshot name from an instance snapshot operation in a custom project", () => {
const operation = craftOperation(
[],
"/1.0/instances/test-instance/snapshots/test-snapshot?project=project",
);
const name = getInstanceSnapshotName(operation);
Expand All @@ -129,8 +172,18 @@ describe("getInstanceSnapshotName", () => {
});

describe("getVolumeSnapshotName", () => {
it("identifies snapshot name from a volume snapshot operation", () => {
it("identifies snapshot name from a volume snapshot operation using resources field", () => {
const operation = craftOperation([
"/1.0/storage-pools/test-pool/volumes/custom/test-volume/snapshots/test-snapshot",
]);
const name = getVolumeSnapshotName(operation);

expect(name).toBe("test-snapshot");
});

it("identifies snapshot name from a volume snapshot operation using entity_url field", () => {
const operation = craftOperation(
[],
"/1.0/storage-pools/test-pool/volumes/custom/test-volume/snapshots/test-snapshot",
);
const name = getVolumeSnapshotName(operation);
Expand All @@ -140,6 +193,7 @@ describe("getVolumeSnapshotName", () => {

it("identifies snapshot name from a volume snapshot operation in a custom project", () => {
const operation = craftOperation(
[],
"/1.0/storage-pools/test-pool/volumes/custom/test-volume/snapshots/test-snapshot?project=project",
);
const name = getVolumeSnapshotName(operation);
Expand Down
57 changes: 30 additions & 27 deletions src/util/operations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,32 @@ import type { LxdEvent } from "types/event";
import type { LxdOperationResponse } from "types/operation";
import { InstanceRichChip } from "pages/instances/InstanceRichChip";

// Extracts entity URLs from an operation, considering renaming operations where the original_entity_url is returned.
const getOperationEntityUrls = (
entities?: string[],
operation?: LxdOperation,
entities: string[] = [],
): string[] => {
const candidates = entities ?? [];
if (operation?.metadata?.["entity_url"]) {
candidates.push(operation.metadata["entity_url"]);
const candidates = new Set<string>(entities);

if (operation?.metadata?.original_entity_url) {
candidates.add(operation.metadata.original_entity_url);
} else if (operation?.metadata?.entity_url) {
candidates.add(operation.metadata.entity_url);
}
return candidates;
return Array.from(candidates);
};

export const getInstanceName = (operation?: LxdOperation): string => {
// the url can be one of below formats
// /1.0/instances/<instance_name>
// /1.0/instances/<instance_name>?project=<project_name>
const candidates = getOperationEntityUrls(
operation?.resources?.instances,
operation,
);
if (operation?.resources?.instance) {
candidates.push(...operation.resources.instance);
}

const resources = [
...(operation?.resources?.instances || []),
...(operation?.resources?.instance || []),
];
const candidates = getOperationEntityUrls(operation, resources);

return (
candidates
?.filter((item) => item.startsWith("/1.0/instances/"))
Expand All @@ -38,8 +42,8 @@ export const getInstanceName = (operation?: LxdOperation): string => {
export const getInstanceSnapshotName = (operation?: LxdOperation): string => {
// /1.0/instances/<instance_name>/snapshots/<snapshot_name>
const instanceSnapshots = getOperationEntityUrls(
operation?.resources?.instances_snapshots,
operation,
operation?.resources?.instances_snapshots,
);
if (instanceSnapshots.length) {
return instanceSnapshots[0].split("/")[5].split("?")[0];
Expand All @@ -51,8 +55,8 @@ export const getInstanceSnapshotName = (operation?: LxdOperation): string => {
export const getVolumeSnapshotName = (operation?: LxdOperation): string => {
// /1.0/storage-pools/<pool_name>/volumes/custom/<volume_name>/snapshots/<snapshot_name>
const storageVolumeSnapshots = getOperationEntityUrls(
operation?.resources?.storage_volume_snapshots,
operation,
operation?.resources?.storage_volume_snapshots,
);

if (storageVolumeSnapshots.length) {
Expand All @@ -74,26 +78,25 @@ export const getProjectName = (operation?: LxdOperation): string => {
return "default";
}

const images = getOperationEntityUrls(
operation?.resources?.images,
operation,
);
if (images.length > 0) {
const urls = getOperationEntityUrls(operation, [
...(operation.resources?.instances || []),
...(operation.resources?.images || []),
]);
if (urls.length === 0) {
return "default";
}

const imageURLs = urls.filter((item) => item.startsWith("/1.0/images/"));
if (imageURLs.length > 0) {
return (
images
.filter((item) => item.startsWith("/1.0/images/"))
imageURLs
.map((item) => item.split("project=")[1])
.pop()
?.split("&")[0] ?? "default"
);
}

const instances = getOperationEntityUrls(
operation.resources?.instances,
operation,
);
return (
instances
urls
.filter((item) => item.startsWith("/1.0/instances/"))
.map((item) => item.split("project=")[1])
.pop()
Expand Down
Loading