Skip to content

Commit affc0bf

Browse files
haraldschillyclaude
andcommitted
server/conat: discover peers only via K8S API
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 8c50882 commit affc0bf

File tree

4 files changed

+156
-76
lines changed

4 files changed

+156
-76
lines changed

src/.claude/settings.local.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(pnpm tsc:*)",
5+
"Bash(pnpm build:*)",
6+
"Bash(git add:*)",
7+
"Bash(git commit:*)"
8+
],
9+
"deny": []
10+
}
11+
}

src/CLAUDE.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
# CoCalc Source Repository
6+
7+
* This is the source code of CoCalc in a Git repository
8+
* It is a complex JavaScript/TypeScript SaaS application
9+
* CoCalc is organized as a monorepository (multi-packages) in the subdirectory "./packages"
10+
* The packages are managed as a pnpm workspace in "./packages/pnpm-workspace.yaml"
11+
12+
## Code Style
13+
14+
- Everything is written in TypeScript code
15+
- Indentation: 2-spaces
16+
- All .js and .ts files are formatted by the tool prettier
17+
- Add suitable types when you write code
18+
- Variable name styles are "camelCase" for local and "FOO_BAR" for global variables. If you edit older code not following these guidlines, adjust this rule to fit the files style.
19+
- Some older code is JavaScript or CoffeeScript, which will be translated to TypeScript
20+
- Use ES modules (import/export) syntax, not CommonJS (require)
21+
- Organize the list of imports in such a way: installed npm packages are on top, newline, then are imports from @cocalc's code base. Sorted alphabetically.
22+
23+
## Development Commands
24+
25+
### Essential Commands
26+
- `pnpm build-dev` - Build all packages for development
27+
- `pnpm clean` - Clean all node_modules and dist directories
28+
- `pnpm database` - Start PostgreSQL database server
29+
- `pnpm hub` - Start the main hub server
30+
- `pnpm psql` - Connect to the PostgreSQL database
31+
- `pnpm test` - Run full test suite
32+
- `pnpm test-parallel` - Run tests in parallel across packages
33+
- `pnpm depcheck` - Check for dependency issues
34+
35+
### Package-Specific Commands
36+
- `cd packages/[package] && pnpm tsc` - Watch TypeScript compilation for a specific package
37+
- `cd packages/[package] && pnpm test` - Run tests for a specific package
38+
- `cd packages/[package] && pnpm build` - Build a specific package
39+
40+
### Development Setup
41+
1. Start database: `pnpm database`
42+
2. Start hub: `pnpm hub`
43+
3. For TypeScript changes, run `pnpm tsc` in the relevant package directory
44+
45+
## Architecture Overview
46+
47+
### Package Structure
48+
CoCalc is organized as a monorepo with key packages:
49+
50+
- **frontend** - React/TypeScript frontend application using Redux-style stores and actions
51+
- **backend** - Node.js backend services and utilities
52+
- **hub** - Main server orchestrating the entire system
53+
- **database** - PostgreSQL database layer with queries and schema
54+
- **util** - Shared utilities and types used across packages
55+
- **comm** - Communication layer including WebSocket types
56+
- **conat** - CoCalc's container/compute orchestration system
57+
- **sync** - Real-time synchronization system for collaborative editing
58+
- **project** - Project-level services and management
59+
- **static** - Static assets and build configuration
60+
- **next** - Next.js server components
61+
62+
### Key Architectural Patterns
63+
64+
#### Frontend Architecture
65+
- **Redux-style State Management**: Uses custom stores and actions pattern (see `packages/frontend/app-framework/actions-and-stores.ts`)
66+
- **TypeScript React Components**: All frontend code is TypeScript with proper typing
67+
- **Modular Store System**: Each feature has its own store/actions (AccountStore, BillingStore, etc.)
68+
- **WebSocket Communication**: Real-time communication with backend via WebSocket messages
69+
70+
#### Backend Architecture
71+
- **PostgreSQL Database**: Primary data store with sophisticated querying system
72+
- **WebSocket Messaging**: Real-time communication between frontend and backend
73+
- **Conat System**: Container orchestration for compute servers
74+
- **Event-Driven Architecture**: Extensive use of EventEmitter patterns
75+
- **Microservice-like Packages**: Each package handles specific functionality
76+
77+
#### Communication Patterns
78+
- **WebSocket Messages**: Primary communication method (see `packages/comm/websocket/types.ts`)
79+
- **Database Queries**: Structured query system with typed interfaces
80+
- **Event Emitters**: Inter-service communication within backend
81+
- **REST-like APIs**: Some HTTP endpoints for specific operations
82+
83+
### Key Technologies
84+
- **TypeScript**: Primary language for all new code
85+
- **React**: Frontend framework
86+
- **PostgreSQL**: Database
87+
- **Node.js**: Backend runtime
88+
- **WebSockets**: Real-time communication
89+
- **pnpm**: Package manager and workspace management
90+
- **Jest**: Testing framework
91+
- **SASS**: CSS preprocessing
92+
93+
### Database Schema
94+
- Comprehensive schema in `packages/util/db-schema`
95+
- Query abstractions in `packages/database/postgres/`
96+
- Type-safe database operations with TypeScript interfaces
97+
98+
### Testing
99+
- **Jest**: Primary testing framework
100+
- **ts-jest**: TypeScript support for Jest
101+
- **jsdom**: Browser environment simulation for frontend tests
102+
- Test files use `.test.ts` or `.spec.ts` extensions
103+
- Each package has its own jest.config.js
104+
105+
### Import Patterns
106+
- Use absolute imports with `@cocalc/` prefix for cross-package imports
107+
- Example: `import { cmp } from "@cocalc/util/misc"`
108+
- Type imports: `import type { Foo } from "./bar"`
109+
- Destructure imports when possible
110+
111+
### Development Workflow
112+
1. Changes to TypeScript require compilation (`pnpm tsc` in relevant package)
113+
2. Database must be running before starting hub
114+
3. Hub coordinates all services and should be restarted after changes
115+
4. Use `pnpm clean && pnpm build-dev` when switching branches or after major changes
116+
117+
# Workflow
118+
- Be sure to typecheck when you're done making a series of code changes
119+
- Prefer running single tests, and not the whole test suite, for performance
120+
121+
## Git Workflow
122+
123+
- Prefix git commits with the package and general area. e.g. 'frontend/latex: ...' if it concerns latex editor changes in the packages/frontend/... code.
124+
- When pushing a new branch to Github, track it upstream. e.g. `git push --set-upstream origin feature-foo` for branch "feature-foo".
125+
126+
# important-instruction-reminders
127+
- Do what has been asked; nothing more, nothing less.
128+
- NEVER create files unless they're absolutely necessary for achieving your goal.
129+
- ALWAYS prefer editing an existing file to creating a new one.
130+
- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.

