UNPKG

@hashgraph/solo

Version:

An opinionated CLI tool to deploy and manage private Hedera Networks.

136 lines (118 loc) 4.56 kB
// SPDX-License-Identifier: Apache-2.0 import {ipV4ToBase64, isIpV4Address} from '../../core/helpers.js'; import {type ConsensusNode} from '../../core/model/consensus-node.js'; import {SoloError} from '../../core/errors/solo-error.js'; import {type K8} from '../../integration/kube/k8.js'; import {NamespaceName} from '../../types/namespace/namespace-name.js'; import {type Service} from '../../integration/kube/resources/service/service.js'; import {type LoadBalancerIngress} from '../../integration/kube/resources/load-balancer-ingress.js'; import {Templates} from '../../core/templates.js'; export class Address { public constructor( public readonly port: number, private readonly fqdnOrIpAddress?: string, public readonly ipAddressV4?: string, public readonly domainName?: string, public readonly ipAddressV4Base64?: string, ) { this.port = port; this.fqdnOrIpAddress = fqdnOrIpAddress; if (this.fqdnOrIpAddress) { if (isIpV4Address(fqdnOrIpAddress)) { this.ipAddressV4 = fqdnOrIpAddress; this.ipAddressV4Base64 = ipV4ToBase64(fqdnOrIpAddress); return; } else { this.domainName = fqdnOrIpAddress; return; } } if (this.domainName) { this.domainName = domainName; return; } this.ipAddressV4 = ipAddressV4; this.ipAddressV4Base64 = ipAddressV4Base64; if (this.ipAddressV4 && !this.ipAddressV4Base64) { this.ipAddressV4Base64 = ipV4ToBase64(this.ipAddressV4); } if (this.ipAddressV4Base64 && !this.ipAddressV4) { // TODO: implement base64 to IPv4 conversion if needed throw new Error('ipAddressV4 must be provided if ipAddressV4Base64 is set'); } if (!this.ipAddressV4 && !this.ipAddressV4Base64) { throw new Error('Either domainName or ipAddressV4 must be provided'); } } public formattedAddress(): string { if (this.domainName) { return `${this.domainName}:${this.port}`; } else if (this.ipAddressV4) { return `${this.ipAddressV4}:${this.port}`; } else { throw new Error('Address is not properly initialized'); } } public hostString(): string { if (this.domainName) { return this.domainName; } else if (this.ipAddressV4) { return this.ipAddressV4; } else { throw new Error('Address is not properly initialized'); } } public static async getExternalAddress( consensusNode: ConsensusNode, k8: K8, port: number, gossipFqdnRestricted: boolean = true, ): Promise<Address> { return Address.resolveLoadBalancerAddress(consensusNode, k8, port, gossipFqdnRestricted); } private static async resolveLoadBalancerAddress( consensusNode: ConsensusNode, k8: K8, port: number, gossipFqdnRestricted: boolean, ): Promise<Address> { const namespace: NamespaceName = NamespaceName.of(consensusNode.namespace); try { const serviceList: Service[] = await k8 .services() .list(namespace, Templates.renderNodeSvcLabelsFromNodeId(consensusNode.nodeId)); if (serviceList && serviceList.length > 0) { const svc: Service = serviceList[0]; if (!svc.metadata.name.startsWith('network-node')) { throw new SoloError(`Service found is not a network node service: ${svc.metadata.name}`); } if ( svc.spec!.type === 'LoadBalancer' && svc.status?.loadBalancer?.ingress && svc.status.loadBalancer.ingress.length > 0 ) { for (let index: number = 0; index < svc.status.loadBalancer.ingress.length; index++) { const ingress: LoadBalancerIngress = svc.status.loadBalancer.ingress[index]; if (ingress.hostname) { return new Address(port, ingress.hostname); } else if (ingress.ip) { return new Address(port, ingress.ip); } } } // If gossip FQDN is allowed by node config, keep using the service FQDN fallback. if (!gossipFqdnRestricted) { return new Address(port, consensusNode.fullyQualifiedDomainName); } // When gossip FQDN is restricted and no LoadBalancer IP is available // (e.g., Kind/NodePort), use cluster IP to avoid CN validation failure. if (svc.spec?.clusterIP && svc.spec.clusterIP !== 'None') { return new Address(port, svc.spec.clusterIP); } } } catch { // Ignore and use FQDN } return new Address(port, consensusNode.fullyQualifiedDomainName); } }