diff --git a/README.md b/README.md index 4fdf3e1..f4e79bf 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ ## Usage +Please check [Releases](https://github.com/codefresh-io/index-alignment/releases) for the relevant version. Version suffix match the corresponding on-premise version, ie this tool `v1.0.2-2.8` is for Codefresh On-prem `v2.8`. + ```shell docker run quay.io/codefresh/index-alignment: --help ``` @@ -19,7 +21,21 @@ Commands: help [command] display help for command ``` +### TLS + +To use, TLS certificates must be mounted into the container. Please read the help for the corresponding command to learn more about the available TLS flags. +```shell +docker run \ + --volume :/tmp/cert.pem \ + --volume :/tmp/ca.pem \ + quay.io/codefresh/index-alignment: \ + stats \ + --tls \ + --tlsCertificateKeyFile="/tmp/cert.pem" \ + --tlsCAFile="/tmp/ca.pem" \ + --uri= +``` ### Commands @@ -38,6 +54,15 @@ We recommend redirecting the output of `compare` command to JSON file. Options: -p, --product Codefresh product: classic | gitops -u, --uri MongoDB URI + --tls Use TLS for the connection. If you are using a self-signed certificate, + you may also need to specify "--tlsCAFile" and/or + "--tlsCertificateKeyFile" (default: false) + --tlsInsecure Allow insecure TLS connections (do not validate CA) (default: false) + --tlsCAFile Specifies the location of a local .pem file that contains the root + certificate chain from the Certificate Authority. This file is used to + validate the certificate presented by the mongod/mongos instance + --tlsCertificateKeyFile Specifies the location of a local .pem file that contains either the + client's TLS/SSL certificate and key -m --db-map [dump-db-name=target-db-name...] Map the databases in the dump with the target databases. We have our own naming convention for the production databases, but it is up to the customers to name their databases (default: ["google_production=codefresh","chart-manager=charts-manager","kubernetes-monitor=k8s-monitor"]) -h, --help display help for command ``` @@ -73,8 +98,17 @@ We recommend redirecting the output of `stats` command to JSON file. ``` Options: - -u, --uri MongoDB URI - -h, --help display help for command + -u, --uri MongoDB URI + --tls Use TLS for the connection. If you are using a self-signed certificate, + you may also need to specify "--tlsCAFile" and/or + "--tlsCertificateKeyFile" (default: false) + --tlsInsecure Allow insecure TLS connections (do not validate CA) (default: false) + --tlsCAFile Specifies the location of a local .pem file that contains the root + certificate chain from the Certificate Authority. This file is used to + validate the certificate presented by the mongod/mongos instance + --tlsCertificateKeyFile Specifies the location of a local .pem file that contains either the + client's TLS/SSL certificate and key + -h, --help display help for command ``` Example: @@ -97,6 +131,15 @@ Sync indexes from a recommended dump with a target MongoDB instance. The command Options: -p, --product Codefresh product: classic | gitops -u, --uri MongoDB URI + --tls Use TLS for the connection. If you are using a self-signed certificate, + you may also need to specify "--tlsCAFile" and/or + "--tlsCertificateKeyFile" (default: false) + --tlsInsecure Allow insecure TLS connections (do not validate CA) (default: false) + --tlsCAFile Specifies the location of a local .pem file that contains the root + certificate chain from the Certificate Authority. This file is used to + validate the certificate presented by the mongod/mongos instance + --tlsCertificateKeyFile Specifies the location of a local .pem file that contains either the + client's TLS/SSL certificate and key -f --force Create indexes even on heavily populated collections, which may take a while -m --db-map [dump-db-name=target-db-name...] Map the databases in the dump with the target databases. We have our own naming convention for the production databases, but it is up to the customers to name their databases (default: ["google_production=codefresh","chart-manager=charts-manager","kubernetes-monitor=k8s-monitor"]) -h, --help display help for command diff --git a/package.json b/package.json index 143f505..f30c33e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "index-alignment", - "version": "0.0.2", + "version": "0.1.0", "main": "dist/index.js", "private": true, "type": "module", diff --git a/src/commands/dump-all-indexes.ts b/src/commands/dump-all-indexes.ts index c8aa9fe..1181ae9 100644 --- a/src/commands/dump-all-indexes.ts +++ b/src/commands/dump-all-indexes.ts @@ -1,18 +1,18 @@ -import { MongoClient } from 'mongodb'; import { mkdir, writeFile } from 'node:fs/promises'; import { isAbsolute, resolve } from 'node:path'; import { cwd } from 'node:process'; import { getAllIndexes } from '../get-indexes.js'; +import { getMongoClient } from '../get-mongo-client.js'; import { logger } from '../logger.js'; import type { DumpOptions } from '../types.js'; export const dumpAllIndexes = async (options: DumpOptions): Promise => { - const { uri, path } = options; + const { path } = options; const dumpDirPath = isAbsolute(path) ? path : resolve(cwd(), path); logger.stderr(`Dumping all indexes to "${dumpDirPath}". Hidden indexes will be ignored. Only databases and collections with authorized access will be dumped.`); await mkdir(dumpDirPath, { recursive: true }); - const client = new MongoClient(uri); + const client = getMongoClient(options); await client.connect(); logger.stderr('Connected to MongoDB'); diff --git a/src/commands/stats.ts b/src/commands/stats.ts index bed7520..f0d8449 100644 --- a/src/commands/stats.ts +++ b/src/commands/stats.ts @@ -1,4 +1,5 @@ -import { Collection, Db, Document, MongoClient } from 'mongodb'; +import type { Collection, Db, Document, MongoClient } from 'mongodb'; +import { getMongoClient } from '../get-mongo-client.js'; import { logger } from '../logger.js'; import type { CollectionStats, DatabaseStats, StatsOptions } from '../types.js'; @@ -74,10 +75,9 @@ export const getAllStats = async (client: MongoClient): Promise => { }; export const stats = async (options: StatsOptions): Promise => { - const { uri } = options; logger.stderr('Reading MongoDB stats. Only databases and collections with authorized access will be covered'); - const client = new MongoClient(uri); + const client = getMongoClient(options); await client.connect(); logger.stderr('Connected to MongoDB'); diff --git a/src/commands/sync.ts b/src/commands/sync.ts index 8af7278..0e0dbb5 100644 --- a/src/commands/sync.ts +++ b/src/commands/sync.ts @@ -1,7 +1,8 @@ -import { Collection, Db, MongoClient } from 'mongodb'; +import type { Collection, Db } from 'mongodb'; import { compareDump } from '../compare-dump.js'; import { heavyCollections, indexLimitPerCollection } from '../config.js'; import { getCollectionIndexes } from '../get-indexes.js'; +import { getMongoClient } from '../get-mongo-client.js'; import { logger } from '../logger.js'; import type { CollectionDiff, DatabaseDiff, Index, SyncOptions } from '../types.js'; import { getTargetToDumpDb } from '../utils.js'; @@ -134,7 +135,7 @@ const syncDatabase = async (db: Db, diff: DatabaseDiff, options: SyncOptions): P export const sync = async (options: SyncOptions): Promise => { logger.stderr(`Syncing indexes for "${options.product}"`); const diff = await compareDump(options); - const client = new MongoClient(options.uri); + const client = getMongoClient(options); await client.connect(); for (const [databaseName, databaseDiff] of Object.entries(diff.databases)) { const db = client.db(databaseName); diff --git a/src/compare-dump.ts b/src/compare-dump.ts index c710d86..f833cd1 100644 --- a/src/compare-dump.ts +++ b/src/compare-dump.ts @@ -1,11 +1,10 @@ -import { MongoClient } from 'mongodb'; import { getAllIndexes } from './get-indexes.js'; +import { getMongoClient } from './get-mongo-client.js'; import { isIndexEqual } from './is-index-equal.js'; import { shouldIgnoreIndexInDump } from './overrides/should-ignore-index-in-dump.js'; - +import { shouldIgnoreIndexInTarget } from './overrides/should-ignore-index-in-target.js'; import { readDump } from './read-dump.js'; import type { CollectionDiff, CollectionIndexes, CompareOptions, DatabaseDiff, DatabaseIndexes, DbMapRaw, FullDiff } from './types.js'; -import { shouldIgnoreIndexInTarget } from './overrides/should-ignore-index-in-target.js'; import { getTargetToDumpDb } from './utils.js'; const compareCollections = (desired: CollectionIndexes, actual?: CollectionIndexes, dbMap?: DbMapRaw): CollectionDiff => { @@ -47,9 +46,10 @@ const compareDatabases = (desired: DatabaseIndexes, actual?: DatabaseIndexes, db return dbDiff; }; -export const compareDump = async ({ product, uri, dbMap }: CompareOptions): Promise => { +export const compareDump = async (options: CompareOptions): Promise => { + const { product, dbMap } = options; const desired = await readDump(product, dbMap); - const client = new MongoClient(uri); + const client = getMongoClient(options); await client.connect(); const actual = await getAllIndexes(client); diff --git a/src/get-mongo-client.ts b/src/get-mongo-client.ts new file mode 100644 index 0000000..abb3337 --- /dev/null +++ b/src/get-mongo-client.ts @@ -0,0 +1,13 @@ +import { MongoClient, MongoClientOptions } from 'mongodb'; +import { logger } from './logger.js'; + +export const getMongoClient = (options: Partial & { uri: string }) => { + const clientOptions = { + tls: options.tls, + tlsInsecure: options.tlsInsecure, + tlsCAFile: options.tlsCAFile, + tlsCertificateKeyFile: options.tlsCertificateKeyFile, + } satisfies MongoClientOptions; + logger.stderr(`The following options will be used for the MongoDB connection: ${JSON.stringify(clientOptions, null, 2)}`); + return new MongoClient(options.uri, clientOptions); +}; diff --git a/src/index.ts b/src/index.ts index 8caa914..d3bc4bb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,12 +10,48 @@ program .description('[Internal] Dump all indexes from a MongoDB instance') .requiredOption('-p, --path ', 'Path to index dump') .requiredOption('-u, --uri ', 'MongoDB URI') + .option( + '--tls', + `Use TLS for the connection. If you are using a self-signed certificate, you may also need to specify "--tlsCAFile" and/or "--tlsCertificateKeyFile"`, + false, + ) + .option( + '--tlsInsecure', + 'Allow insecure TLS connections (do not validate CA)', + false, + ) + .option( + '--tlsCAFile ', + 'Specifies the location of a local .pem file that contains the root certificate chain from the Certificate Authority. This file is used to validate the certificate presented by the mongod/mongos instance', + ) + .option( + '--tlsCertificateKeyFile ', + `Specifies the location of a local .pem file that contains either the client's TLS/SSL certificate and key`, + ) .action(dumpAllIndexes); program .command('stats') .description('Get stats for all collections from a MongoDB instance') .requiredOption('-u, --uri ', 'MongoDB URI') + .option( + '--tls', + `Use TLS for the connection. If you are using a self-signed certificate, you may also need to specify "--tlsCAFile" and/or "--tlsCertificateKeyFile"`, + false, + ) + .option( + '--tlsInsecure', + 'Allow insecure TLS connections (do not validate CA)', + false, + ) + .option( + '--tlsCAFile ', + 'Specifies the location of a local .pem file that contains the root certificate chain from the Certificate Authority. This file is used to validate the certificate presented by the mongod/mongos instance', + ) + .option( + '--tlsCertificateKeyFile ', + `Specifies the location of a local .pem file that contains either the client's TLS/SSL certificate and key`, + ) .action(stats); program @@ -23,6 +59,24 @@ program .description('Compare indexes from a target MongoDB instance with a recommended dump') .requiredOption('-p, --product ', 'Codefresh product: classic | gitops') .requiredOption('-u, --uri ', 'MongoDB URI') + .option( + '--tls', + `Use TLS for the connection. If you are using a self-signed certificate, you may also need to specify "--tlsCAFile" and/or "--tlsCertificateKeyFile"`, + false, + ) + .option( + '--tlsInsecure', + 'Allow insecure TLS connections (do not validate CA)', + false, + ) + .option( + '--tlsCAFile ', + 'Specifies the location of a local .pem file that contains the root certificate chain from the Certificate Authority. This file is used to validate the certificate presented by the mongod/mongos instance', + ) + .option( + '--tlsCertificateKeyFile ', + `Specifies the location of a local .pem file that contains either the client's TLS/SSL certificate and key`, + ) .option( '-m --db-map [dump-db-name=target-db-name...]', 'Map the databases in the dump with the target databases. We have our own naming convention for the production databases, but it is up to the customers to name their databases', @@ -32,9 +86,27 @@ program program .command('sync') - .description('Sync indexes from a recommended dump with a target MongoDB instance. The command will fail if it is required to create indexes on heavily populated collections and the `--force` flag has not been specified') + .description('[ ⚠️ Warning! Do not run this command against production. ] Sync indexes from a recommended dump with a target MongoDB instance. The command will fail if it is required to create indexes on heavily populated collections and the `--force` flag has not been specified') .requiredOption('-p, --product ', 'Codefresh product: classic | gitops') .requiredOption('-u, --uri ', 'MongoDB URI') + .option( + '--tls', + `Use TLS for the connection. If you are using a self-signed certificate, you may also need to specify "--tlsCAFile" and/or "--tlsCertificateKeyFile"`, + false, + ) + .option( + '--tlsInsecure', + 'Allow insecure TLS connections (do not validate CA)', + false, + ) + .option( + '--tlsCAFile ', + 'Specifies the location of a local .pem file that contains the root certificate chain from the Certificate Authority. This file is used to validate the certificate presented by the mongod/mongos instance', + ) + .option( + '--tlsCertificateKeyFile ', + `Specifies the location of a local .pem file that contains either the client's TLS/SSL certificate and key`, + ) .option('-f --force', 'Create indexes even on heavily populated collections, which may take a while') .option( '-m --db-map [dump-db-name=target-db-name...]', diff --git a/src/types.ts b/src/types.ts index bfc3e34..5e0245d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,4 @@ -import type { Document, IndexDescription } from 'mongodb'; +import type { Document, IndexDescription, MongoClientOptions } from 'mongodb'; export type Product = 'classic' | 'gitops'; @@ -61,24 +61,24 @@ export interface DatabaseStats { } // Options -export interface SyncOptions { +export interface SyncOptions extends Partial { uri: string; product: Product; dbMap?: DbMapRaw; force?: boolean; } -export interface CompareOptions { +export interface CompareOptions extends Partial { uri: string; product: Product; dbMap?: DbMapRaw; } -export interface StatsOptions { +export interface StatsOptions extends Partial { uri: string; } -export interface DumpOptions { +export interface DumpOptions extends Partial { uri: string; path: string; }