feat(integrations): add beszel system usage integration
This commit is contained in:
28 files changed
+955
-2
No files matched your search
@@ -0,0 +1,176 @@
|
||||
import { rmdir } from "fs/promises";
|
||||
import { join } from "path";
|
||||
import type { StartedTestContainer } from "testcontainers";
|
||||
import { GenericContainer, Wait } from "testcontainers";
|
||||
import { afterAll, beforeAll, describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { createId } from "@homarr/common";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
|
||||
import { BeszelIntegration } from "../src/beszel/beszel-integration";
|
||||
|
||||
vi.mock("@homarr/db", async (importActual) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
const actual = await importActual<typeof import("@homarr/db")>();
|
||||
return {
|
||||
...actual,
|
||||
db: createDb(),
|
||||
};
|
||||
});
|
||||
|
||||
const email = "e2e@homarr.dev";
|
||||
const password = "12341234";
|
||||
const publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID5PFut0emFoQQ53LlLGfFYA/U1Noe6WPqYN/97LJTvR";
|
||||
const token = "4af8b512-afdc-4e42-a478-d0a3bfbbb4a8";
|
||||
const IMAGE_NAME = "henrygd/beszel:latest";
|
||||
const AGENT_IMAGE_NAME = "henrygd/beszel-agent:latest";
|
||||
const systemId = "nj259tge7e4h7xy";
|
||||
|
||||
describe("Beszel integration", () => {
|
||||
let socketPathId: string;
|
||||
let beszelContainer: StartedTestContainer;
|
||||
let beszelAgentContainer: StartedTestContainer;
|
||||
|
||||
beforeAll(async () => {
|
||||
socketPathId = createId();
|
||||
beszelContainer = await createBeszelContainer(socketPathId).start();
|
||||
beszelAgentContainer = await createBeszelAgentContainer(socketPathId, beszelContainer.getMappedPort(8090)).start();
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 5_000)); // Wait for Beszel to be ready and have some system_data
|
||||
}, 20_000);
|
||||
|
||||
afterAll(async () => {
|
||||
await beszelContainer.stop();
|
||||
await beszelAgentContainer.stop();
|
||||
await rmdir(createSocketPath(socketPathId), { recursive: true });
|
||||
}, 5_000);
|
||||
|
||||
test("Test connection is successful", async () => {
|
||||
// Arrange
|
||||
const integration = createIntegration(beszelContainer.getMappedPort(8090));
|
||||
|
||||
// Act
|
||||
const result = await integration.testConnectionAsync();
|
||||
|
||||
// Assert
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
test("Test connection fails with invalid credentials", async () => {
|
||||
// Arrange
|
||||
const integration = createIntegration(beszelContainer.getMappedPort(8090), {
|
||||
password: "wrongpassword",
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await integration.testConnectionAsync();
|
||||
|
||||
// Assert
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
expect(result.error.type).toBe("authorization");
|
||||
});
|
||||
|
||||
test("Can get systems", async () => {
|
||||
// Arrange
|
||||
const integration = createIntegration(beszelContainer.getMappedPort(8090));
|
||||
|
||||
// Act
|
||||
const systems = await integration.getSystemsAsync();
|
||||
|
||||
// Assert
|
||||
expect(systems.length).toBe(1);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
expect(systems[0]!.name).toBe("localhost");
|
||||
});
|
||||
|
||||
test("Can get system details", async () => {
|
||||
// Arrange
|
||||
const integration = createIntegration(beszelContainer.getMappedPort(8090));
|
||||
|
||||
// Act
|
||||
const system = await integration.getSystemDetailsAsync(systemId);
|
||||
|
||||
// Assert
|
||||
expect(system.id).toBe(systemId);
|
||||
expect(system.name).toBe("localhost");
|
||||
expect(system.agent.connectionType).toBe("webSocket");
|
||||
expect(system.agent.version).toBeDefined();
|
||||
expect(system.status).toBe("up");
|
||||
expect(system.usage.cpuPercentage).toBeGreaterThanOrEqual(0);
|
||||
expect(system.usage.memoryPercentage).toBeGreaterThanOrEqual(0);
|
||||
expect(system.usage.diskPercentage).toBeGreaterThanOrEqual(0);
|
||||
expect(system.usage.load.averages.one).toBeGreaterThanOrEqual(0);
|
||||
expect(system.usage.networkBytes).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
const createIntegration = (port: number, overrides: { hostname?: string; password?: string } = {}) =>
|
||||
new BeszelIntegration({
|
||||
id: createId(),
|
||||
name: "Beszel E2E",
|
||||
url: `http://${overrides.hostname ?? "localhost"}:${port}`,
|
||||
externalUrl: null,
|
||||
decryptedSecrets: [
|
||||
{
|
||||
kind: "username",
|
||||
value: email,
|
||||
},
|
||||
{
|
||||
kind: "password",
|
||||
value: overrides.password ?? password,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const files = ["data.db", "data.db-shm", "data.db-wal", "id_ed25519"];
|
||||
|
||||
const createBeszelContainer = (id: string) => {
|
||||
return new GenericContainer(IMAGE_NAME)
|
||||
.withCopyFilesToContainer(
|
||||
files.map((file) => ({
|
||||
source: join(__dirname, `/volumes/beszel/${file}`),
|
||||
target: `/beszel_data/${file}`,
|
||||
})),
|
||||
)
|
||||
.withExposedPorts(8090)
|
||||
.withBindMounts([
|
||||
{
|
||||
source: createSocketPath(id),
|
||||
target: "/beszel_socket",
|
||||
mode: "rw",
|
||||
},
|
||||
])
|
||||
.withWaitStrategy(Wait.forHttp("/", 8090));
|
||||
};
|
||||
|
||||
const createBeszelAgentContainer = (id: string, port: number) => {
|
||||
return new GenericContainer(AGENT_IMAGE_NAME)
|
||||
.withBindMounts([
|
||||
{
|
||||
source: "/var/run/docker.sock",
|
||||
target: "/var/run/docker.sock",
|
||||
mode: "ro",
|
||||
},
|
||||
{
|
||||
source: createSocketPath(id),
|
||||
target: "/beszel_socket",
|
||||
mode: "rw",
|
||||
},
|
||||
])
|
||||
.withEnvironment({
|
||||
LISTEN: "/beszel_socket/beszel.sock",
|
||||
HUB_URL: `http://host.docker.internal:${port}`,
|
||||
TOKEN: token,
|
||||
KEY: publicKey,
|
||||
})
|
||||
.withExtraHosts([
|
||||
{
|
||||
// This enabled the usage of host.docker.internal as hostname in the container
|
||||
host: "host.docker.internal",
|
||||
ipAddress: "host-gateway",
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const createSocketPath = (id: string) => join(__dirname, `/volumes/beszel/socket-${id}`);
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtz
|
||||
c2gtZWQyNTUxOQAAACA+TxbrdHphaEEOdy5SxnxWAP1NTaHulj6mDf/eyyU70QAA
|
||||
AIgMmaMFDJmjBQAAAAtzc2gtZWQyNTUxOQAAACA+TxbrdHphaEEOdy5SxnxWAP1N
|
||||
TaHulj6mDf/eyyU70QAAAEBgdIVB4OrfE2cUkVn7lpqtyq+XRq2rxzq01U1DPHB+
|
||||
fj5PFut0emFoQQ53LlLGfFYA/U1Noe6WPqYN/97LJTvRAAAAAAECAwQF
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
Reference in new issue
Block a user