@starship-ci/generator
Version:
Kubernetes manifest generator for Starship deployments
385 lines (376 loc) • 15.5 kB
JavaScript
"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.GoRelayerBuilder = exports.GoRelayerStatefulSetGenerator = exports.GoRelayerConfigMapGenerator = void 0;
const helpers = __importStar(require("../../helpers"));
const version_1 = require("../../version");
const base_1 = require("./base");
/**
* ConfigMap generator for Go Relayer
*/
class GoRelayerConfigMapGenerator {
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}`
}
};
const data = {
'path.json': this.generatePathConfig()
};
// Generate individual chain configs
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`);
}
data[`${chainId}.json`] = this.generateChainConfig(chainId, chain);
});
return [
{
apiVersion: 'v1',
kind: 'ConfigMap',
metadata,
data
}
];
}
generatePathConfig() {
const paths = {};
if (this.relayer.channels && this.relayer.channels.length > 0) {
this.relayer.channels.forEach((channel, index) => {
const pathName = `path${index}`;
paths[pathName] = {
src: {
'chain-id': channel['a-chain'],
'client-id': '', // Will be filled during connection creation
'connection-id': channel['a-connection'] || '',
'channel-id': '', // Will be filled during channel creation
'port-id': channel['a-port']
},
dst: {
'chain-id': channel['b-chain'] || '',
'client-id': '', // Will be filled during connection creation
'connection-id': '', // Will be filled during connection creation
'channel-id': '', // Will be filled during channel creation
'port-id': channel['b-port']
},
'src-channel-filter': {
rule: null,
'channel-list': []
}
};
});
}
else if (this.relayer.chains && this.relayer.chains.length >= 2) {
// Generate a default path using the first two chains
const srcChainId = this.relayer.chains[0];
const dstChainId = this.relayer.chains[1];
paths['path'] = {
src: {
'chain-id': srcChainId,
'client-id': '', // Will be filled during connection creation
'connection-id': '', // Will be filled during connection creation
'channel-id': '', // Will be filled during channel creation
'port-id': 'transfer'
},
dst: {
'chain-id': dstChainId,
'client-id': '', // Will be filled during connection creation
'connection-id': '', // Will be filled during connection creation
'channel-id': '', // Will be filled during channel creation
'port-id': 'transfer'
},
'src-channel-filter': {
rule: null,
'channel-list': []
}
};
}
return JSON.stringify({ paths }, null, 2);
}
generateChainConfig(chainId, chain) {
const chainName = helpers.getChainName(String(chain.id));
const relayerConfig = this.relayer.config || {};
const chainConfig = relayerConfig.chains?.find((c) => c.id === chainId) || {};
const config = {
type: 'cosmos',
value: {
key: chainId,
'chain-id': chainId,
'rpc-addr': `http://${chainName}-genesis.$(NAMESPACE).svc.cluster.local:26657`,
'account-prefix': chainConfig.account_prefix || chain.prefix,
'keyring-backend': 'test',
'gas-adjustment': chainConfig.gas_adjustment || 1.2,
'gas-prices': `${chainConfig.gas_prices || '0.01'}${chain.denom}`,
'min-gas-amount': chainConfig.min_gas_amount || 0,
debug: chainConfig.debug || false,
timeout: chainConfig.timeout || '20s',
'block-timeout': chainConfig.block_timeout || '',
'output-format': 'json',
'sign-mode': 'direct',
'extra-codecs': chainConfig.extra_codecs || []
}
};
return JSON.stringify(config, null, 2);
}
}
exports.GoRelayerConfigMapGenerator = GoRelayerConfigMapGenerator;
/**
* StatefulSet generator for Go Relayer
*/
class GoRelayerStatefulSetGenerator {
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 go-relayer init container
initContainers.push(this.generateGoRelayerInitContainer());
return initContainers;
}
generateGoRelayerInitContainer() {
const image = this.relayer.image || 'ghcr.io/cosmology-tech/starship/go-relayer:v2.4.1';
const env = [
{ name: 'KEYS_CONFIG', value: '/keys/keys.json' },
{ name: 'RELAYER_DIR', value: '/root/.relayer' },
{ name: 'RELAYER_INDEX', value: '${HOSTNAME##*-}' },
{
name: 'NAMESPACE',
valueFrom: { fieldRef: { fieldPath: 'metadata.namespace' } }
}
];
const command = this.generateGoRelayerInitCommand();
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 go-relayer container
containers.push({
name: 'relayer',
image: this.relayer.image ||
'ghcr.io/cosmology-tech/starship/go-relayer:v2.4.1',
imagePullPolicy: this.config.images?.imagePullPolicy || 'IfNotPresent',
env: [{ name: 'RELAYER_DIR', value: '/root/.relayer' }],
command: ['bash', '-c'],
args: [
'RLY_INDEX=${HOSTNAME##*-}\necho "Relayer Index: $RLY_INDEX"\nrly 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' } }
];
}
generateGoRelayerInitCommand() {
let command = `set -ux
RLY_INDEX=\${HOSTNAME##*-}
echo "Relayer Index: $RLY_INDEX"
mkdir -p $RELAYER_DIR/config
cp /configs/path.json $RELAYER_DIR/config/
MNEMONIC=$(jq -r ".relayers[$RLY_INDEX].mnemonic" $KEYS_CONFIG)
`;
// Add chain configurations and key creation
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 "Setting up chain ${chainId}..."
cp /configs/${chainId}.json $RELAYER_DIR/config/
rly chains add --file /configs/${chainId}.json ${chainId}
echo "Creating key for ${chainId}..."
echo "$MNEMONIC" | rly keys restore ${chainId} ${chainId} --restore-key-type secp256k1 --coin-type 118
DENOM="${chain.denom}"
RLY_ADDR=$(rly keys show ${chainId} ${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 path setup and channel creation if specified
if (this.relayer.channels && this.relayer.channels.length > 0) {
command += `
echo "Adding paths..."
rly paths add --file /configs/path.json
`;
this.relayer.channels.forEach((channel, index) => {
const pathName = `path${index}`;
if (channel['new-connection']) {
command += `
echo "Creating client, connection and channel for ${pathName}..."
rly tx link ${pathName} --src-port ${channel['a-port']} --dst-port ${channel['b-port']}
`;
}
else {
command += `
echo "Creating channel for ${pathName}..."
rly tx channel ${pathName} --src-port ${channel['a-port']} --dst-port ${channel['b-port']} ${channel.order ? `--order ${channel.order}` : ''}
`;
}
});
}
return command;
}
}
exports.GoRelayerStatefulSetGenerator = GoRelayerStatefulSetGenerator;
/**
* Main Go Relayer builder
*/
class GoRelayerBuilder extends base_1.BaseRelayerBuilder {
constructor(relayer, config) {
super(relayer, config);
this.generators = [
new GoRelayerConfigMapGenerator(relayer, config),
new GoRelayerStatefulSetGenerator(relayer, config)
];
}
}
exports.GoRelayerBuilder = GoRelayerBuilder;