src/packages/server/conat/socketio/dns-scan-k8s-api.ts

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,29 @@
11
import { readFile } from "fs/promises";
22
import * as https from "https";
33

4+
import type { PodInfos } from "./dns-scan";
5+
46
// Define the options interface for type safety
57
interface ListPodsOptions {
68
labelSelector?: string; // e.g. "app=foo,env=prod"
79
}
810

911
let NAMESPACE: string | null = null;
10-
let CA: Buffer | null = null;
12+
let CA: string | null = null;
13+
14+
async function readK8sFile(filename: string): Promise<string> {
15+
const basePath = "/var/run/secrets/kubernetes.io/serviceaccount";
16+
return (await readFile(`${basePath}/${filename}`, "utf8")).trim();
17+
}
1118

1219
async function listPods(options: ListPodsOptions = {}): Promise<any> {
1320
let token: string;
1421
try {
15-
NAMESPACE =
16-
NAMESPACE ??
17-
(
18-
await readFile(
19-
"/var/run/secrets/kubernetes.io/serviceaccount/namespace",
20-
"utf8",
21-
)
22-
).trim();
23-
CA =
24-
CA ??
25-
(await readFile("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"));
22+
NAMESPACE ??= await readK8sFile("namespace");
23+
CA ??= await readK8sFile("ca.crt");
2624

2725
// Read service account details, token could be rotated, so read every time
28-
token = (
29-
await readFile(
30-
"/var/run/secrets/kubernetes.io/serviceaccount/token",
31-
"utf8",
32-
)
33-
).trim();
26+
token = await readK8sFile("token");
3427
} catch (err) {
3528
throw new Error(`Failed to read service account files: ${err}`);
3629
}
@@ -88,11 +81,9 @@ async function listPods(options: ListPodsOptions = {}): Promise<any> {
8881
});
8982
}
9083

