@starship-ci/generator
Version:
Kubernetes manifest generator for Starship deployments
429 lines (422 loc) • 17 kB
JavaScript
;
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;