@owlmeans/kluster 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 OwlMeans Common — Fullstack typescript framework
3
+ Copyright (c) 2026 OwlMeans Common — Fullstack typescript framework
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,640 +1,67 @@
1
1
  # @owlmeans/kluster
2
2
 
3
- The **@owlmeans/kluster** package provides Kubernetes integration for OwlMeans Common Libraries, enabling applications to interact with Kubernetes clusters for service discovery, pod management, and configuration resolution in cloud-native environments.
3
+ Kubernetes integration service — service discovery and pod/service IP resolution for cloud-native OwlMeans apps.
4
4
 
5
- ## Purpose
5
+ ## Overview
6
6
 
7
- This package serves as a Kubernetes integration layer for OwlMeans applications, specifically designed for:
8
-
9
- - **Service Discovery**: Automatically discover and connect to services running in Kubernetes clusters
10
- - **Pod Management**: Query and interact with pods using label selectors
11
- - **Configuration Resolution**: Resolve configuration values from Kubernetes resources at runtime
12
- - **Cloud-native Deployment**: Enable applications to work seamlessly in Kubernetes environments
13
- - **Dynamic Service Endpoints**: Automatically resolve service hostnames and endpoints
14
- - **Network API Integration**: Access Kubernetes networking APIs for advanced operations
15
-
16
- ## Key Concepts
17
-
18
- ### Kubernetes Service Discovery
19
- The package provides automatic service discovery within Kubernetes clusters, allowing applications to find and connect to other services without hardcoded endpoints.
20
-
21
- ### Configuration Directives
22
- Special configuration syntax (`kluster:directive:query`) that gets resolved at runtime using Kubernetes API calls, enabling dynamic configuration based on cluster state.
23
-
24
- ### Lazy Initialization
25
- Kubernetes client is initialized lazily when first accessed, reducing startup time and resource usage when Kubernetes features are not immediately needed.
26
-
27
- ### Label Selector Queries
28
- Uses Kubernetes label selectors to query for pods and services, providing flexible and powerful resource discovery capabilities.
7
+ - `makeKlusterService(alias?)` — creates a Kubernetes API client service
8
+ - `klusterize(context, alias?)` — registers the service and its config-resolution middleware
9
+ - `KlusterService` — resolves pod hostnames and service cluster IPs, dispatches `kluster:` directives
10
+ - Used to resolve `kluster:<selector>` config values to actual pod IPs at runtime
29
11
 
30
12
  ## Installation
31
13
 
32
14
  ```bash
33
- npm install @owlmeans/kluster
34
- ```
35
-
36
- ## API Reference
37
-
38
- ### Types
39
-
40
- #### `KlusterService`
41
- Main service interface for Kubernetes operations.
42
-
43
- ```typescript
44
- interface KlusterService extends LazyService {
45
- config?: KubeConfig
46
- api?: CoreV1Api
47
-
48
- getHostnames(selector: string): Promise<string[]>
49
- getServiceHostname(selector: string): Promise<string>
50
- dispatch<T>(action: string, query: string): Promise<T>
51
- makeNetworkingApi(): NetworkingV1Api
52
- }
53
- ```
54
-
55
- #### `KlusterConfig`
56
- Configuration interface extending ServerConfig.
57
-
58
- ```typescript
59
- interface KlusterConfig extends ServerConfig {
60
- kluster?: {
61
- namespace?: string
62
- }
63
- }
64
- ```
65
-
66
- ### Core Functions
67
-
68
- #### `makeKlusterService(alias?: string): KlusterService`
69
-
70
- Creates a Kubernetes service instance with lazy initialization.
71
-
72
- **Parameters:**
73
- - `alias`: Service alias (defaults to 'kluster')
74
-
75
- **Returns:** KlusterService instance
76
-
77
- ```typescript
78
- import { makeKlusterService } from '@owlmeans/kluster'
79
-
80
- const klusterService = makeKlusterService('k8s-client')
15
+ bun add @owlmeans/kluster
81
16
  ```
82
17
 
83
- #### `klusterize<C extends KlusterConfig, T extends BasicContext<C>>(context: T, alias?: string): T`
84
-
85
- Enhances a context with Kubernetes capabilities and registers necessary middleware.
86
-
87
- **Parameters:**
88
- - `context`: Application context to enhance
89
- - `alias`: Service alias (defaults to 'kluster')
18
+ ## Usage
90
19
 
91
- **Returns:** Enhanced context with Kubernetes support
20
+ Register in server context setup:
92
21
 
93
22
  ```typescript
94
- import { klusterize } from '@owlmeans/kluster'
95
- import { makeServerContext } from '@owlmeans/server-context'
23
+ import { klusterize, DEFAULT_ALIAS as KLUSTER_SERVICE } from '@owlmeans/kluster'
24
+ import type { KlusterService } from '@owlmeans/kluster'
96
25
 
97
- const context = makeServerContext(config)
98
- const k8sContext = klusterize(context)
99
- ```
100
-
101
- #### `createMiddleware(alias?: string): Middleware`
102
-
103
- Creates middleware for processing Kubernetes configuration directives.
104
-
105
- **Parameters:**
106
- - `alias`: Service alias (defaults to 'kluster')
107
-
108
- **Returns:** Middleware instance
26
+ klusterize<C, T>(context)
109
27
 
