Restructure to monorepo (#6907)

* wip: restructure to monorepo

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* refactor create-release-pr to a package

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* build fixes

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* github workflow fixes

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix typo

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* add webpack-env types to core

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix github workflows

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* refactor/fix integration tests

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* lint fix

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* yarn run dev

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* eslint settings for vscode

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* move templates to right package

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* open-lens build fixes

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* integration test fix

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix nx task dependencies

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* use bash shell for unit tests in test workflow

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix test:unit for windows

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix win-ca webpack error in open-lens

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix win-ca webpack error in open-lens

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix build:app on windows

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* remove ELECTRON_BUILDER_EXTRA_ARGS

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* sync src/ from master

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* remove Makefile from core

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen authored and GitHub committed 2023-01-24 10:46:26 -08:00
1 parent 0066ca4ea2
commit 2657df2293
3321 files changed
+28699 -11447

No files matched your search

@@ -0,0 +1,67 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
/*
Cluster tests are run if there is a pre-existing minikube cluster. Before running cluster tests the TEST_NAMESPACE
namespace is removed, if it exists, from the minikube cluster. Resources are created as part of the cluster tests in the
TEST_NAMESPACE namespace. This is done to minimize destructive impact of the cluster tests on an existing minikube
cluster and vice versa.
*/
import type { ElectronApplication, Page } from "playwright";
import * as utils from "../helpers/utils";
describe("preferences page tests", () => {
let window: Page;
let cleanup: undefined | (() => Promise<void>);
beforeEach(async () => {
let app: ElectronApplication;
({ window, cleanup, app } = await utils.start());
await utils.clickWelcomeButton(window);
await app.evaluate(async ({ app }) => {
await app.applicationMenu
?.getMenuItemById(process.platform === "darwin" ? "mac" : "file")
?.submenu
?.getMenuItemById("navigate-to-preferences")
?.click();
});
}, 10*60*1000);
afterEach(async () => {
await cleanup?.();
}, 10*60*1000);
it('shows "preferences" and can navigate through the tabs', async () => {
const pages = [
{
id: "app",
header: "Application",
},
{
id: "proxy",
header: "Proxy",
},
{
id: "kubernetes",
header: "Kubernetes",
},
];
for (const { id, header } of pages) {
await window.click(`[data-preference-tab-link-test=${id}]`);
await window.waitForSelector(`[data-preference-page-title-test] >> text=${header}`);
}
}, 10*60*1000);
// Skipping, but will turn it on again in the follow up PR
it.skip("ensures helm repos", async () => {
await window.click("[data-testid=kubernetes-tab]");
await window.waitForSelector("[data-testid=repository-name]");
await window.click("#HelmRepoSelect");
await window.waitForSelector("div.Select__option");
}, 10*60*1000);
});
@@ -0,0 +1,378 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
/*
Cluster tests are run if there is a pre-existing minikube cluster. Before running cluster tests the TEST_NAMESPACE
namespace is removed, if it exists, from the minikube cluster. Resources are created as part of the cluster tests in the
TEST_NAMESPACE namespace. This is done to minimize destructive impact of the cluster tests on an existing minikube
cluster and vice versa.
*/
import * as utils from "../helpers/utils";
import { minikubeReady } from "../helpers/minikube";
import type { Frame, Page } from "playwright";
import { groupBy, toPairs } from "lodash/fp";
import { pipeline } from "@ogre-tools/fp";
import { describeIf } from "../../../core/src/test-utils/skippers";
const TEST_NAMESPACE = "integration-tests";
describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => {
let window: Page;
let cleanup: undefined | (() => Promise<void>);
let frame: Frame;
beforeEach(async () => {
({ window, cleanup } = await utils.start());
await utils.clickWelcomeButton(window);
frame = await utils.lauchMinikubeClusterFromCatalog(window);
}, 10 * 60 * 1000);
afterEach(async () => {
await cleanup?.();
}, 10 * 60 * 1000);
it("shows cluster context menu in sidebar", async () => {
await frame.click(`[data-testid="sidebar-cluster-dropdown"]`);
await frame.waitForSelector(`.Menu >> text="Add to Hotbar"`);
await frame.waitForSelector(`.Menu >> text="Settings"`);
await frame.waitForSelector(`.Menu >> text="Disconnect"`);
await frame.waitForSelector(`.Menu >> text="Remove"`);
});
// FIXME: failed locally since metrics might already exist, cc @aleksfront
it.skip("opens cluster settings by clicking link in no-metrics area", async () => {
await frame.locator("text=Open cluster settings >> nth=0").click();
await window.waitForSelector(`[data-testid="metrics-header"]`);
});
it(
"should navigate around common cluster pages",
async () => {
const scenariosByParent = pipeline(
scenarios,
groupBy("parentSidebarItemTestId"),
toPairs,
);
for (const [parentSidebarItemTestId, scenarios] of scenariosByParent) {
if (parentSidebarItemTestId !== "null") {
await frame.click(`[data-testid="${parentSidebarItemTestId}"]`);
}
for (const scenario of scenarios) {
await frame.click(`[data-testid="${scenario.sidebarItemTestId}"]`);
await frame.waitForSelector(
scenario.expectedSelector,
selectorTimeout,
);
}
}
},
10 * 60 * 1000,
);
it(
"should show the default namespaces",
async () => {
await navigateToNamespaces(frame);
await frame.waitForSelector("div.TableCell >> text='default'");
await frame.waitForSelector("div.TableCell >> text='kube-system'");
},
10 * 60 * 1000,
);
it(
`should create the ${TEST_NAMESPACE} and a pod in the namespace and then remove that pod via the context menu`,
async () => {
await navigateToNamespaces(frame);
await frame.click("button.add-button");
await frame.waitForSelector(
"div.AddNamespaceDialog >> text='Create Namespace'",
);
const namespaceNameInput = await frame.waitForSelector(
".AddNamespaceDialog input",
);
await namespaceNameInput.type(TEST_NAMESPACE);
await namespaceNameInput.press("Enter");
await frame.waitForSelector(`div.TableCell >> text=${TEST_NAMESPACE}`);
await navigateToPods(frame);
const namespacesSelector = await frame.waitForSelector(
".NamespaceSelect",
);
await namespacesSelector.click();
await namespacesSelector.type(TEST_NAMESPACE);
await namespacesSelector.press("Enter");
await namespacesSelector.click();
await frame.click(".Icon.new-dock-tab");
try {
await frame.click("li.MenuItem.create-resource-tab", {
// NOTE: the following shouldn't be required, but is because without it a TypeError is thrown
// see: https://github.com/microsoft/playwright/issues/8229
position: {
y: 0,
x: 0,
},
});
} catch (error) {
console.log(error);
await frame.waitForTimeout(100_000);
}
const testPodName = "nginx-create-pod-test";
const monacoEditor = await frame.waitForSelector(`.Dock.isOpen [data-test-id="monaco-editor"]`);
await monacoEditor.click();
await monacoEditor.type("apiVersion: v1", { delay: 10 });
await monacoEditor.press("Enter", { delay: 10 });
await monacoEditor.type("kind: Pod", { delay: 10 });
await monacoEditor.press("Enter", { delay: 10 });
await monacoEditor.type("metadata:", { delay: 10 });
await monacoEditor.press("Enter", { delay: 10 });
await monacoEditor.type(` name: ${testPodName}`, { delay: 10 });
await monacoEditor.press("Enter", { delay: 10 });
await monacoEditor.type(`namespace: ${TEST_NAMESPACE}`, { delay: 10 });
await monacoEditor.press("Enter", { delay: 10 });
await monacoEditor.press("Backspace", { delay: 10 });
await monacoEditor.type("spec:", { delay: 10 });
await monacoEditor.press("Enter", { delay: 10 });
await monacoEditor.type(" containers:", { delay: 10 });
await monacoEditor.press("Enter", { delay: 10 });
await monacoEditor.type(`- name: ${testPodName}`, { delay: 10 });
await monacoEditor.press("Enter", { delay: 10 });
await monacoEditor.type(" image: nginx:alpine", { delay: 10 });
await monacoEditor.press("Enter", { delay: 10 });
await frame.click(".Dock .Button >> text='Create'");
await frame.waitForSelector(`.TableCell >> text=${testPodName}`);
await frame.click(".TableRow .TableCell.menu");
await frame.click(".MenuItem >> text=Delete");
await frame.click("button >> text=Remove");
await frame.waitForSelector(`.TableCell >> text=${testPodName}`, { state: "detached" });
},
10 * 60 * 1000,
);
});
const selectorTimeout = { timeout: 30000 };
const scenarios = [
{
expectedSelector: "div[data-testid='cluster-overview-page'] div.label",
parentSidebarItemTestId: null,
sidebarItemTestId: "sidebar-item-link-for-cluster-overview",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: null,
sidebarItemTestId: "sidebar-item-link-for-nodes",
},
{
expectedSelector: 'h5 >> text="Overview"',
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
sidebarItemTestId: "sidebar-item-link-for-overview",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
sidebarItemTestId: "sidebar-item-link-for-pods",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
sidebarItemTestId: "sidebar-item-link-for-deployments",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
sidebarItemTestId: "sidebar-item-link-for-daemon-sets",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
sidebarItemTestId: "sidebar-item-link-for-stateful-sets",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
sidebarItemTestId: "sidebar-item-link-for-replica-sets",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
sidebarItemTestId: "sidebar-item-link-for-jobs",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
sidebarItemTestId: "sidebar-item-link-for-cron-jobs",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-config",
sidebarItemTestId: "sidebar-item-link-for-config-maps",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-config",
sidebarItemTestId: "sidebar-item-link-for-secrets",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-config",
sidebarItemTestId: "sidebar-item-link-for-resource-quotas",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-config",
sidebarItemTestId: "sidebar-item-link-for-limit-ranges",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-config",
sidebarItemTestId: "sidebar-item-link-for-horizontal-pod-auto-scalers",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-config",
sidebarItemTestId: "sidebar-item-link-for-pod-disruption-budgets",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-network",
sidebarItemTestId: "sidebar-item-link-for-services",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-network",
sidebarItemTestId: "sidebar-item-link-for-endpoints",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-network",
sidebarItemTestId: "sidebar-item-link-for-ingresses",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-network",
sidebarItemTestId: "sidebar-item-link-for-network-policies",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-storage",
sidebarItemTestId: "sidebar-item-link-for-persistent-volume-claims",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-storage",
sidebarItemTestId: "sidebar-item-link-for-persistent-volumes",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-storage",
sidebarItemTestId: "sidebar-item-link-for-storage-classes",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: null,
sidebarItemTestId: "sidebar-item-link-for-namespaces",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: null,
sidebarItemTestId: "sidebar-item-link-for-events",
},
{
expectedSelector: "div.HelmCharts input",
parentSidebarItemTestId: "sidebar-item-link-for-helm",
sidebarItemTestId: "sidebar-item-link-for-charts",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-helm",
sidebarItemTestId: "sidebar-item-link-for-releases",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
sidebarItemTestId: "sidebar-item-link-for-service-accounts",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
sidebarItemTestId: "sidebar-item-link-for-cluster-roles",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
sidebarItemTestId: "sidebar-item-link-for-roles",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
sidebarItemTestId: "sidebar-item-link-for-cluster-role-bindings",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
sidebarItemTestId: "sidebar-item-link-for-role-bindings",
},
{
expectedSelector: "h5.title",
parentSidebarItemTestId: null,
sidebarItemTestId: "sidebar-item-link-for-custom-resources",
},
];
const navigateToPods = async (frame: Frame) => {
await frame.click(`[data-testid="sidebar-item-link-for-workloads"]`);
await frame.click(`[data-testid="sidebar-item-link-for-pods"]`);
};
const navigateToNamespaces = async (frame: Frame) => {
await frame.click(`[data-testid="sidebar-item-link-for-namespaces"]`);
};
@@ -0,0 +1,34 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ElectronApplication, Page } from "playwright";
import * as utils from "../helpers/utils";
describe("Lens command palette", () => {
let window: Page;
let cleanup: undefined | (() => Promise<void>);
let app: ElectronApplication;
beforeEach(async () => {
({ window, cleanup, app } = await utils.start());
await utils.clickWelcomeButton(window);
}, 10*60*1000);
afterEach(async () => {
await cleanup?.();
}, 10*60*1000);
describe("menu", () => {
it("opens command dialog from menu", async () => {
await app.evaluate(async ({ app }) => {
await app.applicationMenu
?.getMenuItemById("view")
?.submenu?.getMenuItemById("open-command-palette")
?.click();
});
await window.waitForSelector(".Select__option >> text=Hotbar: Switch");
}, 10*60*1000);
});
});
@@ -0,0 +1,42 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { spawnSync } from "child_process";
export function minikubeReady(testNamespace: string): boolean {
// determine if minikube is running
{
const { status } = spawnSync("minikube status", { shell: true });
if (status !== 0) {
console.warn("minikube not running");
return false;
}
}
// Remove TEST_NAMESPACE if it already exists
{
const { status } = spawnSync(`minikube kubectl -- get namespace ${testNamespace}`, { shell: true });
if (status === 0) {
console.warn(`Removing existing ${testNamespace} namespace`);
const { status, stdout, stderr } = spawnSync(
`minikube kubectl -- delete namespace ${testNamespace}`,
{ shell: true },
);
if (status !== 0) {
console.warn(`Error removing ${testNamespace} namespace: ${stderr.toString()}`);
return false;
}
console.log(stdout.toString());
}
}
return true;
}
@@ -0,0 +1,138 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { createHash } from "crypto";
import { mkdirp, remove } from "fs-extra";
import * as os from "os";
import * as path from "path";
import * as uuid from "uuid";
import type { ElectronApplication, Frame, Page } from "playwright";
import { _electron as electron } from "playwright";
import { noop } from "lodash";
import { disposer } from "../../../core/src/common/utils";
export const appPaths: Partial<Record<NodeJS.Platform, string>> = {
"win32": "./dist/win-unpacked/OpenLens.exe",
"linux": "./dist/linux-unpacked/open-lens",
"darwin": `./dist/mac${ process.arch === "arm64" ? "-arm64" : "" }/OpenLens.app/Contents/MacOS/OpenLens`,
};
async function getMainWindow(app: ElectronApplication, timeout = 50_000): Promise<Page> {
return new Promise((resolve, reject) => {
const cleanup = disposer();
let stdoutBuf = "";
const onWindow = (page: Page) => {
console.log(`Page opened: ${page.url()}`);
if (page.url().startsWith("https://lens.app")) {
cleanup();
console.log(stdoutBuf);
resolve(page);
}
};
app.on("window", onWindow);
cleanup.push(() => app.off("window", onWindow));
const onClose = () => {
cleanup();
reject(new Error("App has unnexpectedly closed"));
};
app.on("close", onClose);
cleanup.push(() => app.off("close", onClose));
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const stdout = app.process().stdout!;
const onData = (chunk: any) => stdoutBuf += chunk.toString();
stdout.on("data", onData);
cleanup.push(() => stdout.off("data", onData));
const timeoutId = setTimeout(() => {
cleanup();
console.log(stdoutBuf);
reject(new Error(`Lens did not open the main window within ${timeout}ms`));
}, timeout);
cleanup.push(() => clearTimeout(timeoutId));
});
}
async function attemptStart() {
const CICD = path.join(os.tmpdir(), "lens-integration-testing", uuid.v4());
// Make sure that the directory is clear
await remove(CICD).catch(noop);
await mkdirp(CICD);
const app = await electron.launch({
args: ["--integration-testing"], // this argument turns off the blocking of quit
executablePath: appPaths[process.platform],
bypassCSP: true,
env: {
CICD,
...process.env,
},
timeout: 100_000,
});
try {
const window = await getMainWindow(app);
return {
app,
window,
cleanup: async () => {
await app.close();
await remove(CICD).catch(noop);
},
};
} catch (error) {
await app.close();
await remove(CICD).catch(noop);
throw error;
}
}
export async function start() {
// this is an attempted workaround for an issue with playwright not always getting the main window when using Electron 14.2.4 (observed on windows)
for (let i = 0; ; i++) {
try {
return await attemptStart();
} catch (error) {
if (i === 4) {
throw error;
}
}
}
}
export async function clickWelcomeButton(window: Page) {
await window.click("[data-testid=welcome-menu-container] li a");
}
function minikubeEntityId() {
return createHash("md5").update(`${path.join(os.homedir(), ".kube", "config")}:minikube`).digest("hex");
}
/**
* From the catalog, click the minikube entity and wait for it to connect, returning its frame
*/
export async function lauchMinikubeClusterFromCatalog(window: Page): Promise<Frame> {
await window.click("div.TableCell >> text='minikube'");
const minikubeFrame = await window.waitForSelector(`#cluster-frame-${minikubeEntityId()}`);
const frame = await minikubeFrame.contentFrame();
if (!frame) {
throw new Error("No iframe for minikube found");
}
await frame.waitForSelector("[data-testid=cluster-sidebar]");
return frame;
}
@@ -0,0 +1,6 @@
{
"extends": "../tsconfig.json",
"include": [
"./**/*",
]
}