UNPKG

@starship-ci/generator

Version:

Kubernetes manifest generator for Starship deployments

429 lines (422 loc) 17 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TsRelayerBuilder = exports.TsRelayerStatefulSetGenerator = exports.TsRelayerConfigMapGenerator = void 0; const helpers = __importStar(require("../../helpers")); const version_1 = require("../../version"); const base_1 = require("./base"); /** * ConfigMap generator for TS Relayer */ class TsRelayerConfigMapGenerator { config; relayer; constructor(relayer, config) { this.config = config; this.relayer = relayer; } generate() { const metadata = { name: `${this.relayer.type}-${this.relayer.name}`, labels: { ...helpers.getCommonLabels(this.config), 'app.kubernetes.io/component': 'relayer', 'app.kubernetes.io/part-of': 'starship', 'app.kubernetes.io/role': this.relayer.type, 'app.kubernetes.io/name': `${this.relayer.type}-${this.relayer.name}` } }; return [ { apiVersion: 'v1', kind: 'ConfigMap', metadata, data: { 'app.yaml': this.generateAppConfig(), 'registry.yaml': this.generateRegistryConfig() } } ]; } generateAppConfig() { const relayerConfig = this.relayer.config || {}; const globalConfig = relayerConfig.global || {}; const chains = {}; this.relayer.chains.forEach((chainId) => { const chain = this.config.chains.find((c) => String(c.id) === chainId); if (!chain) { throw new Error(`Chain ${chainId} not found in configuration`); } const chainName = helpers.getChainName(String(chain.id)); const chainConfig = relayerConfig.chains?.find((c) => c.id === chainId) || {}; chains[chainId] = { chain_id: chainId, rpc: [ `http://${chainName}-genesis.$(NAMESPACE).svc.cluster.local:26657` ], rest: [ `http://${chainName}-genesis.$(NAMESPACE).svc.cluster.local:1317` ], chain_name: chain.name, pretty_name: chainConfig.pretty_name || chain.name, prefix: chain.prefix, denom: chain.denom, decimals: chainConfig.decimals || 6, gas_price: chainConfig.gas_price || '0.01', hd_path: chain.hdPath || "m/44'/118'/0'/0/0" }; }); const appConfig = { global: { api_port: globalConfig.api_port || 3000, timeout: globalConfig.timeout || 10000, memo: globalConfig.memo || '', ...globalConfig }, chains, cl: [] }; if (this.relayer.channels && this.relayer.channels.length > 0) { this.relayer.channels.forEach((channel) => { appConfig.cl.push({ src: { chain_id: channel['a-chain'], connection_id: channel['a-connection'] || '', channel_id: '', // Will be filled during channel creation port_id: channel['a-port'] }, dst: { chain_id: channel['b-chain'] || '', connection_id: '', // Will be filled during connection creation channel_id: '', // Will be filled during channel creation port_id: channel['b-port'] }, new_connection: channel['new-connection'] || false, order: channel.order || 'unordered' }); }); } return `# TS Relayer Configuration ${Object.entries(appConfig) .map(([key, value]) => `${key}: ${JSON.stringify(value, null, 2)}`) .join('\n\n')}`; } generateRegistryConfig() { const chains = []; this.relayer.chains.forEach((chainId) => { const chain = this.config.chains.find((c) => String(c.id) === chainId); if (!chain) { throw new Error(`Chain ${chainId} not found in configuration`); } const chainName = helpers.getChainName(String(chain.id)); const chainConfig = this.relayer.config?.chains?.find((c) => c.id === chainId) || {}; chains.push({ chain_name: chain.name, chain_id: chainId, pretty_name: chainConfig.pretty_name || chain.name, status: 'live', network_type: 'testnet', bech32_prefix: chain.prefix, daemon_name: chain.binary || 'gaiad', node_home: chainConfig.node_home || '$HOME/.gaia', key_algos: ['secp256k1'], slip44: chainConfig.slip44 || 118, fees: { fee_tokens: [ { denom: chain.denom, fixed_min_gas_price: chainConfig.gas_price || 0.01, low_gas_price: chainConfig.gas_price || 0.01, average_gas_price: chainConfig.gas_price || 0.01, high_gas_price: chainConfig.gas_price || 0.01 } ] }, staking: { staking_tokens: [ { denom: chain.denom } ] }, codebase: { git_repo: chainConfig.git_repo || '', recommended_version: chainConfig.version || '', compatible_versions: chainConfig.compatible_versions || [], genesis: { genesis_url: chainConfig.genesis_url || '' } }, apis: { rpc: [ { address: `http://${chainName}-genesis.$(NAMESPACE).svc.cluster.local:26657`, provider: 'starship' } ], rest: [ { address: `http://${chainName}-genesis.$(NAMESPACE).svc.cluster.local:1317`, provider: 'starship' } ] }, explorers: [] }); }); return `# Chain Registry Configuration chains: ${chains .map((chain) => ` - ${Object.entries(chain) .map(([key, value]) => `${key}: ${JSON.stringify(value, null, 4)}`) .join('\n ')}`) .join('\n')}`; } } exports.TsRelayerConfigMapGenerator = TsRelayerConfigMapGenerator; /** * StatefulSet generator for TS Relayer */ class TsRelayerStatefulSetGenerator { config; relayer; constructor(relayer, config) { this.config = config; this.relayer = relayer; } generate() { const fullname = `${this.relayer.type}-${this.relayer.name}`; return [ { apiVersion: 'apps/v1', kind: 'StatefulSet', metadata: { name: fullname, labels: { ...helpers.getCommonLabels(this.config), 'app.kubernetes.io/component': 'relayer', 'app.kubernetes.io/part-of': 'starship', 'app.kubernetes.io/role': this.relayer.type, 'app.kubernetes.io/name': fullname } }, spec: { serviceName: fullname, replicas: this.relayer.replicas || 1, podManagementPolicy: 'Parallel', revisionHistoryLimit: 3, selector: { matchLabels: { 'app.kubernetes.io/instance': 'relayer', 'app.kubernetes.io/type': this.relayer.type, 'app.kubernetes.io/name': fullname } }, template: { metadata: { annotations: { quality: 'release', role: 'api-gateway', sla: 'high', tier: 'gateway' }, labels: { 'app.kubernetes.io/instance': 'relayer', 'app.kubernetes.io/type': this.relayer.type, 'app.kubernetes.io/name': fullname, 'app.kubernetes.io/rawname': this.relayer.name, 'app.kubernetes.io/version': (0, version_1.getGeneratorVersion)() } }, spec: { initContainers: this.generateInitContainers(), containers: this.generateContainers(), volumes: this.generateVolumes() } } } } ]; } generateInitContainers() { const initContainers = []; // Add wait init containers for all chains this.relayer.chains.forEach((chainId) => { const chain = this.config.chains.find((c) => String(c.id) === chainId); if (!chain) return; const chainName = helpers.getChainName(String(chain.id)); initContainers.push({ name: `init-${chainName}`, image: 'ghcr.io/cosmology-tech/starship/wait-for-service:v0.1.0', imagePullPolicy: this.config.images?.imagePullPolicy || 'IfNotPresent', command: ['bash', '-c'], args: [ `echo "Waiting for ${chainName} service..."\nwait-for-service ${chainName}-genesis.$(NAMESPACE).svc.cluster.local:26657` ], env: [ { name: 'NAMESPACE', valueFrom: { fieldRef: { fieldPath: 'metadata.namespace' } } } ] }); }); // Add ts-relayer init container initContainers.push(this.generateTsRelayerInitContainer()); return initContainers; } generateTsRelayerInitContainer() { const image = this.relayer.image || 'ghcr.io/cosmology-tech/starship/ts-relayer:0.9.0'; const env = [ { name: 'KEYS_CONFIG', value: '/keys/keys.json' }, { name: 'RELAYER_DIR', value: '/root/.ts-relayer' }, { name: 'RELAYER_INDEX', value: '${HOSTNAME##*-}' }, { name: 'NAMESPACE', valueFrom: { fieldRef: { fieldPath: 'metadata.namespace' } } } ]; const command = this.generateTsRelayerInitCommand(); return { name: 'init-relayer', image, imagePullPolicy: this.config.images?.imagePullPolicy || 'IfNotPresent', env, command: ['bash', '-c'], args: [command], resources: helpers.getResourceObject(this.relayer.resources || { cpu: '0.2', memory: '200M' }), volumeMounts: [ { mountPath: '/root', name: 'relayer' }, { mountPath: '/configs', name: 'relayer-config' }, { mountPath: '/keys', name: 'keys' }, { mountPath: '/scripts', name: 'scripts' } ] }; } generateContainers() { const containers = []; // Main ts-relayer container containers.push({ name: 'relayer', image: this.relayer.image || 'ghcr.io/cosmology-tech/starship/ts-relayer:0.9.0', imagePullPolicy: this.config.images?.imagePullPolicy || 'IfNotPresent', env: [{ name: 'RELAYER_DIR', value: '/root/.ts-relayer' }], command: ['bash', '-c'], args: [ 'RLY_INDEX=${HOSTNAME##*-}\necho "Relayer Index: $RLY_INDEX"\nts-relayer start' ], resources: helpers.getResourceObject(this.relayer.resources || { cpu: '0.2', memory: '200M' }), securityContext: { allowPrivilegeEscalation: false, runAsUser: 0 }, volumeMounts: [ { mountPath: '/root', name: 'relayer' }, { mountPath: '/configs', name: 'relayer-config' } ] }); return containers; } generateVolumes() { return [ { name: 'relayer', emptyDir: {} }, { name: 'relayer-config', configMap: { name: `${this.relayer.type}-${this.relayer.name}` } }, { name: 'keys', configMap: { name: 'keys' } }, { name: 'scripts', configMap: { name: 'setup-scripts' } } ]; } generateTsRelayerInitCommand() { let command = `set -ux RLY_INDEX=\${HOSTNAME##*-} echo "Relayer Index: $RLY_INDEX" mkdir -p $RELAYER_DIR cp /configs/app.yaml $RELAYER_DIR/ cp /configs/registry.yaml $RELAYER_DIR/ MNEMONIC=$(jq -r ".relayers[$RLY_INDEX].mnemonic" $KEYS_CONFIG) `; // Add key creation and funding for each chain this.relayer.chains.forEach((chainId) => { const chain = this.config.chains.find((c) => String(c.id) === chainId); if (!chain) return; const chainName = helpers.getChainName(String(chain.id)); command += ` echo "Creating key for ${chainId}..." echo "$MNEMONIC" | ts-relayer keys restore ${chainId} --hd-path "${chain.hdPath || "m/44'/118'/0'/0/0"}" DENOM="${chain.denom}" RLY_ADDR=$(ts-relayer keys show ${chainId}) echo "Transfer tokens to address $RLY_ADDR" bash -e /scripts/transfer-tokens.sh \\ $RLY_ADDR \\ $DENOM \\ http://${chainName}-genesis.$NAMESPACE.svc.cluster.local:8000/credit \\ "${chain.faucet?.enabled || false}" || true `; }); // Add channel creation if specified if (this.relayer.channels && this.relayer.channels.length > 0) { this.relayer.channels.forEach((channel) => { if (channel['new-connection']) { command += ` echo "Creating client, connection and channel..." ts-relayer tx link ${channel['a-chain']} ${channel['b-chain']} \\ --src-port ${channel['a-port']} \\ --dst-port ${channel['b-port']} \\ ${channel.order ? `--order ${channel.order}` : ''} `; } else { command += ` echo "Creating channel..." ts-relayer tx channel ${channel['a-chain']} ${channel['b-chain']} \\ --src-port ${channel['a-port']} \\ --dst-port ${channel['b-port']} \\ ${channel.order ? `--order ${channel.order}` : ''} `; } }); } return command; } } exports.TsRelayerStatefulSetGenerator = TsRelayerStatefulSetGenerator; /** * Main TS Relayer builder */ class TsRelayerBuilder extends base_1.BaseRelayerBuilder { constructor(relayer, config) { super(relayer, config); this.generators = [ new TsRelayerConfigMapGenerator(relayer, config), new TsRelayerStatefulSetGenerator(relayer, config) ]; } } exports.TsRelayerBuilder = TsRelayerBuilder;