110
- ```typescript
111
- import { createMiddleware } from '@owlmeans/kluster'
112
-
113
- const middleware = createMiddleware('k8s-client')
114
- context.registerMiddleware(middleware)
28
+ // Access in context type
29
+ context.kluster = () => context.service<KlusterService>(KLUSTER_SERVICE)
115
30
  ```
116
31
 
117
- ### Service Methods
118
-
119
- #### `getHostnames(selector: string): Promise<string[]>`
120
-
121
- Retrieves pod IP addresses using a label selector.
122
-
123
- **Parameters:**
124
- - `selector`: Kubernetes label selector
125
-
126
- **Returns:** Promise resolving to array of pod IP addresses
127
-
128
- ```typescript
129
- const klusterService = context.service<KlusterService>('kluster')
130
- const podIPs = await klusterService.getHostnames('app=redis,tier=cache')
131
- ```
132
-
133
- #### `getServiceHostname(selector: string): Promise<string>`
134
-
135
- Retrieves service hostname using a label selector.
136
-
137
- **Parameters:**
138
- - `selector`: Kubernetes label selector
139
-
140
- **Returns:** Promise resolving to service hostname
141
-
142
- ```typescript
143
- const serviceName = await klusterService.getServiceHostname('app=database')
144
- ```
145
-
146
- #### `dispatch<T>(action: string, query: string): Promise<T>`
147
-
148
- Dispatches actions for configuration resolution.
149
-
150
- **Parameters:**
151
- - `action`: Action type ('hostname' or 'service')
152
- - `query`: Query string (label selector or config path)
153
-
154
- **Returns:** Promise resolving to action result
155
-
156
- ```typescript
157
- const result = await klusterService.dispatch('service', 'app=api-gateway')
158
- ```
159
-
160
- #### `makeNetworkingApi(): NetworkingV1Api`
161
-
162
- Creates a Kubernetes Networking API client.
163
-
164
- **Returns:** NetworkingV1Api instance
165
-
166
- ```typescript
167
- const networkingApi = klusterService.makeNetworkingApi()
168
- const ingresses = await networkingApi.listIngressForAllNamespaces()
169
- ```
170
-
171
- ### Constants
172
-
173
- #### Service Configuration
174
- ```typescript
175
- const DEFAULT_ALIAS = 'kluster'
176
- const DEFAULT_NAMESPACE = 'default'
177
- ```
178
-
179
- #### Configuration Directives
180
- ```typescript
181
- const DIRECTIVE = 'kluster'
182
- const SEP = ':'
183
- const ACT_HOST = 'hostname'
184
- const ACT_SERVICE = 'service'
185
- ```
186
-
187
- ## Usage Examples
188
-
189
- ### Basic Kubernetes Integration
190
-
191
- ```typescript
192
- import { klusterize } from '@owlmeans/kluster'
193
- import { makeServerContext, makeServerConfig } from '@owlmeans/server-context'
194
-
195
- // Create server configuration with Kubernetes settings
196
- const config = makeServerConfig('k8s-app', {
197
- kluster: {
198
- namespace: 'production'
199
- },
200
- port: 8080
201
- })
202
-
203
- // Create and enhance context with Kubernetes capabilities
204
- const context = makeServerContext(config)
205
- const k8sContext = klusterize(context)
206
-
207
- await k8sContext.configure().init()
208
- ```
32
+ Config values can reference Kubernetes selectors:
209
33
 
