@starship-ci/generator
Version:
Kubernetes manifest generator for Starship deployments
437 lines (421 loc) • 18.7 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.CosmosValidatorStatefulSetGenerator = void 0;
const defaults_1 = require("../../../defaults");
const helpers = __importStar(require("../../../helpers"));
const version_1 = require("../../../version");
class CosmosValidatorStatefulSetGenerator {
config;
chain;
scriptManager;
defaultsManager;
constructor(chain, config, scriptManager) {
this.config = config;
this.chain = chain;
this.scriptManager = scriptManager;
this.defaultsManager = new defaults_1.DefaultsManager();
}
labels() {
const processedChain = this.defaultsManager.processChain(this.chain);
return {
...helpers.getCommonLabels(this.config),
'app.kubernetes.io/component': 'chain',
'app.kubernetes.io/part-of': helpers.getChainId(processedChain),
'app.kubernetes.io/id': helpers.getChainId(processedChain),
'app.kubernetes.io/name': `${helpers.getHostname(processedChain)}-validator`,
'app.kubernetes.io/type': `${helpers.getChainId(processedChain)}-statefulset`,
'app.kubernetes.io/role': 'validator',
'starship.io/chain-name': processedChain.name
};
}
generate() {
const processedChain = this.defaultsManager.processChain(this.chain);
return [
{
apiVersion: 'apps/v1',
kind: 'StatefulSet',
metadata: {
name: `${helpers.getHostname(processedChain)}-validator`,
labels: this.labels()
},
spec: {
serviceName: `${helpers.getHostname(processedChain)}-validator`,
podManagementPolicy: 'Parallel',
replicas: (processedChain.numValidators || 1) - 1,
revisionHistoryLimit: 3,
selector: {
matchLabels: {
'app.kubernetes.io/instance': this.config.name,
'app.kubernetes.io/name': `${helpers.getChainId(processedChain)}-validator`
}
},
template: {
metadata: {
annotations: {
quality: 'release',
role: 'api-gateway',
sla: 'high',
tier: 'gateway'
},
labels: {
'app.kubernetes.io/instance': this.config.name,
'app.kubernetes.io/type': helpers.getChainId(processedChain),
'app.kubernetes.io/name': `${helpers.getChainId(processedChain)}-validator`,
'app.kubernetes.io/version': (0, version_1.getGeneratorVersion)(),
'app.kubernetes.io/role': 'validator'
}
},
spec: {
...(processedChain.imagePullSecrets
? helpers.generateImagePullSecrets(processedChain.imagePullSecrets)
: {}),
initContainers: this.createInitContainers(processedChain),
containers: this.createMainContainers(processedChain),
volumes: helpers.generateChainVolumes(processedChain)
}
}
}
}
];
}
createInitContainers(chain) {
const initContainers = [];
// Build images init container if needed
if (chain.build?.enabled || chain.upgrade?.enabled) {
initContainers.push(this.createBuildImagesInitContainer(chain));
}
// Wait for genesis node to be ready
initContainers.push(this.createWaitInitContainer(chain));
// Validator init container
initContainers.push(this.createValidatorInitContainer(chain));
// Validator config init container
initContainers.push(this.createValidatorConfigContainer(chain));
// ICS init container if enabled
if (chain.ics?.enabled) {
initContainers.push(this.createIcsInitContainer(chain));
}
return initContainers;
}
createMainContainers(chain) {
const containers = [];
// Main validator container
containers.push(this.createValidatorContainer(chain));
// Exposer container
containers.push(this.createExposerContainer(chain));
return containers;
}
createWaitInitContainer(chain) {
const exposerPort = this.config.exposer?.ports?.rest || 8081;
return helpers.generateWaitInitContainer([helpers.getChainId(chain)], exposerPort, this.config);
}
createIcsInitContainer(chain) {
const providerChainId = chain.ics?.provider;
const providerHostname = helpers.getChainName(providerChainId);
const providerChain = this.config.chains.find((c) => c.id === providerChainId);
return {
name: 'init-ics',
image: providerChain?.image, // Should use provider chain image in real implementation
imagePullPolicy: this.config.images?.imagePullPolicy || 'IfNotPresent',
env: [
...helpers.getDefaultEnvVars(chain),
...helpers.getChainEnvVars(chain),
{
name: 'NAMESPACE',
valueFrom: {
fieldRef: {
fieldPath: 'metadata.namespace'
}
}
},
{ name: 'KEYS_CONFIG', value: '/configs/keys.json' }
],
command: ['bash', '-c', this.getIcsInitScript(chain, providerHostname)],
resources: helpers.getNodeResources(chain, this.config),
volumeMounts: helpers.generateChainVolumeMounts(chain)
};
}
createBuildImagesInitContainer(chain) {
const buildCommands = [
'# Install cosmovisor',
'go install github.com/cosmos/cosmos-sdk/cosmovisor/cmd/cosmovisor@v1.0.0',
'',
'# Build genesis'
];
if (chain.upgrade?.enabled) {
// Build genesis version
buildCommands.push(`UPGRADE_NAME=genesis CODE_TAG=${chain.upgrade.genesis} bash -e /scripts/build-chain.sh`);
// Build upgrade versions
if (chain.upgrade.upgrades) {
chain.upgrade.upgrades.forEach((upgrade) => {
buildCommands.push(`UPGRADE_NAME=${upgrade.name} CODE_TAG=${upgrade.version} bash -e /scripts/build-chain.sh`);
});
}
}
else if (chain.build?.enabled) {
buildCommands.push(`UPGRADE_NAME=genesis CODE_TAG=${chain.build.source} bash -e /scripts/build-chain.sh`);
}
return {
name: 'init-build-images',
image: 'ghcr.io/cosmology-tech/starship/builder:latest',
imagePullPolicy: 'IfNotPresent',
command: ['bash', '-c', buildCommands.join('\n')],
env: [
{ name: 'CODE_REF', value: chain.repo },
{ name: 'UPGRADE_DIR', value: `${chain.home}/cosmovisor` },
{ name: 'GOBIN', value: '/go/bin' },
{ name: 'CHAIN_NAME', value: helpers.getChainId(chain) },
...helpers.getDefaultEnvVars(chain)
],
resources: helpers.getNodeResources(chain, this.config),
volumeMounts: helpers.generateChainVolumeMounts(chain)
};
}
createValidatorInitContainer(chain) {
return {
name: 'init-validator',
image: chain.image,
imagePullPolicy: this.config.images?.imagePullPolicy || 'IfNotPresent',
env: [
...helpers.getDefaultEnvVars(chain),
...helpers.getChainEnvVars(chain),
...helpers.getTimeoutEnvVars(this.config.timeouts || {}),
...helpers.getGenesisEnvVars(chain, this.config.exposer?.ports?.rest || 8081),
{ name: 'KEYS_CONFIG', value: '/configs/keys.json' },
{
name: 'FAUCET_ENABLED',
value: String(chain.faucet?.enabled || false)
},
{ name: 'METRICS', value: String(chain.metrics || false) }
],
command: ['bash', '-c', this.getValidatorInitScript(chain)],
resources: helpers.getNodeResources(chain, this.config),
volumeMounts: helpers.generateChainVolumeMounts(chain)
};
}
createValidatorConfigContainer(chain) {
return {
name: 'init-config',
image: chain.image,
imagePullPolicy: this.config.images?.imagePullPolicy || 'IfNotPresent',
env: [
...helpers.getDefaultEnvVars(chain),
...helpers.getChainEnvVars(chain),
...helpers.getTimeoutEnvVars(this.config.timeouts || {}),
...helpers.getGenesisEnvVars(chain, this.config.exposer?.ports?.rest || 8081),
{ name: 'KEYS_CONFIG', value: '/configs/keys.json' },
{ name: 'METRICS', value: String(chain.metrics || false) }
],
command: ['bash', '-c', this.getValidatorConfigScript(chain)],
resources: helpers.getNodeResources(chain, this.config),
volumeMounts: helpers.generateChainVolumeMounts(chain)
};
}
createValidatorContainer(chain) {
return {
name: 'validator',
image: chain.image,
imagePullPolicy: this.config.images?.imagePullPolicy || 'IfNotPresent',
env: [
...helpers.getDefaultEnvVars(chain),
...helpers.getChainEnvVars(chain),
...helpers.getGenesisEnvVars(chain, this.config.exposer?.ports?.rest || 8081),
{ name: 'KEYS_CONFIG', value: '/configs/keys.json' },
{ name: 'SLOGFILE', value: 'slog.slog' },
...(chain.env || []).map((env) => ({
name: env.name,
value: String(env.value)
}))
],
command: ['bash', '-c', this.getValidatorStartScript(chain)],
resources: helpers.getNodeResources(chain, this.config),
volumeMounts: helpers.generateChainVolumeMounts(chain),
...(chain.cometmock?.enabled || chain.ics?.enabled
? {}
: {
lifecycle: {
postStart: {
exec: {
command: [
'bash',
'-c',
'-e',
this.getValidatorPostStartScript(chain)
]
}
}
}
}),
...(chain.cometmock?.enabled
? {}
: {
readinessProbe: chain.readinessProbe || {
exec: {
command: [
'bash',
'-e',
'/scripts/chain-rpc-ready.sh',
'http://localhost:26657'
]
},
initialDelaySeconds: 10,
periodSeconds: 10,
timeoutSeconds: 15
}
})
};
}
createExposerContainer(chain) {
return {
name: 'exposer',
image: this.config.exposer?.image ||
'ghcr.io/cosmology-tech/starship/exposer:latest',
imagePullPolicy: this.config.images?.imagePullPolicy || 'IfNotPresent',
env: [
...helpers.getDefaultEnvVars(chain),
...helpers.getChainEnvVars(chain),
...helpers.getGenesisEnvVars(chain, this.config.exposer?.ports?.rest || 8081),
{ name: 'EXPOSER_HTTP_PORT', value: '8081' },
{ name: 'EXPOSER_GRPC_PORT', value: '9099' },
{
name: 'EXPOSER_GENESIS_FILE',
value: `${chain.home}/config/genesis.json`
},
{ name: 'EXPOSER_MNEMONIC_FILE', value: '/configs/keys.json' },
{
name: 'EXPOSER_PRIV_VAL_FILE',
value: `${chain.home}/config/priv_validator_key.json`
},
{
name: 'EXPOSER_NODE_KEY_FILE',
value: `${chain.home}/config/node_key.json`
},
{
name: 'EXPOSER_PRIV_VAL_STATE_FILE',
value: `${chain.home}/data/priv_validator_state.json`
}
],
command: ['exposer'],
resources: helpers.getResourceObject(this.config.exposer?.resources || { cpu: '0.1', memory: '128M' }),
volumeMounts: [
{ mountPath: chain.home, name: 'node' },
{ mountPath: '/configs', name: 'addresses' }
]
};
}
getIcsInitScript(chain, providerHostname) {
return `
VAL_INDEX=\${HOSTNAME##*-}
echo "Validator Index: $VAL_INDEX"
echo "Fetching priv keys from provider exposer"
curl -s http://${providerHostname}-validator-$VAL_INDEX.${providerHostname}-validator.$NAMESPACE.svc.cluster.local:8081/priv_keys | jq > $CHAIN_DIR/config/provider_priv_validator_key.json
cat $CHAIN_DIR/config/provider_priv_validator_key.json
echo "Replace provider priv validator key with provider keys"
mv $CHAIN_DIR/config/priv_validator_key.json $CHAIN_DIR/config/previous_priv_validator_key.json
mv $CHAIN_DIR/config/provider_priv_validator_key.json $CHAIN_DIR/config/priv_validator_key.json
`.trim();
}
getValidatorInitScript(chain) {
const toBuild = chain.build?.enabled || chain.upgrade?.enabled;
return `
VAL_INDEX=\${HOSTNAME##*-}
echo "Validator Index: $VAL_INDEX"
${toBuild ? 'cp $CHAIN_DIR/cosmovisor/genesis/bin/$CHAIN_BIN /usr/bin' : ''}
if [ -f $CHAIN_DIR/config/genesis.json ]; then
echo "Genesis file exists, exiting early"
exit 0
fi
VAL_NAME=$(jq -r ".validators[0].name" $KEYS_CONFIG)-$VAL_INDEX
echo "Validator Index: $VAL_INDEX, Key name: $VAL_NAME"
echo "Recover validator $VAL_NAME"
$CHAIN_BIN init $VAL_NAME --chain-id $CHAIN_ID
jq -r ".validators[0].mnemonic" $KEYS_CONFIG | $CHAIN_BIN keys add $VAL_NAME --index $VAL_INDEX --recover --keyring-backend="test"
curl http://$GENESIS_HOST.$NAMESPACE.svc.cluster.local:$GENESIS_PORT/genesis -o $CHAIN_DIR/config/genesis.json
echo "Genesis file that we got....."
cat $CHAIN_DIR/config/genesis.json
echo "Create node id json file"
NODE_ID=$($CHAIN_BIN tendermint show-node-id)
echo '{"node_id":"'$NODE_ID'"}' > $CHAIN_DIR/config/node_id.json
`.trim();
}
getValidatorConfigScript(chain) {
const toBuild = chain.build?.enabled || chain.upgrade?.enabled;
return `
VAL_INDEX=\${HOSTNAME##*-}
echo "Validator Index: $VAL_INDEX"
${toBuild ? 'cp $CHAIN_DIR/cosmovisor/genesis/bin/$CHAIN_BIN /usr/bin' : ''}
echo "Running setup config script..."
bash -e /scripts/update-config.sh
curl -s http://$GENESIS_HOST.$NAMESPACE.svc.cluster.local:$GENESIS_PORT/node_id
NODE_ID=$(curl -s http://$GENESIS_HOST.$NAMESPACE.svc.cluster.local:$GENESIS_PORT/node_id | jq -r ".node_id")
if [[ $NODE_ID == "" ]]; then
echo "Node ID is null, exiting early"
exit 1
fi
GENESIS_NODE_P2P=$NODE_ID@$GENESIS_HOST.$NAMESPACE.svc.cluster.local:26656
echo "Node P2P: $GENESIS_NODE_P2P"
sed -i "s/persistent_peers = \\"\\"/persistent_peers = \\"$GENESIS_NODE_P2P\\"/g" $CHAIN_DIR/config/config.toml
echo "Printing the whole config.toml file"
cat $CHAIN_DIR/config/config.toml
`.trim();
}
getValidatorStartScript(chain) {
const toBuild = chain.build?.enabled || chain.upgrade?.enabled;
return `
set -eux
START_ARGS=""
${chain.cometmock?.enabled ? 'START_ARGS="--grpc-web.enable=false --transport=grpc --with-tendermint=false --address tcp://0.0.0.0:26658"' : ''}
# Starting the chain
${toBuild
? `
cp $CHAIN_DIR/cosmovisor/genesis/bin/$CHAIN_BIN /usr/bin
/usr/bin/cosmovisor start $START_ARGS`
: `
$CHAIN_BIN start $START_ARGS`}
`.trim();
}
getValidatorPostStartScript(chain) {
return `
until bash -e /scripts/chain-rpc-ready.sh http://localhost:26657; do
sleep 10
done
set -eux
export
VAL_INDEX=\${HOSTNAME##*-}
VAL_NAME="$(jq -r ".validators[0].name" $KEYS_CONFIG)-$VAL_INDEX"
echo "Validator Index: $VAL_INDEX, Key name: $VAL_NAME. Chain bin $CHAIN_BIN"
VAL_ADDR=$($CHAIN_BIN keys show $VAL_NAME -a --keyring-backend="test")
echo "Transfer tokens to address $VAL_ADDR before trying to create validator. Best effort"
bash -e /scripts/transfer-tokens.sh \\
$VAL_ADDR \\
$DENOM \\
http://$GENESIS_HOST.$NAMESPACE.svc.cluster.local:8000/credit \\
"${chain.faucet?.enabled || false}" || true
$CHAIN_BIN keys list --keyring-backend test | jq
VAL_NAME=$VAL_NAME bash -e /scripts/create-validator.sh
`.trim();
}
}
exports.CosmosValidatorStatefulSetGenerator = CosmosValidatorStatefulSetGenerator;