91-
export async function getAddressesFromK8sApi(): Promise<
92-
{ name: string; podIP: string }[]
93-
> {
84+
export async function getAddressesFromK8sApi(): Promise<PodInfos> {
9485
const res = await listPods({ labelSelector: "run=hub-conat-router" });
95-
const ret: { name: string; podIP: string }[] = [];
86+
const ret: PodInfos = [];
9687
for (const pod of res.items) {
9788
const name = pod.metadata?.name;
9889
const podIP = pod.status?.podIP;

src/packages/server/conat/socketio/dns-scan.ts

Lines changed: 2 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,14 @@ import { delay } from "awaiting";
88
import { lookup } from "dns/promises";
99
import { hostname } from "node:os";
1010

11-
import { executeCode } from "@cocalc/backend/execute-code";
1211
import { getLogger } from "@cocalc/backend/logger";
1312
import port from "@cocalc/backend/port";
1413
import type { ConatServer } from "@cocalc/conat/core/server";
15-
import { split, unreachable } from "@cocalc/util/misc";
1614
import { getAddressesFromK8sApi } from "./dns-scan-k8s-api";
1715

1816
export const SCAN_INTERVAL = 15_000;
1917

20-
type PeerDiscovery = "KUBECTL" | "API";
21-
22-
function isPeerDiscovery(x: string): x is PeerDiscovery {
23-
return x === "KUBECTL" || x === "API";
24-
}
25-
26-
const PEER_DISCOVERY: PeerDiscovery = (function () {
27-
const val = process.env.COCALC_CONAT_PEER_DISCOVERY ?? "KUBECTL";
28-
if (!isPeerDiscovery(val)) {
29-
throw Error(`Invalid COCALC_CONAT_PEER_DISCOVERY: ${val}`);
30-
}
31-
return val;
32-
})();
18+
export type PodInfos = { name: string; podIP: string }[];
3319

3420
const logger = getLogger("conat:socketio:dns-scan");
3521

@@ -100,49 +86,11 @@ export async function getAddresses(): Promise<string[]> {
10086
const i = h.lastIndexOf("-");
10187
const prefix = h.slice(0, i);
10288

103-
const podInfos = await getPodInfos();
89+
const podInfos: PodInfos = await getAddressesFromK8sApi();
10490
for (const { name, podIP } of podInfos) {
10591
if (name != h && name.startsWith(prefix)) {
10692
v.push(`http://${podIP}:${port}`);
10793
}
10894
}
10995
return v;
11096
}
111-
112-
async function getPodInfos(): Promise<{ name: string; podIP: string }[]> {
113-
switch (PEER_DISCOVERY) {
114-
case "KUBECTL":
115-
return await getAddressesFromKubectl();
116-
case "API":
117-
return await getAddressesFromK8sApi();
118-
default:
119-
unreachable(PEER_DISCOVERY);
120-
throw Error(`Unknown PEER_DISCOVERY: ${PEER_DISCOVERY}`);
121-
}
122-
}
123-
124-
async function getAddressesFromKubectl(): Promise<
125-
{ name: string; podIP: string }[]
126-
> {
127-
const ret: { name: string; podIP: string }[] = [];
128-
const { stdout } = await executeCode({
129-
command: "kubectl",
130-
args: [
131-
"get",
132-
"pods",
133-
"-l",
134-
"run=hub-conat-router",
135-
"-o",
136-
`jsonpath={range .items[*]}{.metadata.name}{"\\t"}{.status.podIP}{"\\n"}{end}`,
137-
],
138-
});
139-
for (const x of stdout.split("\n")) {
140-
const row = split(x);
141-
if (row.length == 2) {
142-
ret.push({ name: row[0], podIP: row[1] });
143-
} else {
144-
logger.warn(`Unexpected row from kubectl: ${x}`);
145-
}
146-
}
147-
return ret;
148-
}

0 commit comments

Comments
 (0)