210
- ### Service Discovery
211
-
212
- ```typescript
213
- import { KlusterService } from '@owlmeans/kluster'
214
-
215
- // Get Kubernetes service
216
- const klusterService = context.service<KlusterService>('kluster')
217
-
218
- // Discover database service
219
- const dbServiceName = await klusterService.getServiceHostname('app=postgres,tier=database')
220
- console.log('Database service:', dbServiceName)
221
-
222
- // Get all Redis cache pod IPs
223
- const redisIPs = await klusterService.getHostnames('app=redis,tier=cache')
224
- console.log('Redis pods:', redisIPs)
225
-
226
- // Connect to discovered service
227
- const dbConnection = createConnection({
228
- host: dbServiceName,
229
- port: 5432,
230
- database: 'myapp'
231
- })
232
- ```
233
-
234
- ### Configuration Directives
235
-
236
- ```typescript
237
- // Configuration with Kubernetes directives
238
- const config = makeServerConfig('microservice', {
239
- kluster: {
240
- namespace: 'production'
241
- },
242
- services: {
243
- // These will be resolved at runtime using Kubernetes API
244
- database: 'kluster:service:app=postgres',
245
- cache: 'kluster:service:app=redis',
246
- messageQueue: 'kluster:service:app=rabbitmq'
247
- },
248
- externalServices: {
249
- // Resolve to actual pod IPs for load balancing
250
- apiNodes: 'kluster:hostname:app=api,tier=backend'
251
- }
252
- })
253
-
254
- // After context initialization, directives are resolved
255
- await context.configure().init()
256
-
257
- // Now services contain actual Kubernetes service names
258
- console.log(context.cfg.services.database) // e.g., "postgres-service"
259
- console.log(context.cfg.externalServices.apiNodes) // e.g., ["10.1.1.5", "10.1.1.6"]
260
- ```
261
-
262
- ### Microservice Communication
263
-
264
- ```typescript
265
- import { klusterize, KlusterService } from '@owlmeans/kluster'
266
-
267
- // Setup microservice with Kubernetes discovery
268
- const config = makeServerConfig('user-service', {
269
- kluster: { namespace: 'microservices' },
270
- downstream: {
271
- authService: 'kluster:service:app=auth-service',
272
- emailService: 'kluster:service:app=email-service',
273
- notificationService: 'kluster:service:app=notification-service'
274
- }
275
- })
276
-
277
- const context = klusterize(makeServerContext(config))
278
- await context.configure().init()
279
-
280
- // Use discovered services
281
- async function createUser(userData: UserData) {
282
- // Call authentication service
283
- const authResponse = await fetch(`http://${context.cfg.downstream.authService}/validate`, {
284
- method: 'POST',
285
- headers: { 'Content-Type': 'application/json' },
286
- body: JSON.stringify(userData)
287
- })
288
-
289
- if (authResponse.ok) {
290
- const user = await saveUser(userData)
291
-
292
- // Send welcome email
293
- await fetch(`http://${context.cfg.downstream.emailService}/send`, {
294
- method: 'POST',
295
- body: JSON.stringify({
296
- to: user.email,
297
- template: 'welcome',
298
- data: { userName: user.name }
299
- })
300
- })
301
-
302
- return user
303
- }
304
-
305
- throw new Error('Authentication failed')
306
- }
307
- ```
308
-
309
- ### Load Balancing with Pod Discovery
310
-
311
- ```typescript
312
- import { KlusterService } from '@owlmeans/kluster'
313
-
314
- class LoadBalancedClient {
315
- private klusterService: KlusterService
316
- private endpoints: string[] = []
317
- private currentIndex = 0
318
-
319
- constructor(klusterService: KlusterService, private selector: string) {
320
- this.klusterService = klusterService
321
- this.refreshEndpoints()
322
- }
323
-
324
- private async refreshEndpoints() {
325
- try {
326
- this.endpoints = await this.klusterService.getHostnames(this.selector)
327
- console.log(`Discovered ${this.endpoints.length} endpoints`)
328
- } catch (error) {
329
- console.error('Failed to refresh endpoints:', error)
330
- }
331
- }
332
-
333
- private getNextEndpoint(): string {
334
- if (this.endpoints.length === 0) {
335
- throw new Error('No available endpoints')
336
- }
337
-
338
- const endpoint = this.endpoints[this.currentIndex]
339
- this.currentIndex = (this.currentIndex + 1) % this.endpoints.length
340
- return endpoint
341
- }
342
-
343
- async makeRequest(path: string, options: RequestInit = {}) {
344
- const endpoint = this.getNextEndpoint()
345
- const url = `http://${endpoint}:8080${path}`
346
-
347
- try {
348
- const response = await fetch(url, options)
349
- return response
350
- } catch (error) {
351
- console.error(`Request to ${endpoint} failed:`, error)
352
- // Refresh endpoints and retry
353
- await this.refreshEndpoints()
354
- throw error
355
- }
356
- }
357
- }
358
-
359
- // Usage
360
- const klusterService = context.service<KlusterService>('kluster')
361
- const apiClient = new LoadBalancedClient(klusterService, 'app=api,tier=backend')
362
-
363
- const response = await apiClient.makeRequest('/health')
364
- ```
365
-
366
- ### Health Monitoring
367
-
368
- ```typescript
369
- import { KlusterService } from '@owlmeans/kluster'
370
-
371
- class HealthMonitor {
372
- constructor(
373
- private klusterService: KlusterService,
374
- private services: Record<string, string>
375
- ) {}
376
-
377
- async checkServiceHealth(): Promise<Record<string, boolean>> {
378
- const healthStatus: Record<string, boolean> = {}
379
-
380
- for (const [serviceName, selector] of Object.entries(this.services)) {
381
- try {
382
- const serviceHost = await this.klusterService.getServiceHostname(selector)
383
- const response = await fetch(`http://${serviceHost}/health`, {
384
- timeout: 5000
385
- })
386
- healthStatus[serviceName] = response.ok
387
- } catch (error) {
388
- console.error(`Health check failed for ${serviceName}:`, error)
389
- healthStatus[serviceName] = false
390
- }
391
- }
392
-
393
- return healthStatus
394
- }
395
-
396
- async checkPodHealth(): Promise<Record<string, number>> {
397
- const podCounts: Record<string, number> = {}
398
-
399
- for (const [serviceName, selector] of Object.entries(this.services)) {
400
- try {
401
- const podIPs = await this.klusterService.getHostnames(selector)
402
- podCounts[serviceName] = podIPs.length
403
- } catch (error) {
404
- console.error(`Pod discovery failed for ${serviceName}:`, error)
405
- podCounts[serviceName] = 0
406
- }
407
- }
408
-
409
- return podCounts
410
- }
411
- }
412
-
413
- // Setup health monitoring
414
- const healthMonitor = new HealthMonitor(klusterService, {
415
- 'database': 'app=postgres',
416
- 'cache': 'app=redis',
417
- 'api': 'app=api,tier=backend'
418
- })
419
-
420
- // Monitor health every 30 seconds
421
- setInterval(async () => {
422
- const serviceHealth = await healthMonitor.checkServiceHealth()
423
- const podCounts = await healthMonitor.checkPodHealth()
424
-
425
- console.log('Service Health:', serviceHealth)
426
- console.log('Pod Counts:', podCounts)
427
- }, 30000)
428
- ```
429
-
430
- ### Ingress Management
431
-
432
- ```typescript
433
- import { KlusterService } from '@owlmeans/kluster'
434
-
435
- async function manageIngress(klusterService: KlusterService) {
436
- const networkingApi = klusterService.makeNetworkingApi()
437
-
438
- // List all ingresses
439
- const ingresses = await networkingApi.listIngressForAllNamespaces()
440
- console.log('Available ingresses:', ingresses.body.items.map(i => i.metadata?.name))
441
-
442
- // Create new ingress
443
- const ingressManifest = {
444
- apiVersion: 'networking.k8s.io/v1',
445
- kind: 'Ingress',
446
- metadata: {
447
- name: 'api-ingress',
448
- namespace: 'production'
449
- },
450
- spec: {
451
- rules: [{
452
- host: 'api.example.com',
453
- http: {
454
- paths: [{
455
- path: '/',
456
- pathType: 'Prefix',
457
- backend: {
458
- service: {
459
- name: 'api-service',
460
- port: { number: 80 }
461
- }
462
- }
463
- }]
464
- }
465
- }]
466
- }
467
- }
468
-
469
- try {
470
- await networkingApi.createNamespacedIngress('production', ingressManifest)
471
- console.log('Ingress created successfully')
472
- } catch (error) {
473
- console.error('Failed to create ingress:', error)
34
+ ```json
35
+ {
36
+ "dbs": {
37
+ "mongo": { "url": "kluster:app=mongo-svc" }
474
38
  }
475
39
  }
476
40
  ```
477
41
 
478
- ### Configuration File Resolution
479
-
480
- ```typescript
481
- // config.yaml
482
- const yamlConfig = `
483
- database:
484
- host: kluster:service:app=postgres
485
- port: 5432
486
- replicas: kluster:hostname:app=postgres
487
-
488
- cache:
489
- primary: kluster:service:app=redis-primary
490
- replicas: kluster:hostname:app=redis-replica
42
+ The middleware resolves `kluster:` prefixed values to real cluster IPs at startup.
491
43
 
492
- services:
493
- auth: kluster:service:app=auth-service
494
- notification: kluster:service:app=notification-service
495
- `
44
+ ## API
496
45
 
497
- // Load and process configuration
498
- import yaml from 'yaml'
46
+ ### `makeKlusterService(alias?): KlusterService`
499
47
 
500
- const config = yaml.parse(yamlConfig)
501
- const context = klusterize(makeServerContext(config))
48
+ Creates the Kubernetes service. Reads in-cluster config or `~/.kube/config`.
502
49
 
503
- // After initialization, all kluster directives are resolved
504
- await context.configure().init()
505
-
506
- console.log('Resolved configuration:', context.cfg)
507
- // Output:
508
- // {
509
- // database: { host: 'postgres-service', port: 5432, replicas: ['10.1.1.10', '10.1.1.11'] },
510
- // cache: { primary: 'redis-primary-service', replicas: ['10.1.2.20', '10.1.2.21'] },
511
- // services: { auth: 'auth-service', notification: 'notification-service' }
512
- // }
513
- ```
514
-
515
- ## Error Handling
516
-
517
- ```typescript
518
- import { KlusterService } from '@owlmeans/kluster'
50
+ ### `klusterize<C, T>(context, alias?): T`
519
51
 
520
- async function robustServiceDiscovery(klusterService: KlusterService) {
521
- try {
522
- // Attempt service discovery
523
- const serviceName = await klusterService.getServiceHostname('app=api')
524
- return serviceName
525
- } catch (error) {
526
- console.error('Service discovery failed:', error)
527
-
528
- // Fallback to default service name
529
- return 'api-service'
530
- }
531
- }
532
-
533
- // Handle network errors gracefully
534
- async function resilientPodDiscovery(klusterService: KlusterService, selector: string) {
535
- const maxRetries = 3
536
- let retries = 0
537
-
538
- while (retries < maxRetries) {
539
- try {
540
- const pods = await klusterService.getHostnames(selector)
541
- if (pods.length > 0) {
542
- return pods
543
- }
544
- throw new Error('No pods found')
545
- } catch (error) {
546
- retries++
547
- console.warn(`Pod discovery attempt ${retries} failed:`, error.message)
548
-
549
- if (retries < maxRetries) {
550
- // Exponential backoff
551
- await new Promise(resolve => setTimeout(resolve, Math.pow(2, retries) * 1000))
552
- }
553
- }
554
- }
555
-
556
- throw new Error(`Failed to discover pods after ${maxRetries} attempts`)
557
- }
558
- ```
559
-
560
- ## Integration Patterns
561
-
562
- ### Deployment Configuration
563
-
564
- ```yaml
565
- # kubernetes-config.yaml
566
- apiVersion: v1
567
- kind: ConfigMap
568
- metadata:
569
- name: app-config
570
- namespace: production
571
- data:
572
- config.json: |
573
- {
574
- "database": "kluster:service:app=postgres",
575
- "cache": "kluster:service:app=redis",
576
- "messageQueue": "kluster:service:app=rabbitmq"
577
- }
578
- ```
579
-
580
- ```typescript
581
- // Application code
582
- import { klusterize } from '@owlmeans/kluster'
583
-
584
- const config = makeServerConfig('app', {
585
- kluster: { namespace: 'production' },
586
- // Configuration loaded from ConfigMap
587
- database: 'kluster:service:app=postgres',
588
- cache: 'kluster:service:app=redis'
589
- })
590
-
591
- const context = klusterize(makeServerContext(config))
592
- ```
593
-
594
- ### Helm Chart Integration
595
-
596
- ```yaml
597
- # values.yaml
598
- app:
599
- namespace: {{ .Values.namespace }}
600
- services:
601
- database: "kluster:service:app=postgres"
602
- cache: "kluster:service:app=redis"
603
-
604
- # deployment.yaml
605
- apiVersion: apps/v1
606
- kind: Deployment
607
- spec:
608
- template:
609
- spec:
610
- containers:
611
- - name: app
612
- env:
613
- - name: KLUSTER_NAMESPACE
614
- value: {{ .Values.namespace }}
615
- ```
52
+ Registers the kluster service and a middleware that resolves `kluster:` directives in config.
616
53
 
617
- ## Best Practices
54
+ ### `KlusterService`
618
55
 
619
- 1. **Namespace Management**: Always specify appropriate namespaces for service discovery
620
- 2. **Label Consistency**: Use consistent labeling conventions across services
621
- 3. **Error Handling**: Implement robust error handling for network operations
622
- 4. **Caching**: Consider caching service discovery results to reduce API calls
623
- 5. **Security**: Ensure proper RBAC permissions for Kubernetes API access
624
- 6. **Monitoring**: Monitor service discovery operations and cluster health
625
- 7. **Fallbacks**: Provide fallback mechanisms when service discovery fails
56
+ - `getHostnames(selector, namespace?): Promise<string[]>` — list pod IPs matching a label selector
57
+ - `getServiceHostname(selector, namespace?): Promise<string | null>` — get a service's cluster IP
58
+ - `dispatch<T>(action, query): Promise<T>` — dispatch a kluster directive query
626
59
 
627
- ## Dependencies
60
+ ### `KlusterConfig`
628
61
 
629
- This package depends on:
630
- - `@owlmeans/config` - Configuration management
631
- - `@owlmeans/context` - Context management system
632
- - `@owlmeans/server-config` - Server configuration utilities
633
- - `@owlmeans/server-context` - Server context management
634
- - `@kubernetes/client-node` - Official Kubernetes client library
62
+ Extends `ServerConfig` with optional `namespace: string`.
635
63
 
636
64
  ## Related Packages
637
65
 
638
- - [`@owlmeans/server-app`](../server-app) - Server application framework
639
- - [`@owlmeans/server-context`](../server-context) - Server context management
640
- - [`@owlmeans/config`](../config) - Configuration management system
66
+ - [`@owlmeans/server-app`](../server-app) — `makeContext` where `klusterize` is typically called
67
+ - [`@owlmeans/mongo`](../mongo) — MongoDB URL can be a `kluster:` directive
package/build/consts.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export declare const KLUSTER_SERVICE_ALIAS = "kluster";
1
2
  export declare const DEFAULT_ALIAS = "kluster";
2
3
  export declare const DIRECTIVE = "kluster";
3
4
  export declare const SEP = ":";
@@ -1 +1 @@
1
- {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,aAAa,YAAY,CAAA;AAEtC,eAAO,MAAM,SAAS,YAAY,CAAA;AAElC,eAAO,MAAM,GAAG,MAAM,CAAA;AAEtB,eAAO,MAAM,iBAAiB,YAAY,CAAA;AAE1C,eAAO,MAAM,QAAQ,aAAa,CAAA;AAElC,eAAO,MAAM,WAAW,YAAY,CAAA"}
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,qBAAqB,YAAY,CAAA;AAE9C,eAAO,MAAM,aAAa,YAAwB,CAAA;AAElD,eAAO,MAAM,SAAS,YAAY,CAAA;AAElC,eAAO,MAAM,GAAG,MAAM,CAAA;AAEtB,eAAO,MAAM,iBAAiB,YAAY,CAAA;AAE1C,eAAO,MAAM,QAAQ,aAAa,CAAA;AAElC,eAAO,MAAM,WAAW,YAAY,CAAA"}
package/build/consts.js CHANGED
@@ -1,4 +1,5 @@
1
- export const DEFAULT_ALIAS = 'kluster';
1
+ export const KLUSTER_SERVICE_ALIAS = 'kluster';
2
+ export const DEFAULT_ALIAS = KLUSTER_SERVICE_ALIAS;
2
3
  export const DIRECTIVE = 'kluster';
3
4
  export const SEP = ':';
4
5
  export const DEFAULT_NAMESPACE = 'default';
@@ -1 +1 @@
1
- {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,aAAa,GAAG,SAAS,CAAA;AAEtC,MAAM,CAAC,MAAM,SAAS,GAAG,SAAS,CAAA;AAElC,MAAM,CAAC,MAAM,GAAG,GAAG,GAAG,CAAA;AAEtB,MAAM,CAAC,MAAM,iBAAiB,GAAG,SAAS,CAAA;AAE1C,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAA;AAElC,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,CAAA"}
1
+ {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,qBAAqB,GAAG,SAAS,CAAA;AAE9C,MAAM,CAAC,MAAM,aAAa,GAAG,qBAAqB,CAAA;AAElD,MAAM,CAAC,MAAM,SAAS,GAAG,SAAS,CAAA;AAElC,MAAM,CAAC,MAAM,GAAG,GAAG,GAAG,CAAA;AAEtB,MAAM,CAAC,MAAM,iBAAiB,GAAG,SAAS,CAAA;AAE1C,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAA;AAElC,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,CAAA"}
@@ -1,3 +1,4 @@
1
1
  import type { KlusterService } from './types.js';
2
+ export declare const isNotFoundError: (e: unknown) => boolean;
2
3
  export declare const makeKlusterService: (alias?: string) => KlusterService;
3
4
  //# sourceMappingURL=service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAiB,cAAc,EAAE,MAAM,YAAY,CAAA;AAS/D,eAAO,MAAM,kBAAkB,GAAI,QAAO,MAAsB,KAAG,cAyElE,CAAA"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAiB,cAAc,EAAE,MAAM,YAAY,CAAA;AAS/D,eAAO,MAAM,eAAe,GAAI,GAAG,OAAO,KAAG,OAa5C,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,QAAO,MAAsB,KAAG,cAqElE,CAAA"}
package/build/service.js CHANGED
@@ -1,21 +1,40 @@
1
1
  import { ACT_HOST, ACT_SERVICE, DEFAULT_ALIAS, DEFAULT_NAMESPACE } from './consts.js';
2
2
  import { assertContext, createLazyService } from '@owlmeans/context';
3
- import { KubeConfig, CoreV1Api, HttpError, NetworkingV1Api } from '@kubernetes/client-node';
3
+ import { KubeConfig, CoreV1Api, ApiException, NetworkingV1Api, AppsV1Api } from '@kubernetes/client-node';
4
4
  import { readConfigValue } from '@owlmeans/server-config';
5
+ export const isNotFoundError = (e) => {
6
+ if (e == null || typeof e !== 'object')
7
+ return false;
8
+ const err = e;
9
+ // 1.4.0 ApiException uses .code
10
+ if (err['code'] === 404)
11
+ return true;
12
+ // 0.22.x HttpError used .statusCode
13
+ if (err['statusCode'] === 404)
14
+ return true;
15
+ // Some shapes nest status in .response or .body
16
+ const body = err['body'];
17
+ if (body?.['code'] === 404)
18
+ return true;
19
+ const response = err['response'];
20
+ if (response?.['statusCode'] === 404)
21
+ return true;
22
+ return false;
23
+ };
5
24
  export const makeKlusterService = (alias = DEFAULT_ALIAS) => {
6
25
  const location = `kluster:${alias}`;
7
26
  const updateSelector = (selector) => selector.startsWith('/') || selector.startsWith('file:')
8
27
  ? readConfigValue(selector, selector) : selector;
9
28
  const service = createLazyService(alias, {
10
- getHostnames: async (selector) => {
29
+ getHostnames: async (selector, namespace) => {
11
30
  try {
12
31
  const ctx = assertContext(service.ctx, location);
13
- const namespace = ctx.cfg.kluster?.namespace ?? DEFAULT_NAMESPACE;
14
- const { body } = await service.api.listNamespacedPod(namespace, undefined, undefined, undefined, undefined, selector);
15
- return body.items.map(item => item.status?.podIP).filter(name => name != null);
32
+ namespace ??= ctx.cfg.kluster?.namespace ?? DEFAULT_NAMESPACE;
33
+ const result = await service.api.listNamespacedPod({ namespace, labelSelector: selector });
34
+ return result.items.map(item => item.status?.podIP).filter(name => name != null);
16
35
  }
17
36
  catch (e) {
18
- if (e instanceof HttpError) {
37
+ if (e instanceof ApiException) {
19
38
  console.error(e.name, e.cause, e.message, e.body);
20
39
  }
21
40
  else {
@@ -24,18 +43,15 @@ export const makeKlusterService = (alias = DEFAULT_ALIAS) => {
24
43
  return [];
25
44
  }
26
45
  },
27
- getServiceHostname: async (selector) => {
46
+ getServiceHostname: async (selector, namespace) => {
28
47
  try {
29
48
  const ctx = assertContext(service.ctx, location);
30
- const namespace = ctx.cfg.kluster?.namespace ?? DEFAULT_NAMESPACE;
31
- const { body } = await service.api.listNamespacedService(namespace, undefined, undefined, undefined, undefined, selector);
32
- if (body.items[0] == null) {
33
- return selector;
34
- }
35
- return body.items[0].metadata?.name ?? selector;
49
+ namespace ??= ctx.cfg.kluster?.namespace ?? DEFAULT_NAMESPACE;
50
+ const result = await service.api.listNamespacedService({ namespace, labelSelector: selector });
51
+ return result.items?.[0]?.spec?.clusterIP ?? null;
36
52
  }
37
53
  catch (e) {
38
- if (e instanceof HttpError) {
54
+ if (e instanceof ApiException) {
39
55
  console.error(e.name, e.cause, e.message, e.body);
40
56
  }
41
57
  else {
@@ -54,7 +70,8 @@ export const makeKlusterService = (alias = DEFAULT_ALIAS) => {
54
70
  }
55
71
  throw new SyntaxError(`Unknown kluster directive: ${action}`);
56
72
  },
57
- makeNetworkingApi: () => service.config.makeApiClient(NetworkingV1Api)
73
+ makeNetworkingApi: () => service.config.makeApiClient(NetworkingV1Api),
74
+ makeAppsApi: () => service.config.makeApiClient(AppsV1Api),
58
75
  }, service => async () => {
59
76
  if (service.config == null || service.api == null) {
60
77
  service.config = new KubeConfig();
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
package/build/types.d.ts CHANGED
@@ -1,13 +1,14 @@
1
1
  import type { LazyService } from '@owlmeans/context';
2
- import type { KubeConfig, CoreV1Api, NetworkingV1Api } from '@kubernetes/client-node';
2
+ import type { KubeConfig, CoreV1Api, NetworkingV1Api, AppsV1Api } from '@kubernetes/client-node';
3
3
  import type { ServerConfig } from '@owlmeans/server-context';
4
4
  export interface KlusterService extends LazyService {
5
5
  config?: KubeConfig;
6
6
  api?: CoreV1Api;
7
- getHostnames: (selector: string) => Promise<string[]>;
8
- getServiceHostname: (selector: string) => Promise<string>;
7
+ getHostnames: (selector: string, namespace?: string) => Promise<string[]>;
8
+ getServiceHostname: (selector: string, namespace?: string) => Promise<string | null>;
9
9
  dispatch: <T>(action: string, query: string) => Promise<T>;
10
10
  makeNetworkingApi: () => NetworkingV1Api;
11
+ makeAppsApi: () => AppsV1Api;
11
12
  }
12
13
  export interface KlusterConfig extends ServerConfig {
13
14
  kluster?: {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACrF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAE5D,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,GAAG,CAAC,EAAE,SAAS,CAAA;IAEf,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IAErD,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAEzD,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;IAE1D,iBAAiB,EAAE,MAAM,eAAe,CAAA;CACzC;AAED,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,OAAO,CAAC,EAAE;QACR,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,CAAA;CACF"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAChG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAE5D,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,GAAG,CAAC,EAAE,SAAS,CAAA;IAEf,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IAGzE,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAEpF,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;IAE1D,iBAAiB,EAAE,MAAM,eAAe,CAAA;IAExC,WAAW,EAAE,MAAM,SAAS,CAAA;CAC7B;AAED,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,OAAO,CAAC,EAAE;QACR,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,CAAA;CACF"}
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@owlmeans/kluster",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
+ "license": "MIT",
4
5
  "type": "module",
5
6
  "scripts": {
6
7
  "build": "tsc -b",
@@ -20,16 +21,17 @@
20
21
  }
21
22
  },
22
23
  "dependencies": {
23
- "@kubernetes/client-node": "^0.22.0",
24
- "@owlmeans/config": "^0.1.1",
25
- "@owlmeans/context": "^0.1.1",
26
- "@owlmeans/server-config": "^0.1.1",
27
- "@owlmeans/server-context": "^0.1.1"
24
+ "@kubernetes/client-node": "^1.4.0",
25
+ "@owlmeans/config": "^0.1.3",
26
+ "@owlmeans/context": "^0.1.3",
27
+ "@owlmeans/server-config": "^0.1.3",
28
+ "@owlmeans/server-context": "^0.1.3"
28
29
  },
29
30
  "devDependencies": {
31
+ "@owlmeans/dep-config": "workspace:*",
30
32
  "@types/node": "^24.10.1",
31
33
  "nodemon": "^3.1.11",
32
- "typescript": "^5.8.3"
34
+ "typescript": "^6.0.2"
33
35
  },
34
36
  "publishConfig": {
35
37
  "access": "public"
package/src/consts.ts CHANGED
@@ -1,5 +1,7 @@
1
1
 
2
- export const DEFAULT_ALIAS = 'kluster'
2
+ export const KLUSTER_SERVICE_ALIAS = 'kluster'
3
+
4
+ export const DEFAULT_ALIAS = KLUSTER_SERVICE_ALIAS
3
5
 
4
6
  export const DIRECTIVE = 'kluster'
5
7
 
package/src/service.ts CHANGED
@@ -1,13 +1,28 @@
1
1
  import { ACT_HOST, ACT_SERVICE, DEFAULT_ALIAS, DEFAULT_NAMESPACE } from './consts.js'
2
2
  import type { KlusterConfig, KlusterService } from './types.js'
3
3
  import { assertContext, createLazyService } from '@owlmeans/context'
4
- import { KubeConfig, CoreV1Api, HttpError, NetworkingV1Api } from '@kubernetes/client-node'
4
+ import { KubeConfig, CoreV1Api, ApiException, NetworkingV1Api, AppsV1Api } from '@kubernetes/client-node'
5
5
  import { ServerContext } from '@owlmeans/server-context'
6
6
  import { readConfigValue } from '@owlmeans/server-config'
7
7
 
8
8
  type Config = KlusterConfig
9
9
  type Context = ServerContext<Config>
10
10
 
11
+ export const isNotFoundError = (e: unknown): boolean => {
12
+ if (e == null || typeof e !== 'object') return false
13
+ const err = e as Record<string, unknown>
14
+ // 1.4.0 ApiException uses .code
15
+ if (err['code'] === 404) return true
16
+ // 0.22.x HttpError used .statusCode
17
+ if (err['statusCode'] === 404) return true
18
+ // Some shapes nest status in .response or .body
19
+ const body = err['body'] as Record<string, unknown> | undefined
20
+ if (body?.['code'] === 404) return true
21
+ const response = err['response'] as Record<string, unknown> | undefined
22
+ if (response?.['statusCode'] === 404) return true
23
+ return false
24
+ }
25
+
11
26
  export const makeKlusterService = (alias: string = DEFAULT_ALIAS): KlusterService => {
12
27
  const location = `kluster:${alias}`
13
28
 
@@ -16,18 +31,16 @@ export const makeKlusterService = (alias: string = DEFAULT_ALIAS): KlusterServic
16
31
  ? readConfigValue(selector, selector) : selector
17
32
 
18
33
  const service: KlusterService = createLazyService<KlusterService>(alias, {
19
- getHostnames: async selector => {
34
+ getHostnames: async (selector, namespace) => {
20
35
  try {
21
36
  const ctx = assertContext<Config, Context>(service.ctx as Context, location)
22
- const namespace = ctx.cfg.kluster?.namespace ?? DEFAULT_NAMESPACE
37
+ namespace ??= ctx.cfg.kluster?.namespace ?? DEFAULT_NAMESPACE
23
38
 
24
- const { body } = await service.api!.listNamespacedPod(
25
- namespace, undefined, undefined, undefined, undefined, selector
26
- )
39
+ const result = await service.api!.listNamespacedPod({ namespace, labelSelector: selector })
27
40
 
28
- return body.items.map(item => item.status?.podIP).filter(name => name != null)
41
+ return result.items.map(item => item.status?.podIP).filter(name => name != null)
29
42
  } catch (e) {
30
- if (e instanceof HttpError) {
43
+ if (e instanceof ApiException) {
31
44
  console.error(e.name, e.cause, e.message, e.body)
32
45
  } else {
33
46
  throw e
@@ -36,20 +49,16 @@ export const makeKlusterService = (alias: string = DEFAULT_ALIAS): KlusterServic
36
49
  }
37
50
  },
38
51
 
39
- getServiceHostname: async selector => {
52
+ getServiceHostname: async (selector, namespace) => {
40
53
  try {
41
54
  const ctx = assertContext<Config, Context>(service.ctx as Context, location)
42
- const namespace = ctx.cfg.kluster?.namespace ?? DEFAULT_NAMESPACE
55
+ namespace ??= ctx.cfg.kluster?.namespace ?? DEFAULT_NAMESPACE
43
56
 
44
- const { body } = await service.api!.listNamespacedService(namespace, undefined, undefined, undefined, undefined, selector)
57
+ const result = await service.api!.listNamespacedService({ namespace, labelSelector: selector })
45
58
 
46
- if (body.items[0] == null) {
47
- return selector
48
- }
49
-
50
- return body.items[0].metadata?.name ?? selector
59
+ return result.items?.[0]?.spec?.clusterIP ?? null
51
60
  } catch (e) {
52
- if (e instanceof HttpError) {
61
+ if (e instanceof ApiException) {
53
62
  console.error(e.name, e.cause, e.message, e.body)
54
63
  } else {
55
64
  throw e
@@ -69,7 +78,9 @@ export const makeKlusterService = (alias: string = DEFAULT_ALIAS): KlusterServic
69
78
  throw new SyntaxError(`Unknown kluster directive: ${action}`)
70
79
  },
71
80
 
72
- makeNetworkingApi: () => service.config!.makeApiClient(NetworkingV1Api)
81
+ makeNetworkingApi: () => service.config!.makeApiClient(NetworkingV1Api),
82
+
83
+ makeAppsApi: () => service.config!.makeApiClient(AppsV1Api),
73
84
  }, service => async () => {
74
85
  if (service.config == null || service.api == null) {
75
86
  service.config = new KubeConfig()
package/src/types.ts CHANGED
@@ -1,18 +1,21 @@
1
1
  import type { LazyService } from '@owlmeans/context'
2
- import type { KubeConfig, CoreV1Api, NetworkingV1Api } from '@kubernetes/client-node'
2
+ import type { KubeConfig, CoreV1Api, NetworkingV1Api, AppsV1Api } from '@kubernetes/client-node'
3
3
  import type { ServerConfig } from '@owlmeans/server-context'
4
4
 
5
5
  export interface KlusterService extends LazyService {
6
6
  config?: KubeConfig
7
7
  api?: CoreV1Api
8
8
 
9
- getHostnames: (selector: string) => Promise<string[]>
9
+ getHostnames: (selector: string, namespace?: string) => Promise<string[]>
10
10
 
11
- getServiceHostname: (selector: string) => Promise<string>
11
+ // It should also return the array of cluster ips
12
+ getServiceHostname: (selector: string, namespace?: string) => Promise<string | null>
12
13
 
13
14
  dispatch: <T>(action: string, query: string) => Promise<T>
14
15
 
15
16
  makeNetworkingApi: () => NetworkingV1Api
17
+
18
+ makeAppsApi: () => AppsV1Api
16
19
  }
17
20
 
18
21
  export interface KlusterConfig extends ServerConfig {
package/tsconfig.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "extends": [
3
- "../tsconfig.default.json",
3
+ "@owlmeans/dep-config/tsconfig.base.json",
4
+ "@owlmeans/dep-config/tsconfig.node.json"
4
5
  ],
5
6
  "compilerOptions": {
6
- "rootDir": "./src/", /* Specify the root folder within your source files. */
7
- "outDir": "./build/", /* Specify an output folder for all emitted files. */
8
- "moduleResolution": "Bundler"
7
+ "rootDir": "./src/",
8
+ "outDir": "./build/"
9
9
  },
10
10
  "exclude": [
11
11
  "./dist/**/*",
12
12
  "./build/**/*",
13
13
  "./*.ts"
14
14
  ]
15
- }
15
+ }