UNPKG

heroku-kafka

Version:
164 lines (163 loc) 6.58 kB
import { Command, flags } from '@heroku-cli/command'; import { color, hux } from '@heroku/heroku-cli-util'; import { Args, ux } from '@oclif/core'; import humanize from 'humanize-plus'; import { request } from '../../lib/clusters.js'; import fetcherFn from '../../lib/fetcher.js'; import utilizationBar from '../../lib/utilization-bar.js'; const VERSION = 'v0'; function configVarsFromName(attachments, name) { return attachments .filter((att) => att.addon.name === name) .map((att) => att.name + '_URL') .sort((a, b) => { if (a === 'KAFKA_URL') return -1; if (b === 'KAFKA_URL') return 1; return a.localeCompare(b); }); } function formatInfo(info) { const { cluster } = info; const { addon } = info; const lines = [ { name: 'Plan', values: [addon.plan.name] }, { name: 'Status', values: [cluster.state.message] }, { name: 'Version', values: [cluster.version] }, { name: 'Created', values: [cluster.created_at] }, ]; if (cluster.robot.is_robot) { lines.push({ name: 'Robot', values: ['True'] }, { name: 'Robot TTL', values: [cluster.robot.robot_ttl] }); } const { limits } = cluster; // we hide __consumer_offsets in topic listing; don't count it const topicCount = cluster.topics.filter((topic) => topic !== '__consumer_offsets').length; if (limits.max_topics) { lines.push({ name: 'Topics', values: [`${utilizationBar(topicCount, limits.max_topics)} ${topicCount} / ${limits.max_topics} topics, see heroku kafka:topics`], }); } else { lines.push({ name: 'Topics', values: [`${topicCount} ${humanize.pluralize(topicCount, 'topic')}, see heroku kafka:topics`], }); } if (cluster.topic_prefix) { lines.push({ name: 'Prefix', values: [cluster.topic_prefix] }); } if (limits.max_partition_replica_count) { lines.push({ name: 'Partitions', values: [`${utilizationBar(cluster.partition_replica_count, limits.max_partition_replica_count)} ${cluster.partition_replica_count} / ${limits.max_partition_replica_count} partition ${humanize.pluralize(cluster.partition_replica_count, 'replica')} (partitions × replication factor)`], }); } lines.push({ name: 'Messages', values: [`${humanize.intComma(cluster.messages_in_per_sec)} ${humanize.pluralize(cluster.messages_in_per_sec, 'message')}/s`], }); lines.push({ name: 'Traffic', values: [`${humanize.fileSize(cluster.bytes_in_per_sec)}/s in / ${humanize.fileSize(cluster.bytes_out_per_sec)}/s out`], }); if (cluster.data_size !== undefined && limits.data_size.limit_bytes !== undefined) { const size = cluster.data_size; const limit = limits.data_size.limit_bytes; const percentage = ((size / limit) * 100.0).toFixed(2); lines.push({ name: 'Data Size', values: [`${utilizationBar(size, limit)} ${humanize.fileSize(size)} / ${humanize.fileSize(limit)} (${percentage}%)`], }); } if (cluster.customer_encryption_key) { lines.push({ name: 'Customer Encryption Key', values: [cluster.customer_encryption_key], }); } lines.push({ name: 'Add-on', values: [color.addon(addon.name)] }); return lines; } function displayCluster(cluster) { hux.styledHeader(cluster.configVars.map(c => color.label(c)).join(', ')); const clusterInfo = formatInfo(cluster); const info = clusterInfo.reduce((info, i) => { if (i.values.length > 0) { info[i.name] = i.values.join(', '); } return info; }, {}); const keys = clusterInfo.map(i => i.name); hux.styledObject(info, keys); console.log(); } export default class Info extends Command { static aliases = ['kafka']; static args = { cluster: Args.string({ description: 'cluster to operate on', required: false }), }; static description = 'display cluster information'; static examples = [ '$ heroku kafka', '$ heroku kafka:info', '$ heroku kafka:info HEROKU_KAFKA_BROWN_URL', ]; static flags = { app: flags.app({ required: true }), remote: flags.remote(), }; static topic = 'kafka'; async run() { const { args, flags } = await this.parse(Info); const fetcher = fetcherFn(this.heroku); const { app } = flags; const { cluster } = args; let addons = []; const { body: attachments } = await this.heroku.get(`/apps/${app}/addon-attachments`); if (cluster) { addons = await Promise.all([fetcher.addon(app, cluster)]); } else { addons = await fetcher.all(app); if (addons.length === 0) { ux.stdout(`${color.app(app)} has no heroku-kafka clusters.\n`); return; } } let clusters = await Promise.all(addons.map(async (addon) => { const clusterData = await request(this.heroku, { method: 'GET', path: `/data/kafka/${VERSION}/clusters/${addon.id}`, }).then((res) => res.body || res).catch((error) => { if (error.statusCode !== 404) throw error; const warnMsg = `${color.addon(addon.name)} is not yet provisioned.\nRun ${color.code('heroku kafka:wait')} to wait until the cluster is provisioned.`; ux.warn(warnMsg); return null; }); return { addon, attachments, cluster: clusterData, }; })); clusters = clusters.filter((cluster) => cluster.cluster); clusters.forEach((cluster) => { cluster.configVars = configVarsFromName(cluster.attachments, cluster.addon.name); }); clusters = clusters.sort((a, b) => { // Primary sort: KAFKA_URL comes first (false < true) const aIsNotKafkaUrl = a.configVars[0] !== 'KAFKA_URL'; const bIsNotKafkaUrl = b.configVars[0] !== 'KAFKA_URL'; if (aIsNotKafkaUrl !== bIsNotKafkaUrl) { return aIsNotKafkaUrl ? 1 : -1; } // Secondary sort: alphabetically by configVars[0] return a.configVars[0].localeCompare(b.configVars[0]); }); clusters.forEach(displayCluster); } } export { displayCluster };