Skip to content

Commit

Permalink
feat: experimental enclave building in the EMUI (#2137)
Browse files Browse the repository at this point in the history
## Description:
This PR adds an experimental switch to the 'about' dialog, which allows
a 'Build Enclave' option to be enabled in the EMUI.

This option presents a graphical interface for drawing out an enclave of
services and artifacts, which can then be repeatedly executed to
populate/modify an enclave.

## Is this change user facing?
YES (but behind a feature switch)

## References (if applicable):
* Brief on slack.
  • Loading branch information
Dartoxian committed Feb 8, 2024
1 parent e176a3a commit 8a26a6c
Show file tree
Hide file tree
Showing 44 changed files with 2,437 additions and 277 deletions.
6 changes: 6 additions & 0 deletions enclave-manager/web/packages/app/package.json
Expand Up @@ -4,6 +4,7 @@
"private": true,
"homepage": ".",
"dependencies": {
"@dagrejs/dagre": "^1.0.4",
"ansi-to-html": "^0.7.2",
"enclave-manager-sdk": "file:../../../api/typescript",
"html-react-parser": "^4.2.2",
Expand All @@ -12,11 +13,16 @@
"kurtosis-ui-components": "0.86.16",
"react-error-boundary": "^4.0.11",
"react-hook-form": "^7.47.0",
"react-mentions": "^4.4.10",
"reactflow": "^11.10.2",
"uuid": "^9.0.1",
"yaml": "^2.3.4"
},
"devDependencies": {
"@types/js-cookie": "^3.0.6",
"@types/react-mentions": "^4.1.13",
"@types/streamsaver": "^2.0.4",
"@types/uuid": "^9.0.8",
"serve": "^14.2.1",
"source-map-explorer": "^2.5.3"
},
Expand Down
Expand Up @@ -3,6 +3,7 @@ import {
DownloadFilesArtifactArgs,
FilesArtifactNameAndUuid,
RunStarlarkPackageArgs,
RunStarlarkScriptArgs,
ServiceInfo,
} from "enclave-manager-sdk/build/api_container_service_pb";
import {
Expand All @@ -22,6 +23,7 @@ import {
GetStarlarkRunRequest,
InspectFilesArtifactContentsRequest,
RunStarlarkPackageRequest,
RunStarlarkScriptRequest,
} from "enclave-manager-sdk/build/kurtosis_enclave_manager_api_pb";
import { assertDefined, asyncResult, isDefined, RemoveFunctions } from "kurtosis-ui-components";
import { EnclaveFullInfo } from "../../emui/enclaves/types";
Expand Down Expand Up @@ -202,17 +204,37 @@ export abstract class KurtosisClient {
apicInfo: RemoveFunctions<EnclaveAPIContainerInfo>,
packageId: string,
args: Record<string, any>,
dryRun: boolean = false,
) {
// Not currently using asyncResult as the return type here is an asyncIterable
const request = new RunStarlarkPackageRequest({
apicIpAddress: apicInfo.bridgeIpAddress,
apicPort: apicInfo.grpcPortInsideEnclave,
RunStarlarkPackageArgs: new RunStarlarkPackageArgs({
dryRun: false,
dryRun,
packageId: packageId,
serializedParams: JSON.stringify(args),
}),
});
return this.client.runStarlarkPackage(request, this.getHeaderOptions());
}

async runStarlarkScript(
apicInfo: RemoveFunctions<EnclaveAPIContainerInfo>,
serializedScript: string,
args: Record<string, any> = {},
dryRun: boolean = false,
) {
// Not currently using asyncResult as the return type here is an asyncIterable
const request = new RunStarlarkScriptRequest({
apicIpAddress: apicInfo.bridgeIpAddress,
apicPort: apicInfo.grpcPortInsideEnclave,
RunStarlarkScriptArgs: new RunStarlarkScriptArgs({
dryRun,
serializedScript,
serializedParams: JSON.stringify(args),
}),
});
return this.client.runStarlarkScript(request, this.getHeaderOptions());
}
}
19 changes: 12 additions & 7 deletions enclave-manager/web/packages/app/src/emui/App.tsx
Expand Up @@ -5,10 +5,12 @@ import { KurtosisClientProvider, useKurtosisClient } from "../client/enclaveMana
import { KurtosisPackageIndexerProvider } from "../client/packageIndexer/KurtosisPackageIndexerClientContext";
import { CatalogContextProvider } from "./catalog/CatalogContext";
import { catalogRoutes } from "./catalog/CatalogRoutes";
import { BuildEnclave } from "./enclaves/components/BuildEnclave";
import { CreateEnclave } from "./enclaves/components/CreateEnclave";
import { enclaveRoutes } from "./enclaves/EnclaveRoutes";
import { EnclavesContextProvider } from "./enclaves/EnclavesContext";
import { Navbar } from "./Navbar";
import { SettingsContextProvider } from "./settings";

const logLogo = (t: string) => console.log(`%c ${t}`, "background: black; color: #00C223");
logLogo(`
Expand All @@ -35,13 +37,15 @@ console.log(`Kurtosis web UI version: ${process.env.REACT_APP_VERSION || "Unknow

export const EmuiApp = () => {
return (
<KurtosisThemeProvider>
<KurtosisPackageIndexerProvider>
<KurtosisClientProvider>
<KurtosisRouter />
</KurtosisClientProvider>
</KurtosisPackageIndexerProvider>
</KurtosisThemeProvider>
<SettingsContextProvider>
<KurtosisThemeProvider>
<KurtosisPackageIndexerProvider>
<KurtosisClientProvider>
<KurtosisRouter />
</KurtosisClientProvider>
</KurtosisPackageIndexerProvider>
</KurtosisThemeProvider>
</SettingsContextProvider>
);
};

Expand All @@ -65,6 +69,7 @@ const KurtosisRouter = () => {
<EnclavesContextProvider>
<Outlet />
<CreateEnclave />
<BuildEnclave />
</EnclavesContextProvider>
),
children: enclaveRoutes(),
Expand Down
32 changes: 31 additions & 1 deletion enclave-manager/web/packages/app/src/emui/Navbar.tsx
@@ -1,5 +1,7 @@
import {
Button,
FormControl,
FormLabel,
Input,
InputGroup,
InputRightElement,
Expand All @@ -10,6 +12,7 @@ import {
ModalFooter,
ModalHeader,
ModalOverlay,
Switch,
Text,
} from "@chakra-ui/react";
import { CopyButton, NavButton, Navigation, NavigationDivider } from "kurtosis-ui-components";
Expand All @@ -21,8 +24,10 @@ import { PiLinkSimpleBold } from "react-icons/pi";
import { Link, useLocation } from "react-router-dom";
import { KURTOSIS_CLOUD_CONNECT_URL } from "../client/constants";
import { useKurtosisClient } from "../client/enclaveManager/KurtosisClientContext";
import { settingKeys, useSettings } from "./settings";

export const Navbar = () => {
const { updateSetting, settings } = useSettings();
const location = useLocation();
const kurtosisClient = useKurtosisClient();
const [showAboutDialog, setShowAboutDialog] = useState(false);
Expand Down Expand Up @@ -78,7 +83,32 @@ export const Navbar = () => {
/>
</InputRightElement>
</InputGroup>
<Text></Text>
<Text
as={"h2"}
fontSize={"lg"}
m={"16px 0"}
pt={"16px"}
borderTopWidth={"1px"}
borderTopColor={"gray.60"}
fontWeight={"semibold"}
>
Settings:
</Text>
<FormControl display="flex" alignItems="center">
<FormLabel htmlFor="experimental-build" mb="0">
Enable experimental enclave builder interface?
</FormLabel>
<Switch
id="experimental-build"
onChange={() =>
updateSetting(
settingKeys.ENABLE_EXPERIMENTAL_BUILD_ENCLAVE,
!settings.ENABLE_EXPERIMENTAL_BUILD_ENCLAVE,
)
}
isChecked={settings.ENABLE_EXPERIMENTAL_BUILD_ENCLAVE}
/>
</FormControl>
</ModalBody>

<ModalFooter>
Expand Down
Expand Up @@ -2,10 +2,10 @@ import { Flex, Heading, Spinner } from "@chakra-ui/react";
import { GetPackagesResponse, KurtosisPackage } from "kurtosis-cloud-indexer-sdk";
import { ReadPackageResponse } from "kurtosis-cloud-indexer-sdk/build/kurtosis_package_indexer_pb";
import { isDefined, SavedPackagesProvider } from "kurtosis-ui-components";
import { createContext, PropsWithChildren, useCallback, useContext, useEffect, useState } from "react";
import { createContext, PropsWithChildren, useCallback, useContext, useEffect, useLayoutEffect, useState } from "react";
import { Result } from "true-myth";
import { useKurtosisPackageIndexerClient } from "../../client/packageIndexer/KurtosisPackageIndexerClientContext";
import { loadSavedPackageNames, storeSavedPackages } from "./storage";
import { settingKeys, useSettings } from "../settings";

export type CatalogState = {
catalog: Result<GetPackagesResponse, string>;
Expand All @@ -16,6 +16,7 @@ export type CatalogState = {
const CatalogContext = createContext<CatalogState>(null as any);

export const CatalogContextProvider = ({ children }: PropsWithChildren) => {
const { settings, updateSetting } = useSettings();
const packageIndexerClient = useKurtosisPackageIndexerClient();
const [catalog, setCatalog] = useState<Result<GetPackagesResponse, string>>();
const [savedPackages, setSavedPackages] = useState<KurtosisPackage[]>([]);
Expand All @@ -25,24 +26,25 @@ export const CatalogContextProvider = ({ children }: PropsWithChildren) => {
const catalog = await packageIndexerClient.getPackages();
setCatalog(catalog);

if (catalog.isOk) {
const savedPackageNames = new Set(loadSavedPackageNames());
setSavedPackages(catalog.value.packages.filter((kurtosisPackage) => savedPackageNames.has(kurtosisPackage.name)));
}

return catalog;
}, [packageIndexerClient]);

const togglePackageSaved = useCallback((kurtosisPackage: KurtosisPackage) => {
setSavedPackages((savedPackages) => {
const packageSavedAlready = savedPackages.some((p) => p.name === kurtosisPackage.name);
const newSavedPackages: KurtosisPackage[] = packageSavedAlready
? savedPackages.filter((p) => p.name !== kurtosisPackage.name)
: [...savedPackages, kurtosisPackage];
storeSavedPackages(newSavedPackages);
return newSavedPackages;
});
}, []);
const togglePackageSaved = useCallback(
(kurtosisPackage: KurtosisPackage) => {
setSavedPackages((savedPackages) => {
const packageSavedAlready = savedPackages.some((p) => p.name === kurtosisPackage.name);
const newSavedPackages: KurtosisPackage[] = packageSavedAlready
? savedPackages.filter((p) => p.name !== kurtosisPackage.name)
: [...savedPackages, kurtosisPackage];
updateSetting(
settingKeys.SAVED_PACKAGES,
newSavedPackages.map((kurtosisPackage) => kurtosisPackage.name),
);
return newSavedPackages;
});
},
[updateSetting],
);

const getSinglePackage = useCallback(
async (packageName: string) => {
Expand All @@ -55,6 +57,14 @@ export const CatalogContextProvider = ({ children }: PropsWithChildren) => {
refreshCatalog();
}, [refreshCatalog]);

// Use a Layout effect so that the saved packages are set before first render.
useLayoutEffect(() => {
if (isDefined(catalog) && catalog.isOk) {
const savedPackageNames = new Set(settings.SAVED_PACKAGES);
setSavedPackages(catalog.value.packages.filter((kurtosisPackage) => savedPackageNames.has(kurtosisPackage.name)));
}
}, [catalog, settings.SAVED_PACKAGES]);

if (!isDefined(catalog)) {
return (
<Flex width="100%" direction="column" alignItems={"center"} gap={"1rem"} padding={"3rem"}>
Expand Down
26 changes: 0 additions & 26 deletions enclave-manager/web/packages/app/src/emui/catalog/storage.ts

This file was deleted.

Expand Up @@ -47,6 +47,13 @@ export type EnclavesState = {
enclave: RemoveFunctions<EnclaveInfo>,
packageId: string,
args: Record<string, any>,
dryRun?: boolean,
) => Promise<AsyncIterable<StarlarkRunResponseLine>>;
runStarlarkScript: (
enclave: RemoveFunctions<EnclaveInfo>,
script: string,
args: Record<string, any>,
dryRun?: boolean,
) => Promise<AsyncIterable<StarlarkRunResponseLine>>;
updateStarlarkFinishedInEnclave: (enclave: RemoveFunctions<EnclaveInfo>) => void;
};
Expand Down Expand Up @@ -168,10 +175,30 @@ export const EnclavesContextProvider = ({ skipInitialLoad, children }: EnclavesC
);

const runStarlarkPackage = useCallback(
async (enclave: RemoveFunctions<EnclaveInfo>, packageId: string, args: Record<string, any>) => {
async (
enclave: RemoveFunctions<EnclaveInfo>,
packageId: string,
args: Record<string, any>,
dryRun: boolean = false,
) => {
setState((state) => ({ ...state, starlarkRunningInEnclaves: [...state.starlarkRunningInEnclaves, enclave] }));
assertDefined(enclave.apiContainerInfo, `apic info not defined in enclave ${enclave.name}`);
const resp = await kurtosisClient.runStarlarkPackage(enclave.apiContainerInfo, packageId, args, dryRun);
return resp;
},
[kurtosisClient],
);

const runStarlarkScript = useCallback(
async (
enclave: RemoveFunctions<EnclaveInfo>,
script: string,
args: Record<string, any>,
dryRun: boolean = false,
) => {
setState((state) => ({ ...state, starlarkRunningInEnclaves: [...state.starlarkRunningInEnclaves, enclave] }));
assertDefined(enclave.apiContainerInfo, `apic info not defined in enclave ${enclave.name}`);
const resp = await kurtosisClient.runStarlarkPackage(enclave.apiContainerInfo, packageId, args);
const resp = await kurtosisClient.runStarlarkScript(enclave.apiContainerInfo, script, args, dryRun);
return resp;
},
[kurtosisClient],
Expand Down Expand Up @@ -217,6 +244,7 @@ export const EnclavesContextProvider = ({ skipInitialLoad, children }: EnclavesC
createEnclave,
destroyEnclaves,
runStarlarkPackage,
runStarlarkScript,
updateStarlarkFinishedInEnclave,
}}
>
Expand Down
@@ -0,0 +1,31 @@
import { isDefined } from "kurtosis-ui-components";
import { useEffect, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { useSettings } from "../../settings";
import { KURTOSIS_BUILD_ENCLAVE_URL_ARG } from "./configuration/drawer/constants";
import { EnclaveBuilderModal } from "./modals/EnclaveBuilderModal";

export const BuildEnclave = () => {
const { settings } = useSettings();
const navigate = useNavigate();
const location = useLocation();

const [buildEnclaveOpen, setBuildEnclaveOpen] = useState(false);

useEffect(() => {
setBuildEnclaveOpen(location.hash === `#${KURTOSIS_BUILD_ENCLAVE_URL_ARG}`);
}, [location]);

const handleCloseBuildEnclave = () => {
setBuildEnclaveOpen(false);
if (isDefined(location.hash)) {
navigate(`${location.pathname}${location.search}`);
}
};

if (!settings.ENABLE_EXPERIMENTAL_BUILD_ENCLAVE) {
return null;
}

return <EnclaveBuilderModal isOpen={buildEnclaveOpen} onClose={handleCloseBuildEnclave} />;
};

0 comments on commit 8a26a6c

Please sign in to comment.