@digifi/k8s-grpc-js-resolver 0.1.0

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.
Files changed (47) hide show
  1. package/LICENSE +14 -0
  2. package/README.md +98 -0
  3. package/dist/dns-fallback.d.ts +20 -0
  4. package/dist/dns-fallback.d.ts.map +1 -0
  5. package/dist/dns-fallback.js +84 -0
  6. package/dist/dns-fallback.js.map +1 -0
  7. package/dist/endpoint-slice-store.d.ts +81 -0
  8. package/dist/endpoint-slice-store.d.ts.map +1 -0
  9. package/dist/endpoint-slice-store.js +201 -0
  10. package/dist/endpoint-slice-store.js.map +1 -0
  11. package/dist/index.d.ts +4 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +17 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/k8s-api-client.d.ts +18 -0
  16. package/dist/k8s-api-client.d.ts.map +1 -0
  17. package/dist/k8s-api-client.js +125 -0
  18. package/dist/k8s-api-client.js.map +1 -0
  19. package/dist/k8s-endpoint-watch.d.ts +4 -0
  20. package/dist/k8s-endpoint-watch.d.ts.map +1 -0
  21. package/dist/k8s-endpoint-watch.js +194 -0
  22. package/dist/k8s-endpoint-watch.js.map +1 -0
  23. package/dist/k8s-resolver.d.ts +43 -0
  24. package/dist/k8s-resolver.d.ts.map +1 -0
  25. package/dist/k8s-resolver.js +230 -0
  26. package/dist/k8s-resolver.js.map +1 -0
  27. package/dist/resolve-grpc-target-address.d.ts +8 -0
  28. package/dist/resolve-grpc-target-address.d.ts.map +1 -0
  29. package/dist/resolve-grpc-target-address.js +34 -0
  30. package/dist/resolve-grpc-target-address.js.map +1 -0
  31. package/dist/resolver-logger.d.ts +13 -0
  32. package/dist/resolver-logger.d.ts.map +1 -0
  33. package/dist/resolver-logger.js +41 -0
  34. package/dist/resolver-logger.js.map +1 -0
  35. package/dist/resolver.constants.d.ts +7 -0
  36. package/dist/resolver.constants.d.ts.map +1 -0
  37. package/dist/resolver.constants.js +17 -0
  38. package/dist/resolver.constants.js.map +1 -0
  39. package/dist/rewrite-grpc-address.d.ts +32 -0
  40. package/dist/rewrite-grpc-address.d.ts.map +1 -0
  41. package/dist/rewrite-grpc-address.js +96 -0
  42. package/dist/rewrite-grpc-address.js.map +1 -0
  43. package/dist/watch-stream-parser.d.ts +23 -0
  44. package/dist/watch-stream-parser.d.ts.map +1 -0
  45. package/dist/watch-stream-parser.js +51 -0
  46. package/dist/watch-stream-parser.js.map +1 -0
  47. package/package.json +55 -0
