@starship-ci/generator
Version:
Kubernetes manifest generator for Starship deployments
359 lines (358 loc) • 10.2 kB
JavaScript
import { getGeneratorVersion } from './version';
/**
* Convert chain.id to name usable by templates
* Replaces underscores with hyphens and truncates to 63 chars
*/
export function getChainName(chainId) {
return String(chainId).replace(/_/g, '-').substring(0, 63);
}
/**
* Create a default fully qualified app name
*/
export function getReleaseName(config) {
// Use the name from StarshipConfig
const releaseName = config.name || 'starship';
return releaseName.substring(0, 63).replace(/-$/, '');
}
/**
* Common labels for all resources
*/
export function getCommonLabels(config) {
return {
...getSelectorLabels(config),
'app.kubernetes.io/version': getGeneratorVersion(),
'app.kubernetes.io/managed-by': 'starship'
};
}
/**
* Selector labels for resources
*/
export function getSelectorLabels(config) {
return {
'starship.io/name': config.name
};
}
/**
* Default environment variables for chain containers
*/
export function getDefaultEnvVars(chain) {
return [
{ name: 'DENOM', value: chain.denom || '' },
{ name: 'COINS', value: chain.coins || '' },
{ name: 'CHAIN_BIN', value: chain.binary || '' },
{ name: 'CHAIN_DIR', value: chain.home || '' },
{ name: 'CODE_REPO', value: chain.repo || '' },
{ name: 'DAEMON_HOME', value: chain.home || '' },
{ name: 'DAEMON_NAME', value: chain.binary || '' }
];
}
/**
* Chain-specific environment variables
*/
export function getChainEnvVars(chain) {
return [{ name: 'CHAIN_ID', value: String(chain.id) }];
}
/**
* Timeout environment variables
*/
export function getTimeoutEnvVars(timeouts) {
const envVars = [];
for (const [key, value] of Object.entries(timeouts)) {
envVars.push({
name: key.toUpperCase(),
value: String(value)
});
}
return envVars;
}
/**
* Genesis-specific environment variables
*/
export function getGenesisEnvVars(chain, port) {
return [
{
name: 'GENESIS_HOST',
value: `${getChainName(String(chain.id))}-genesis`
},
{ name: 'GENESIS_PORT', value: String(port) },
{
name: 'NAMESPACE',
valueFrom: {
fieldRef: {
fieldPath: 'metadata.namespace'
}
}
}
];
}
/**
* Get resource object based on input
* Handles both simple cpu/memory format and full k8s resource format
*/
export function getResourceObject(resources) {
if (!resources) {
return {};
}
if (resources.cpu && resources.memory) {
// Simple format: { cpu: "0.5", memory: "500M" }
return {
limits: {
cpu: resources.cpu,
memory: resources.memory
},
requests: {
cpu: resources.cpu,
memory: resources.memory
}
};
}
// Full k8s format
return resources;
}
/**
* Get node resources with chain-specific overrides
*/
export function getNodeResources(chain, context) {
if (chain.resources) {
return getResourceObject(chain.resources);
}
return getResourceObject(context.resources?.node || {
cpu: '0.5',
memory: '500M'
});
}
/**
* Standard port mappings for Cosmos chains
*/
export function getPortMap() {
return {
p2p: 26656,
address: 26658,
grpc: 9090,
'grpc-web': 9091,
rest: 1317,
rpc: 26657,
metrics: 26660,
exposer: 8081,
faucet: 8000
};
}
/**
* Returns comma-separated list of chain IDs
*/
export function getChainIds(chains) {
return chains.map((chain) => chain.id).join(',');
}
/**
* Returns comma-separated list of chain names
* If chain name is custom, use chain id instead
*/
export function getChainNames(chains) {
return chains
.map((chain) => (chain.name === 'custom' ? chain.id : chain.name))
.join(',');
}
/**
* Returns comma-separated list of internal RPC addresses
*/
export function getChainInternalRpcAddrs(chains) {
return chains
.map((chain) => `http://${getChainName(String(chain.id))}-genesis.$(NAMESPACE).svc.cluster.local:26657`)
.join(',');
}
/**
* Returns comma-separated list of RPC addresses
*/
export function getChainRpcAddrs(chains, config) {
const localhost = config.registry?.localhost;
const ingress = config.ingress;
return chains
.map((chain) => {
if (localhost && chain.ports?.rpc) {
return `http://localhost:${chain.ports.rpc}`;
}
else if (ingress?.enabled && ingress.host) {
const host = ingress.host.replace('*.', '');
return `https://rpc.${chain.id}-genesis.${host}`;
}
else {
return `http://${getChainName(String(chain.id))}-genesis.$(NAMESPACE).svc.cluster.local:26657`;
}
})
.join(',');
}
/**
* Returns comma-separated list of GRPC addresses
*/
export function getChainGrpcAddrs(chains, config) {
const localhost = config.registry?.localhost;
const ingress = config.ingress;
return chains
.map((chain) => {
if (localhost && chain.ports?.grpc) {
return `http://localhost:${chain.ports.grpc}`;
}
else if (ingress?.enabled && ingress.host) {
const host = ingress.host.replace('*.', '');
return `https://grpc.${chain.id}-genesis.${host}`;
}
else {
return `http://${getChainName(String(chain.id))}-genesis.$(NAMESPACE).svc.cluster.local:9091`;
}
})
.join(',');
}
/**
* Returns comma-separated list of REST addresses
*/
export function getChainRestAddrs(chains, config) {
const localhost = config.registry?.localhost;
const ingress = config.ingress;
return chains
.map((chain) => {
if (localhost && chain.ports?.rest) {
return `http://localhost:${chain.ports.rest}`;
}
else if (ingress?.enabled && ingress.host) {
const host = ingress.host.replace('*.', '');
return `https://rest.${chain.id}-genesis.${host}`;
}
else {
return `http://${getChainName(String(chain.id))}-genesis.$(NAMESPACE).svc.cluster.local:1317`;
}
})
.join(',');
}
/**
* Returns comma-separated list of exposer addresses
*/
export function getChainExposerAddrs(chains, port = 8081) {
return chains
.map((chain) => `http://${getChainName(String(chain.id))}-genesis.$(NAMESPACE).svc.cluster.local:${port}`)
.join(',');
}
/**
* Generate init container for waiting on chains to be ready
*/
export function generateWaitInitContainer(chainIDs, port, config) {
const waitScript = chainIDs
.map((chainID) => `
while [ $(curl -sw '%{http_code}' http://${getChainName(String(chainID))}-genesis.$NAMESPACE.svc.cluster.local:$GENESIS_PORT/node_id -o /dev/null) -ne 200 ]; do
echo "Genesis validator does not seem to be ready for: ${chainID}. Waiting for it to start..."
echo "Checking: http://${getChainName(String(chainID))}-genesis.$NAMESPACE.svc.cluster.local:$GENESIS_PORT/node_id"
sleep 10;
done`)
.join('\n');
return {
name: 'wait-for-chains',
image: 'curlimages/curl:latest',
imagePullPolicy: config?.images?.imagePullPolicy || 'IfNotPresent',
env: [
{ name: 'GENESIS_PORT', value: String(port) },
{
name: 'NAMESPACE',
valueFrom: {
fieldRef: {
fieldPath: 'metadata.namespace'
}
}
}
],
command: ['/bin/sh', '-c', `${waitScript}\necho "Ready to start"\nexit 0`],
resources: getResourceObject(config?.resources?.wait || { cpu: '0.1', memory: '128M' })
};
}
/**
* Generate image pull secrets
*/
export function generateImagePullSecrets(imagePullSecrets) {
if (!imagePullSecrets || imagePullSecrets.length === 0) {
return null;
}
return {
imagePullSecrets: imagePullSecrets.map((secret) => ({
name: secret.name
}))
};
}
/**
* Extract tag from docker image
*/
export function extractImageTag(image) {
const match = image.match(/[^:]+$/);
return match ? match[0] : 'latest';
}
/**
* Generate volume mounts for chain containers
*/
export function generateChainVolumeMounts(chain) {
return [
{
mountPath: chain.home,
name: 'node'
},
{
mountPath: '/configs',
name: 'addresses'
},
{
mountPath: '/scripts',
name: 'scripts'
}
];
}
/**
* Generate standard volumes for chain pods
*/
export function generateChainVolumes(chain) {
const volumes = [
{
name: 'node',
emptyDir: {}
},
{
name: 'addresses',
configMap: {
name: 'keys'
}
},
{
name: 'scripts',
configMap: {
name: `setup-scripts-${getChainName(String(chain.id))}`
}
}
];
// Add patch volume if genesis override exists
if (chain.genesis) {
volumes.push({
name: 'patch',
configMap: {
name: `patch-${getChainName(String(chain.id))}`
}
});
}
// Add faucet volume if starship faucet is enabled
if (chain.faucet?.enabled && chain.faucet.type === 'starship') {
volumes.push({
name: 'faucet',
emptyDir: {}
});
}
// Add proposal volume if ICS is enabled
if (chain.ics?.enabled) {
volumes.push({
name: 'proposal',
configMap: {
name: `consumer-proposal-${getChainName(String(chain.id))}`
}
});
}
return volumes;
}
export function getHostname(chain) {
return getChainName(String(chain.id));
}
export function getChainId(chain) {
return String(chain.id);
}