package/LICENSE ADDED
@@ -0,0 +1,14 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 DigiFi
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without
6
+ fee is hereby granted, provided that the above copyright notice and this permission notice appear
7
+ in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
10
+ SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
11
+ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
13
+ NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
14
+ OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # @digifi/k8s-grpc-js-resolver
2
+
3
+ A [`@grpc/grpc-js`](https://www.npmjs.com/package/@grpc/grpc-js) resolver that watches Kubernetes
4
+ EndpointSlices and pushes backend changes into grpc-js. It is intended for headless Services used
5
+ with client-side `round_robin` load balancing.
6
+
7
+ The stock DNS resolver may keep a subchannel for a terminating pod until the connection fails or
8
+ grpc-js asks DNS to resolve again. This package removes an endpoint as soon as Kubernetes reports
9
+ `ready: false`, `serving: false`, or `terminating: true`, and adds new endpoints on the watch event.
10
+
11
+ ## Install
12
+
13
+ ```sh
14
+ npm install @digifi/k8s-grpc-js-resolver @grpc/grpc-js
15
+ ```
16
+
17
+ The package declares grpc-js as a peer dependency so the application and resolver share the same
18
+ process-wide resolver registry.
19
+
20
+ ## Usage
21
+
22
+ ```ts
23
+ import { credentials } from '@grpc/grpc-js';
24
+ import { resolveGrpcTargetAddress } from '@digifi/k8s-grpc-js-resolver';
25
+
26
+ const target = resolveGrpcTargetAddress('digifi-users-headless:4004');
27
+ const client = new UsersClient(target, credentials.createInsecure(), {
28
+ 'grpc.service_config': JSON.stringify({
29
+ loadBalancingConfig: [{ round_robin: {} }],
30
+ }),
31
+ });
32
+ ```
33
+
34
+ Set `GRPC_K8S_RESOLVER_ENABLED=true` in the client workload. Recognized in-cluster Service
35
+ addresses are rewritten to `k8s://<namespace>/<service>:<port>`. External hosts, IP literals,
36
+ loopback addresses, and targets that already have another scheme are left unchanged.
37
+
38
+ Explicit `k8s://` targets register the resolver without requiring the feature flag:
39
+
40
+ ```ts
41
+ resolveGrpcTargetAddress('k8s://test4/digifi-users-headless:4004');
42
+ ```
43
+
44
+ ### Environment
45
+
46
+ | Variable | Default | Purpose |
47
+ | -------------------------------- | ------------------------------ | ------------------------------------------------------------- |
48
+ | `GRPC_K8S_RESOLVER_ENABLED` | `false` | Rewrites recognized in-cluster `host:port` targets. |
49
+ | `GRPC_K8S_RESOLVER_DNS_FALLBACK` | `true` | Uses headless DNS when initial EndpointSlice discovery fails. |
50
+ | `POD_NAMESPACE` | ServiceAccount namespace file | Overrides the caller pod namespace. |
51
+ | `KUBERNETES_CLUSTER_DOMAIN` | detected, then `cluster.local` | Overrides the cluster domain used by DNS fallback. |
52
+ | `LogLevel` | `info` | Controls resolver `error`, `warn`, and `info` output. |
53
+
54
+ DNS fallback is used only when EndpointSlice discovery is unavailable. A healthy watch reporting
55
+ zero ready endpoints publishes an empty resolution so grpc-js drops stale subchannels.
56
+
57
+ ## Kubernetes RBAC
58
+
59
+ The caller pod's ServiceAccount must be able to read EndpointSlices in its namespace:
60
+
61
+ ```yaml
62
+ apiVersion: rbac.authorization.k8s.io/v1
63
+ kind: Role
64
+ metadata:
65
+ name: grpc-endpointslice-reader
66
+ rules:
67
+ - apiGroups: [discovery.k8s.io]
68
+ resources: [endpointslices]
69
+ verbs: [get, list, watch]
70
+ ---
71
+ apiVersion: rbac.authorization.k8s.io/v1
72
+ kind: RoleBinding
73
+ metadata:
74
+ name: grpc-endpointslice-reader
75
+ roleRef:
76
+ apiGroup: rbac.authorization.k8s.io
77
+ kind: Role
78
+ name: grpc-endpointslice-reader
79
+ subjects:
80
+ - kind: ServiceAccount
81
+ name: default
82
+ namespace: test4
83
+ ```
84
+
85
+ Prefer a dedicated ServiceAccount in shared environments. The resolver keeps one watch per
86
+ namespace in each Node.js process and shares it across all gRPC targets in that namespace.
87
+
88
+ ## Development
89
+
90
+ ```sh
91
+ npm ci
92
+ npm run check
93
+ npm pack --dry-run
94
+ ```
95
+
96
+ Publishing is configured for the public `@digifi` scope on npm. Update `package.json` to the release
97
+ version, merge it, and publish a GitHub Release. The release workflow expects an `NPM_AUTH_TOKEN`
98
+ repository secret. A manual workflow dispatch can publish the same version under a custom npm tag.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * A pod's `search` list contains `svc.<cluster-domain>`, which is the only reliable in-cluster
3
+ * source for the domain. Falls back to the upstream default when running outside a cluster.
4
+ */
5
+ export declare const readClusterDomainFromSearchDomains: (searchDomains: string[]) => string | null;
6
+ export declare const readClusterDomain: () => string;
7
+ /**
8
+ * The trailing dot makes the name absolute. Without it a four-label name stays below the default
9
+ * `ndots:5` of a pod's resolv.conf and every lookup first walks the whole search list, costing
10
+ * three NXDOMAIN round trips before the real answer.
11
+ */
12
+ export declare const buildHeadlessServiceDnsName: (serviceName: string, namespace: string, clusterDomain?: string) => string;
13
+ /**
14
+ * Uses `resolve*` rather than `lookup`: `lookup` is `getaddrinfo`, which occupies a libuv
15
+ * threadpool slot shared with file and crypto work, and applies the search list. Both families are
16
+ * queried because that is what the headless DNS path this falls back to used to return.
17
+ */
18
+ export declare const resolveHeadlessDnsHosts: (serviceName: string, namespace: string) => Promise<string[]>;
19
+ export declare const resetClusterDomainCache: () => void;
20
+ //# sourceMappingURL=dns-fallback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dns-fallback.d.ts","sourceRoot":"","sources":["../src/dns-fallback.ts"],"names":[],"mappings":"AAYA;;;GAGG;AACH,eAAO,MAAM,kCAAkC,GAAI,eAAe,MAAM,EAAE,KAAG,MAAM,GAAG,IAUrF,CAAC;AAiBF,eAAO,MAAM,iBAAiB,QAAO,MASpC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,GACtC,aAAa,MAAM,EACnB,WAAW,MAAM,EACjB,gBAAe,MAA4B,KAC1C,MAA6D,CAAC;AAEjE;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAClC,aAAa,MAAM,EACnB,WAAW,MAAM,KAChB,OAAO,CAAC,MAAM,EAAE,CAmBlB,CAAC;AAEF,eAAO,MAAM,uBAAuB,QAAO,IAE1C,CAAC"}
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resetClusterDomainCache = exports.resolveHeadlessDnsHosts = exports.buildHeadlessServiceDnsName = exports.readClusterDomain = exports.readClusterDomainFromSearchDomains = void 0;
4
+ const node_fs_1 = require("node:fs");
5
+ const promises_1 = require("node:dns/promises");
6
+ const RESOLV_CONF_PATH = '/etc/resolv.conf';
7
+ const CLUSTER_DOMAIN_ENV = 'KUBERNETES_CLUSTER_DOMAIN';
8
+ const DEFAULT_CLUSTER_DOMAIN = 'cluster.local';
9
+ const SERVICE_SEARCH_DOMAIN_PATTERN = /^svc\.(.+)$/;
10
+ const DNS_TIMEOUT_MS = 5_000;
11
+ const DNS_ATTEMPTS = 2;
12
+ let cachedClusterDomain;
13
+ /**
14
+ * A pod's `search` list contains `svc.<cluster-domain>`, which is the only reliable in-cluster
15
+ * source for the domain. Falls back to the upstream default when running outside a cluster.
16
+ */
17
+ const readClusterDomainFromSearchDomains = (searchDomains) => {
18
+ for (const searchDomain of searchDomains) {
19
+ const match = SERVICE_SEARCH_DOMAIN_PATTERN.exec(searchDomain.replace(/\.$/, ''));
20
+ if (match) {
21
+ return match[1];
22
+ }
23
+ }
24
+ return null;
25
+ };
26
+ exports.readClusterDomainFromSearchDomains = readClusterDomainFromSearchDomains;
27
+ const readSearchDomains = () => {
28
+ try {
29
+ return (0, node_fs_1.readFileSync)(RESOLV_CONF_PATH, 'utf8')
30
+ .split('\n')
31
+ .flatMap((line) => {
32
+ const [directive, ...domains] = line.trim().split(/\s+/);
33
+ return directive === 'search' ? domains : [];
34
+ });
35
+ }
36
+ catch {
37
+ // Not running in-cluster, or resolv.conf is unreadable.
38
+ return [];
39
+ }
40
+ };
41
+ const readClusterDomain = () => {
42
+ if (cachedClusterDomain === undefined) {
43
+ cachedClusterDomain =
44
+ process.env[CLUSTER_DOMAIN_ENV] ||
45
+ (0, exports.readClusterDomainFromSearchDomains)(readSearchDomains()) ||
46
+ DEFAULT_CLUSTER_DOMAIN;
47
+ }
48
+ return cachedClusterDomain;
49
+ };
50
+ exports.readClusterDomain = readClusterDomain;
51
+ /**
52
+ * The trailing dot makes the name absolute. Without it a four-label name stays below the default
53
+ * `ndots:5` of a pod's resolv.conf and every lookup first walks the whole search list, costing
54
+ * three NXDOMAIN round trips before the real answer.
55
+ */
56
+ const buildHeadlessServiceDnsName = (serviceName, namespace, clusterDomain = (0, exports.readClusterDomain)()) => `${serviceName}.${namespace}.svc.${clusterDomain}.`;
57
+ exports.buildHeadlessServiceDnsName = buildHeadlessServiceDnsName;
58
+ /**
59
+ * Uses `resolve*` rather than `lookup`: `lookup` is `getaddrinfo`, which occupies a libuv
60
+ * threadpool slot shared with file and crypto work, and applies the search list. Both families are
61
+ * queried because that is what the headless DNS path this falls back to used to return.
62
+ */
63
+ const resolveHeadlessDnsHosts = async (serviceName, namespace) => {
64
+ const hostname = (0, exports.buildHeadlessServiceDnsName)(serviceName, namespace);
65
+ const resolver = new promises_1.Resolver({ timeout: DNS_TIMEOUT_MS, tries: DNS_ATTEMPTS });
66
+ const [ipv4, ipv6] = await Promise.allSettled([
67
+ resolver.resolve4(hostname),
68
+ resolver.resolve6(hostname),
69
+ ]);
70
+ const hosts = [
71
+ ...(ipv4.status === 'fulfilled' ? ipv4.value : []),
72
+ ...(ipv6.status === 'fulfilled' ? ipv6.value : []),
73
+ ];
74
+ if (hosts.length === 0 && ipv4.status === 'rejected') {
75
+ throw ipv4.reason;
76
+ }
77
+ return hosts;
78
+ };
79
+ exports.resolveHeadlessDnsHosts = resolveHeadlessDnsHosts;
80
+ const resetClusterDomainCache = () => {
81
+ cachedClusterDomain = undefined;
82
+ };
83
+ exports.resetClusterDomainCache = resetClusterDomainCache;
84
+ //# sourceMappingURL=dns-fallback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dns-fallback.js","sourceRoot":"","sources":["../src/dns-fallback.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * In-memory projection of Kubernetes EndpointSlices, kept per namespace.
3
+ *
4
+ * Deliberately free of I/O so the merge/readiness/notification rules can be tested directly.
5
+ * The watch driver in `k8s-endpoint-watch` owns the transport and feeds this store.
6
+ */
7
+ export interface IEndpointSlicePort {
8
+ portName: string;
9
+ port: number;
10
+ }
11
+ export interface IRawEndpointConditions {
12
+ ready?: boolean;
13
+ serving?: boolean;
14
+ terminating?: boolean;
15
+ }
16
+ export interface IRawEndpoint {
17
+ addresses?: string[];
18
+ conditions?: IRawEndpointConditions;
19
+ }
20
+ export interface IRawEndpointPort {
21
+ name?: string;
22
+ port?: number;
23
+ protocol?: string;
24
+ }
25
+ export interface IRawEndpointSlice {
26
+ addressType?: string;
27
+ metadata?: {
28
+ name?: string;
29
+ resourceVersion?: string;
30
+ labels?: Record<string, string>;
31
+ };
32
+ endpoints?: IRawEndpoint[];
33
+ ports?: IRawEndpointPort[];
34
+ }
35
+ export interface INormalizedEndpointSlice {
36
+ sliceName: string;
37
+ serviceName: string;
38
+ hosts: string[];
39
+ ports: IEndpointSlicePort[];
40
+ }
41
+ /**
42
+ * `pending` means no successful list has completed yet, so callers must keep waiting instead of
43
+ * reporting a failure. `ready` and `failed` are both terminal enough to answer a name resolution.
44
+ */
45
+ export type IServiceResolution = {
46
+ status: 'pending';
47
+ } | {
48
+ status: 'ready';
49
+ hosts: string[];
50
+ ports: IEndpointSlicePort[];
51
+ } | {
52
+ status: 'failed';
53
+ message: string;
54
+ };
55
+ export type IStoreListener = () => void;
56
+ /**
57
+ * A nil `ready` means "unknown", which consumers are expected to treat as ready. `terminating`
58
+ * endpoints are the whole point of this resolver: they must leave the pool before the pod dies.
59
+ */
60
+ export declare const isEndpointReady: (endpoint: IRawEndpoint) => boolean;
61
+ export declare const normalizeEndpointSlice: (endpointSlice: IRawEndpointSlice) => INormalizedEndpointSlice | undefined;
62
+ export declare const addEndpointSliceListener: (namespace: string, serviceName: string, listener: IStoreListener) => void;
63
+ export declare const removeEndpointSliceListener: (namespace: string, serviceName: string, listener: IStoreListener) => void;
64
+ export declare const getListenedServices: (namespace: string) => string[];
65
+ export declare const hasListenedServices: (namespace: string) => boolean;
66
+ /**
67
+ * Applies a full list result. Every listened service is notified, including services with no
68
+ * slices at all, so a missing or misspelled service resolves to an error instead of hanging.
69
+ */
70
+ export declare const replaceNamespaceSlices: (namespace: string, rawSlices: IRawEndpointSlice[]) => void;
71
+ export declare const applyEndpointSliceEvent: (namespace: string, eventType: string, rawSlice: IRawEndpointSlice) => void;
72
+ /**
73
+ * Records a namespace-level watch failure. Endpoints already known stay in place — a transient
74
+ * API server blip must not tear down a working pool — so the failure only becomes visible to
75
+ * callers while the namespace has never been listed successfully.
76
+ */
77
+ export declare const markNamespaceFailed: (namespace: string, message: string) => void;
78
+ export declare const resetNamespace: (namespace: string) => void;
79
+ export declare const resolveService: (namespace: string, serviceName: string) => IServiceResolution;
80
+ export declare const resetEndpointSliceStore: () => void;
81
+ //# sourceMappingURL=endpoint-slice-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint-slice-store.d.ts","sourceRoot":"","sources":["../src/endpoint-slice-store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,CAAC,EAAE,sBAAsB,CAAC;CACrC;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACjC,CAAC;IACF,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,kBAAkB,EAAE,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,GACrB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,kBAAkB,EAAE,CAAA;CAAE,GACjE;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1C,MAAM,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC;AAsCxC;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,UAAU,YAAY,KAAG,OAUxD,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,eAAe,iBAAiB,KAC/B,wBAAwB,GAAG,SA2B7B,CAAC;AAsBF,eAAO,MAAM,wBAAwB,GACnC,WAAW,MAAM,EACjB,aAAa,MAAM,EACnB,UAAU,cAAc,KACvB,IAMF,CAAC;AAEF,eAAO,MAAM,2BAA2B,GACtC,WAAW,MAAM,EACjB,aAAa,MAAM,EACnB,UAAU,cAAc,KACvB,IAeF,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,WAAW,MAAM,KAAG,MAAM,EAE7D,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,WAAW,MAAM,KAAG,OAEvD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GAAI,WAAW,MAAM,EAAE,WAAW,iBAAiB,EAAE,KAAG,IAgB1F,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,WAAW,MAAM,EACjB,WAAW,MAAM,EACjB,UAAU,iBAAiB,KAC1B,IA2BF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,WAAW,MAAM,EAAE,SAAS,MAAM,KAAG,IAWxE,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,WAAW,MAAM,KAAG,IAElD,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,WAAW,MAAM,EAAE,aAAa,MAAM,KAAG,kBAiCvE,CAAC;AAEF,eAAO,MAAM,uBAAuB,QAAO,IAE1C,CAAC"}
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ /**
3
+ * In-memory projection of Kubernetes EndpointSlices, kept per namespace.
4
+ *
5
+ * Deliberately free of I/O so the merge/readiness/notification rules can be tested directly.
6
+ * The watch driver in `k8s-endpoint-watch` owns the transport and feeds this store.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.resetEndpointSliceStore = exports.resolveService = exports.resetNamespace = exports.markNamespaceFailed = exports.applyEndpointSliceEvent = exports.replaceNamespaceSlices = exports.hasListenedServices = exports.getListenedServices = exports.removeEndpointSliceListener = exports.addEndpointSliceListener = exports.normalizeEndpointSlice = exports.isEndpointReady = void 0;
10
+ const SERVICE_NAME_LABEL = 'kubernetes.io/service-name';
11
+ /** `FQDN` slices carry hostnames rather than addresses and are not usable as dial targets. */
12
+ const IP_ADDRESS_TYPES = new Set(['IPv4', 'IPv6']);
13
+ const namespaceStates = new Map();
14
+ const createNamespaceState = () => ({
15
+ status: 'pending',
16
+ failureMessage: '',
17
+ slicesByName: new Map(),
18
+ listenersByService: new Map(),
19
+ });
20
+ const getOrCreateNamespaceState = (namespace) => {
21
+ const existing = namespaceStates.get(namespace);
22
+ if (existing) {
23
+ return existing;
24
+ }
25
+ const created = createNamespaceState();
26
+ namespaceStates.set(namespace, created);
27
+ return created;
28
+ };
29
+ /**
30
+ * A nil `ready` means "unknown", which consumers are expected to treat as ready. `terminating`
31
+ * endpoints are the whole point of this resolver: they must leave the pool before the pod dies.
32
+ */
33
+ const isEndpointReady = (endpoint) => {
34
+ const conditions = endpoint.conditions;
35
+ if (!conditions) {
36
+ return true;
37
+ }
38
+ return (conditions.ready !== false && conditions.serving !== false && conditions.terminating !== true);
39
+ };
40
+ exports.isEndpointReady = isEndpointReady;
41
+ const normalizeEndpointSlice = (endpointSlice) => {
42
+ const sliceName = endpointSlice.metadata?.name;
43
+ if (!sliceName) {
44
+ return undefined;
45
+ }
46
+ if (endpointSlice.addressType !== undefined && !IP_ADDRESS_TYPES.has(endpointSlice.addressType)) {
47
+ return undefined;
48
+ }
49
+ const hosts = (endpointSlice.endpoints ?? [])
50
+ .filter(exports.isEndpointReady)
51
+ .flatMap((endpoint) => endpoint.addresses ?? []);
52
+ const ports = (endpointSlice.ports ?? [])
53
+ .filter((port) => {
54
+ return port.protocol === 'TCP' && typeof port.port === 'number';
55
+ })
56
+ .map((port) => ({ portName: port.name ?? '', port: port.port }));
57
+ return {
58
+ sliceName,
59
+ serviceName: endpointSlice.metadata?.labels?.[SERVICE_NAME_LABEL] ?? '',
60
+ hosts,
61
+ ports,
62
+ };
63
+ };
64
+ exports.normalizeEndpointSlice = normalizeEndpointSlice;
65
+ const notifyServices = (namespace, serviceNames) => {
66
+ const state = namespaceStates.get(namespace);
67
+ if (!state) {
68
+ return;
69
+ }
70
+ for (const serviceName of serviceNames) {
71
+ const listeners = state.listenersByService.get(serviceName);
72
+ if (!listeners) {
73
+ continue;
74
+ }
75
+ for (const listener of listeners) {
76
+ listener();
77
+ }
78
+ }
79
+ };
80
+ const addEndpointSliceListener = (namespace, serviceName, listener) => {
81
+ const state = getOrCreateNamespaceState(namespace);
82
+ const listeners = state.listenersByService.get(serviceName) ?? new Set();
83
+ listeners.add(listener);
84
+ state.listenersByService.set(serviceName, listeners);
85
+ };
86
+ exports.addEndpointSliceListener = addEndpointSliceListener;
87
+ const removeEndpointSliceListener = (namespace, serviceName, listener) => {
88
+ const state = namespaceStates.get(namespace);
89
+ const listeners = state?.listenersByService.get(serviceName);
90
+ if (!listeners) {
91
+ return;
92
+ }
93
+ listeners.delete(listener);
94
+ if (listeners.size > 0) {
95
+ return;
96
+ }
97
+ state?.listenersByService.delete(serviceName);
98
+ };
99
+ exports.removeEndpointSliceListener = removeEndpointSliceListener;
100
+ const getListenedServices = (namespace) => {
101
+ return Array.from(namespaceStates.get(namespace)?.listenersByService.keys() ?? []);
102
+ };
103
+ exports.getListenedServices = getListenedServices;
104
+ const hasListenedServices = (namespace) => {
105
+ return (namespaceStates.get(namespace)?.listenersByService.size ?? 0) > 0;
106
+ };
107
+ exports.hasListenedServices = hasListenedServices;
108
+ /**
109
+ * Applies a full list result. Every listened service is notified, including services with no
110
+ * slices at all, so a missing or misspelled service resolves to an error instead of hanging.
111
+ */
112
+ const replaceNamespaceSlices = (namespace, rawSlices) => {
113
+ const state = getOrCreateNamespaceState(namespace);
114
+ state.status = 'ready';
115
+ state.failureMessage = '';
116
+ state.slicesByName = new Map();
117
+ for (const rawSlice of rawSlices) {
118
+ const normalized = (0, exports.normalizeEndpointSlice)(rawSlice);
119
+ if (normalized) {
120
+ state.slicesByName.set(normalized.sliceName, normalized);
121
+ }
122
+ }
123
+ notifyServices(namespace, state.listenersByService.keys());
124
+ };
125
+ exports.replaceNamespaceSlices = replaceNamespaceSlices;
126
+ const applyEndpointSliceEvent = (namespace, eventType, rawSlice) => {
127
+ const normalized = (0, exports.normalizeEndpointSlice)(rawSlice);
128
+ if (!normalized) {
129
+ return;
130
+ }
131
+ const state = getOrCreateNamespaceState(namespace);
132
+ const previous = state.slicesByName.get(normalized.sliceName);
133
+ const affectedServices = new Set();
134
+ // A slice can be relabelled onto another service, so the previous owner has to be notified too.
135
+ if (previous?.serviceName) {
136
+ affectedServices.add(previous.serviceName);
137
+ }
138
+ if (eventType === 'DELETED') {
139
+ state.slicesByName.delete(normalized.sliceName);
140
+ }
141
+ else {
142
+ state.slicesByName.set(normalized.sliceName, normalized);
143
+ if (normalized.serviceName) {
144
+ affectedServices.add(normalized.serviceName);
145
+ }
146
+ }
147
+ notifyServices(namespace, affectedServices);
148
+ };
149
+ exports.applyEndpointSliceEvent = applyEndpointSliceEvent;
150
+ /**
151
+ * Records a namespace-level watch failure. Endpoints already known stay in place — a transient
152
+ * API server blip must not tear down a working pool — so the failure only becomes visible to
153
+ * callers while the namespace has never been listed successfully.
154
+ */
155
+ const markNamespaceFailed = (namespace, message) => {
156
+ const state = getOrCreateNamespaceState(namespace);
157
+ if (state.status === 'ready') {
158
+ return;
159
+ }
160
+ state.status = 'failed';
161
+ state.failureMessage = message;
162
+ notifyServices(namespace, (0, exports.getListenedServices)(namespace));
163
+ };
164
+ exports.markNamespaceFailed = markNamespaceFailed;
165
+ const resetNamespace = (namespace) => {
166
+ namespaceStates.delete(namespace);
167
+ };
168
+ exports.resetNamespace = resetNamespace;
169
+ const resolveService = (namespace, serviceName) => {
170
+ const state = namespaceStates.get(namespace);
171
+ if (!state || state.status === 'pending') {
172
+ return { status: 'pending' };
173
+ }
174
+ if (state.status === 'failed') {
175
+ return { status: 'failed', message: state.failureMessage };
176
+ }
177
+ const hosts = new Set();
178
+ const portsByNumber = new Map();
179
+ for (const slice of state.slicesByName.values()) {
180
+ if (slice.serviceName !== serviceName) {
181
+ continue;
182
+ }
183
+ for (const host of slice.hosts) {
184
+ hosts.add(host);
185
+ }
186
+ for (const port of slice.ports) {
187
+ portsByNumber.set(port.port, port);
188
+ }
189
+ }
190
+ return {
191
+ status: 'ready',
192
+ hosts: Array.from(hosts),
193
+ ports: Array.from(portsByNumber.values()),
194
+ };
195
+ };
196
+ exports.resolveService = resolveService;
197
+ const resetEndpointSliceStore = () => {
198
+ namespaceStates.clear();
199
+ };
200
+ exports.resetEndpointSliceStore = resetEndpointSliceStore;
201
+ //# sourceMappingURL=endpoint-slice-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint-slice-store.js","sourceRoot":"","sources":["../src/endpoint-slice-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { registerK8sResolver, resolveGrpcTargetAddress } from './resolve-grpc-target-address';
2
+ export { GRPC_K8S_RESOLVER_DNS_FALLBACK_ENV, GRPC_K8S_RESOLVER_ENABLED_ENV, K8S_RESOLVER_SCHEME, } from './resolver.constants';
3
+ export { extractServiceAndNamespace, isK8sResolverEnabled, parseHostPortAddress, rewriteGrpcAddressIfEnabled, toK8sResolverAddress, } from './rewrite-grpc-address';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC9F,OAAO,EACL,kCAAkC,EAClC,6BAA6B,EAC7B,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,oBAAoB,EACpB,2BAA2B,EAC3B,oBAAoB,GACrB,MAAM,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toK8sResolverAddress = exports.rewriteGrpcAddressIfEnabled = exports.parseHostPortAddress = exports.isK8sResolverEnabled = exports.extractServiceAndNamespace = exports.K8S_RESOLVER_SCHEME = exports.GRPC_K8S_RESOLVER_ENABLED_ENV = exports.GRPC_K8S_RESOLVER_DNS_FALLBACK_ENV = exports.resolveGrpcTargetAddress = exports.registerK8sResolver = void 0;
4
+ var resolve_grpc_target_address_1 = require("./resolve-grpc-target-address");
5
+ Object.defineProperty(exports, "registerK8sResolver", { enumerable: true, get: function () { return resolve_grpc_target_address_1.registerK8sResolver; } });
6
+ Object.defineProperty(exports, "resolveGrpcTargetAddress", { enumerable: true, get: function () { return resolve_grpc_target_address_1.resolveGrpcTargetAddress; } });
7
+ var resolver_constants_1 = require("./resolver.constants");
8
+ Object.defineProperty(exports, "GRPC_K8S_RESOLVER_DNS_FALLBACK_ENV", { enumerable: true, get: function () { return resolver_constants_1.GRPC_K8S_RESOLVER_DNS_FALLBACK_ENV; } });
9
+ Object.defineProperty(exports, "GRPC_K8S_RESOLVER_ENABLED_ENV", { enumerable: true, get: function () { return resolver_constants_1.GRPC_K8S_RESOLVER_ENABLED_ENV; } });
10
+ Object.defineProperty(exports, "K8S_RESOLVER_SCHEME", { enumerable: true, get: function () { return resolver_constants_1.K8S_RESOLVER_SCHEME; } });
11
+ var rewrite_grpc_address_1 = require("./rewrite-grpc-address");
12
+ Object.defineProperty(exports, "extractServiceAndNamespace", { enumerable: true, get: function () { return rewrite_grpc_address_1.extractServiceAndNamespace; } });
13
+ Object.defineProperty(exports, "isK8sResolverEnabled", { enumerable: true, get: function () { return rewrite_grpc_address_1.isK8sResolverEnabled; } });
14
+ Object.defineProperty(exports, "parseHostPortAddress", { enumerable: true, get: function () { return rewrite_grpc_address_1.parseHostPortAddress; } });
15
+ Object.defineProperty(exports, "rewriteGrpcAddressIfEnabled", { enumerable: true, get: function () { return rewrite_grpc_address_1.rewriteGrpcAddressIfEnabled; } });
16
+ Object.defineProperty(exports, "toK8sResolverAddress", { enumerable: true, get: function () { return rewrite_grpc_address_1.toK8sResolverAddress; } });
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,6EAA8F;AAArF,kIAAA,mBAAmB,OAAA;AAAE,uIAAA,wBAAwB,OAAA;AACtD,2DAI8B;AAH5B,wIAAA,kCAAkC,OAAA;AAClC,mIAAA,6BAA6B,OAAA;AAC7B,yHAAA,mBAAmB,OAAA;AAErB,+DAMgC;AAL9B,kIAAA,0BAA0B,OAAA;AAC1B,4HAAA,oBAAoB,OAAA;AACpB,4HAAA,oBAAoB,OAAA;AACpB,mIAAA,2BAA2B,OAAA;AAC3B,4HAAA,oBAAoB,OAAA"}
@@ -0,0 +1,18 @@
1
+ export declare const SA_TOKEN_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token";
2
+ export declare const SA_CA_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt";
3
+ export declare const SA_NAMESPACE_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/namespace";
4
+ export interface IKubernetesResponse {
5
+ statusCode: number;
6
+ body: string;
7
+ }
8
+ export declare class KubernetesApiError extends Error {
9
+ readonly statusCode?: number | undefined;
10
+ constructor(message: string, statusCode?: number | undefined);
11
+ get isExpiredResourceVersion(): boolean;
12
+ }
13
+ export declare const readServiceAccountNamespace: () => string | null;
14
+ export declare const requestJson: <T>(path: string, signal: AbortSignal, idleTimeoutMs: number) => Promise<T>;
15
+ /** Resolves when the server closes the stream, which it does routinely once the watch times out. */
16
+ export declare const streamLines: (path: string, signal: AbortSignal, idleTimeoutMs: number, onChunk: (chunk: string) => void) => Promise<void>;
17
+ export declare const resetClusterConnectionCache: () => void;
18
+ //# sourceMappingURL=k8s-api-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"k8s-api-client.d.ts","sourceRoot":"","sources":["../src/k8s-api-client.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,aAAa,wDAA0B,CAAC;AACrD,eAAO,MAAM,UAAU,yDAA2B,CAAC;AACnD,eAAO,MAAM,iBAAiB,4DAA8B,CAAC;AAE7D,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAOD,qBAAa,kBAAmB,SAAQ,KAAK;aAGzB,UAAU,CAAC,EAAE,MAAM;gBADnC,OAAO,EAAE,MAAM,EACC,UAAU,CAAC,EAAE,MAAM,YAAA;IAMrC,IAAW,wBAAwB,IAAI,OAAO,CAE7C;CACF;AAsCD,eAAO,MAAM,2BAA2B,QAAO,MAAM,GAAG,IAUvD,CAAC;AAyFF,eAAO,MAAM,WAAW,GAAU,CAAC,EACjC,MAAM,MAAM,EACZ,QAAQ,WAAW,EACnB,eAAe,MAAM,KACpB,OAAO,CAAC,CAAC,CAMX,CAAC;AAEF,oGAAoG;AACpG,eAAO,MAAM,WAAW,GACtB,MAAM,MAAM,EACZ,QAAQ,WAAW,EACnB,eAAe,MAAM,EACrB,SAAS,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,KAC/B,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF,eAAO,MAAM,2BAA2B,QAAO,IAG9C,CAAC"}