From 1c98d8c008c76e33424fe5e07ab2f6ec96457b24 Mon Sep 17 00:00:00 2001 From: Zhenya Tikhonov Date: Tue, 1 Jul 2025 19:40:39 +0400 Subject: [PATCH 01/11] docs: document `indexStats` (#3) --- README.md | 2 ++ package.json | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c824fe..4fdf3e1 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,8 @@ Get stats for all collections from a MongoDB instance. Following commands will b * `$collStats` aggregation ([doc](https://www.mongodb.com/docs/v5.0/reference/operator/aggregation/collStats/)); +* `$indexStats` aggregation ([doc](https://www.mongodb.com/docs/v5.0/reference/operator/aggregation/indexStats/)); + * `$planCacheStats` aggregation ([doc](https://www.mongodb.com/docs/v5.0/reference/operator/aggregation/plancachestats/)); * queries `_id` of the oldest doc in each collection. diff --git a/package.json b/package.json index a01a4e1..143f505 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "name": "index-alignment", - "version": "0.0.1", + "version": "0.0.2", "main": "dist/index.js", + "private": true, "type": "module", "files": [ "dist/**/*" From 0b4064ee52298f01bbb87986400c40ede145224b Mon Sep 17 00:00:00 2001 From: Zhenya Tikhonov Date: Mon, 7 Jul 2025 11:02:07 +0400 Subject: [PATCH 02/11] feat: support TLS (#6) * docs: improve docs * feat: support TLS connections * docs: document TLS options --- README.md | 47 +++++++++++++++++++- package.json | 2 +- src/commands/dump-all-indexes.ts | 6 +-- src/commands/stats.ts | 6 +-- src/commands/sync.ts | 5 ++- src/compare-dump.ts | 10 ++--- src/get-mongo-client.ts | 13 ++++++ src/index.ts | 74 +++++++++++++++++++++++++++++++- src/types.ts | 10 ++--- 9 files changed, 151 insertions(+), 22 deletions(-) create mode 100644 src/get-mongo-client.ts 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; } From 7e23ec511061a4484aa36e796330315203c9f31d Mon Sep 17 00:00:00 2001 From: Zhenya Tikhonov Date: Thu, 17 Jul 2025 15:53:36 +0400 Subject: [PATCH 03/11] feat(options): add `tlsCertificateKeyFilePassword` flag (#8) * feat(options): add `tlsCertificateKeyFilePassword` flag * ci: trigger ci --- README.md | 3 +++ package.json | 2 +- src/get-mongo-client.ts | 1 + src/index.ts | 16 ++++++++++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f4e79bf..900f9c1 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ Options: 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 + --tlsCertificateKeyFilePassword Specifies the password to de-crypt the tlsCertificateKeyFile -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 ``` @@ -108,6 +109,7 @@ Options: 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 + --tlsCertificateKeyFilePassword Specifies the password to de-crypt the tlsCertificateKeyFile -h, --help display help for command ``` @@ -140,6 +142,7 @@ Options: 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 + --tlsCertificateKeyFilePassword Specifies the password to de-crypt the tlsCertificateKeyFile -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 f30c33e..8291bdd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "index-alignment", - "version": "0.1.0", + "version": "0.2.0", "main": "dist/index.js", "private": true, "type": "module", diff --git a/src/get-mongo-client.ts b/src/get-mongo-client.ts index abb3337..0db1153 100644 --- a/src/get-mongo-client.ts +++ b/src/get-mongo-client.ts @@ -7,6 +7,7 @@ export const getMongoClient = (options: Partial & { uri: str tlsInsecure: options.tlsInsecure, tlsCAFile: options.tlsCAFile, tlsCertificateKeyFile: options.tlsCertificateKeyFile, + tlsCertificateKeyFilePassword: options.tlsCertificateKeyFilePassword, } 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 d3bc4bb..3466a2e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,6 +28,10 @@ program '--tlsCertificateKeyFile ', `Specifies the location of a local .pem file that contains either the client's TLS/SSL certificate and key`, ) + .option( + '--tlsCertificateKeyFilePassword ', + 'Specifies the password to de-crypt the tlsCertificateKeyFile', + ) .action(dumpAllIndexes); program @@ -52,6 +56,10 @@ program '--tlsCertificateKeyFile ', `Specifies the location of a local .pem file that contains either the client's TLS/SSL certificate and key`, ) + .option( + '--tlsCertificateKeyFilePassword ', + 'Specifies the password to de-crypt the tlsCertificateKeyFile', + ) .action(stats); program @@ -77,6 +85,10 @@ program '--tlsCertificateKeyFile ', `Specifies the location of a local .pem file that contains either the client's TLS/SSL certificate and key`, ) + .option( + '--tlsCertificateKeyFilePassword ', + 'Specifies the password to de-crypt the tlsCertificateKeyFile', + ) .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', @@ -107,6 +119,10 @@ program '--tlsCertificateKeyFile ', `Specifies the location of a local .pem file that contains either the client's TLS/SSL certificate and key`, ) + .option( + '--tlsCertificateKeyFilePassword ', + 'Specifies the password to de-crypt the tlsCertificateKeyFile', + ) .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...]', From e3ce63fabb8582f33a0e9523d86835083787a499 Mon Sep 17 00:00:00 2001 From: alinashklyar Date: Fri, 25 Jul 2025 17:14:51 +0300 Subject: [PATCH 04/11] added logic to ingnore default collations --- src/is-index-equal.ts | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/is-index-equal.ts b/src/is-index-equal.ts index ce59da4..5fb0e9f 100644 --- a/src/is-index-equal.ts +++ b/src/is-index-equal.ts @@ -1,5 +1,6 @@ import { deepStrictEqual } from 'node:assert'; import type { Index } from './types.js'; +import { CollationOptions, Document } from 'mongodb'; /** * Compare two indexes to check if they are equal. @@ -20,16 +21,53 @@ import type { Index } from './types.js'; * * - If both `key` and options (except for `name`) are equal, the indexes are considered equal. */ + +const defaultCollation: CollationOptions = { + locale: 'en_US', + caseLevel: false, + caseFirst: 'off', + strength: 1, + numericOrdering: false, + alternate: 'non-ignorable', + maxVariable: 'punct', + normalization: false, + backwards: false, +}; + +function isDefaultCollation(collation: Document | undefined): boolean { + if (!collation) return false; + + const collationEntries = Object.entries(collation); + const defaultEntries = Object.entries(defaultCollation); + + if (collationEntries.length !== defaultEntries.length) return false; + + for (const [key, value] of collationEntries) { + if (defaultCollation[key as keyof CollationOptions] !== value) return false; + } + + return true; +} + export const isIndexEqual = (a: Index, b: Index): boolean => { const aKey = a.key; const aOptions = { ...a, key: undefined, name: undefined }; const bKey = b.key; const bOptions = { ...b, key: undefined, name: undefined }; const isKeyEqual = JSON.stringify(aKey) === JSON.stringify(bKey); + + if (isDefaultCollation(aOptions.collation)) delete aOptions.collation; + if (isDefaultCollation(bOptions.collation)) delete bOptions.collation; + try { deepStrictEqual(aOptions, bOptions); return isKeyEqual; - } catch { + } catch (e: any) { + console.log('Index options differ:', { + aOptions, + bOptions, + diff: e.message, + }); return false; } }; From 1e7ab5bf155a7e72c060de9e5eec734e8556f36a Mon Sep 17 00:00:00 2001 From: alinashklyar Date: Fri, 25 Jul 2025 17:15:46 +0300 Subject: [PATCH 05/11] removed catch --- src/is-index-equal.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/is-index-equal.ts b/src/is-index-equal.ts index 5fb0e9f..d5bdb63 100644 --- a/src/is-index-equal.ts +++ b/src/is-index-equal.ts @@ -62,12 +62,7 @@ export const isIndexEqual = (a: Index, b: Index): boolean => { try { deepStrictEqual(aOptions, bOptions); return isKeyEqual; - } catch (e: any) { - console.log('Index options differ:', { - aOptions, - bOptions, - diff: e.message, - }); + } catch { return false; } }; From 951fc515e3485f221fe7624e7ea11adfd75377d5 Mon Sep 17 00:00:00 2001 From: alinashklyar Date: Fri, 25 Jul 2025 17:23:44 +0300 Subject: [PATCH 06/11] added specs --- src/is-index-equal.spec.ts | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/is-index-equal.spec.ts b/src/is-index-equal.spec.ts index 0bf33ee..e0698fe 100644 --- a/src/is-index-equal.spec.ts +++ b/src/is-index-equal.spec.ts @@ -102,4 +102,50 @@ describe('isIndexEqual', () => { expect(isIndexEqual(input.a, input.b)).toBe(false); }); }); + + it('should return true when keys match and collation is default', () => { + const indexA = { + key: { a: 1 }, + collation: { + locale: 'en_US', + caseLevel: false, + caseFirst: 'off', + strength: 1, + numericOrdering: false, + alternate: 'non-ignorable', + maxVariable: 'punct', + normalization: false, + backwards: false, + }, + }; + + const indexB = { + key: { a: 1 }, + }; + + expect(isIndexEqual(indexA, indexB)).toBe(true); + }); + + it('should return false when keys match but collation is not default', () => { + const indexA = { + key: { a: 1 }, + collation: { + locale: 'en_US', + caseLevel: false, + caseFirst: 'off', + strength: 2, + numericOrdering: false, + alternate: 'non-ignorable', + maxVariable: 'punct', + normalization: false, + backwards: false, + }, + }; + + const indexB = { + key: { a: 1 }, + }; + + expect(isIndexEqual(indexA, indexB)).toBe(false); + }); }); From cdd566c03a74382542370939989e4f1f3ebc8ec8 Mon Sep 17 00:00:00 2001 From: alinashklyar Date: Mon, 28 Jul 2025 16:53:55 +0300 Subject: [PATCH 07/11] added includeCollation flag --- .gitignore | 4 ++ src/compare-dump.ts | 16 +++--- src/index.ts | 1 + src/is-index-equal.spec.ts | 39 +++++++++++--- src/is-index-equal.ts | 53 ++++++++----------- src/overrides/should-ignore-index-in-dump.ts | 17 ++++-- .../should-ignore-index-in-target.ts | 17 ++++-- src/types.ts | 1 + 8 files changed, 91 insertions(+), 57 deletions(-) diff --git a/.gitignore b/.gitignore index 94bae75..37e0191 100644 --- a/.gitignore +++ b/.gitignore @@ -132,3 +132,7 @@ dist !indexes/** .DS_Store + +#Webstorm +.idea/ + diff --git a/src/compare-dump.ts b/src/compare-dump.ts index f833cd1..7155ad2 100644 --- a/src/compare-dump.ts +++ b/src/compare-dump.ts @@ -7,22 +7,22 @@ import { readDump } from './read-dump.js'; import type { CollectionDiff, CollectionIndexes, CompareOptions, DatabaseDiff, DatabaseIndexes, DbMapRaw, FullDiff } from './types.js'; import { getTargetToDumpDb } from './utils.js'; -const compareCollections = (desired: CollectionIndexes, actual?: CollectionIndexes, dbMap?: DbMapRaw): CollectionDiff => { +const compareCollections = (desired: CollectionIndexes, actual?: CollectionIndexes, dbMap?: DbMapRaw, options?: CompareOptions): CollectionDiff => { const dumpDbName = getTargetToDumpDb(dbMap).get(desired.databaseName) ?? desired.databaseName; const missingIndexes = desired.indexes.filter((desiredIndex) => { // Skip indexes that should be ignored in the dump - if (shouldIgnoreIndexInDump(dumpDbName, desired.collectionName, desiredIndex)) return false; + if (shouldIgnoreIndexInDump(dumpDbName, desired.collectionName, desiredIndex, options)) return false; - const match = actual?.indexes.find(actualIndex => isIndexEqual(desiredIndex, actualIndex)); + const match = actual?.indexes.find(actualIndex => isIndexEqual(desiredIndex, actualIndex, options?.includeCollations)); return !match; }); const extraIndexes = actual?.indexes.filter((actualIndex) => { // Skip indexes that should be ignored in the target - if (shouldIgnoreIndexInTarget(dumpDbName, actual.collectionName, actualIndex)) return false; + if (shouldIgnoreIndexInTarget(dumpDbName, actual.collectionName, actualIndex, options)) return false; - const match = desired.indexes.find(desiredIndex => isIndexEqual(desiredIndex, actualIndex)); + const match = desired.indexes.find(desiredIndex => isIndexEqual(desiredIndex, actualIndex, options?.includeCollations)); return !match; }); @@ -33,12 +33,12 @@ const compareCollections = (desired: CollectionIndexes, actual?: CollectionIndex }; }; -const compareDatabases = (desired: DatabaseIndexes, actual?: DatabaseIndexes, dbMap?: DbMapRaw): DatabaseDiff => { +const compareDatabases = (desired: DatabaseIndexes, actual?: DatabaseIndexes, dbMap?: DbMapRaw, options?: CompareOptions): DatabaseDiff => { const dbDiff: DatabaseDiff = { databaseName: desired.databaseName, collections: {} }; for (const desiredCol of desired.collections) { const actualCol = actual?.collections.find(actuaCol => actuaCol.collectionName === desiredCol.collectionName); - const collectionResult = compareCollections(desiredCol, actualCol, dbMap); + const collectionResult = compareCollections(desiredCol, actualCol, dbMap, options); if (collectionResult.missingIndexes || collectionResult.extraIndexes) { dbDiff.collections[desiredCol.collectionName] = collectionResult; } @@ -56,7 +56,7 @@ export const compareDump = async (options: CompareOptions): Promise => const diff: FullDiff = { databases: {} }; for (const desiredDb of desired) { const actualDb = actual.find(db => db.databaseName === desiredDb.databaseName); - const dbDiff = compareDatabases(desiredDb, actualDb, dbMap); + const dbDiff = compareDatabases(desiredDb, actualDb, dbMap, options); if (Object.keys(dbDiff.collections).length !== 0) { diff.databases[desiredDb.databaseName] = dbDiff; } diff --git a/src/index.ts b/src/index.ts index 3466a2e..dbec82e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -94,6 +94,7 @@ program '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', defaultDbMap, ) + .option('--includeCollations', 'Include collations difference in the comparison', false) .action(compare); program diff --git a/src/is-index-equal.spec.ts b/src/is-index-equal.spec.ts index e0698fe..52d0bdf 100644 --- a/src/is-index-equal.spec.ts +++ b/src/is-index-equal.spec.ts @@ -12,13 +12,13 @@ describe('isIndexEqual', () => { it('should return true', () => { const a = { key: { a: 1 } }; const b = { key: { a: 1 } }; - expect(isIndexEqual(a, b)).toBe(true); + expect(isIndexEqual(a, b, false)).toBe(true); }); it('should ignore index names', () => { const a = { name: 'index1', key: { a: 1 } }; const b = { name: 'index2', key: { a: 1 } }; - expect(isIndexEqual(a, b)).toBe(true); + expect(isIndexEqual(a, b, false)).toBe(true); }); it.each([ @@ -56,7 +56,7 @@ describe('isIndexEqual', () => { }, ] satisfies TestCase[])('should ignore order of non-key properties', (input) => { expect(JSON.stringify(input.a)).not.toEqual(JSON.stringify(input.b)); // validate testcase itself - expect(isIndexEqual(input.a, input.b)).toBe(true); + expect(isIndexEqual(input.a, input.b, false)).toBe(true); }); }); @@ -64,13 +64,13 @@ describe('isIndexEqual', () => { it('should return false when keys are different', () => { const a = { key: { a: 1 } }; const b = { key: { b: 1 } }; - expect(isIndexEqual(a, b)).toBe(false); + expect(isIndexEqual(a, b, false)).toBe(false); }); it('should ignore index names', () => { const a = { name: 'same', key: { a: 1 } }; const b = { name: 'same', key: { b: 1 } }; - expect(isIndexEqual(a, b)).toBe(false); + expect(isIndexEqual(a, b, false)).toBe(false); }); it.each([ @@ -99,7 +99,7 @@ describe('isIndexEqual', () => { ] satisfies TestCase[])('should return false when keys are same, but options are different', (input) => { expect(JSON.stringify(input.a)).not.toEqual(JSON.stringify(input.b)); // validate testcase itself expect(JSON.stringify(input.a.key)).toEqual(JSON.stringify(input.b.key)); // validate testcase itself - expect(isIndexEqual(input.a, input.b)).toBe(false); + expect(isIndexEqual(input.a, input.b, false)).toBe(false); }); }); @@ -123,7 +123,30 @@ describe('isIndexEqual', () => { key: { a: 1 }, }; - expect(isIndexEqual(indexA, indexB)).toBe(true); + expect(isIndexEqual(indexA, indexB, false)).toBe(true); + }); + + it('should return false when keys match and collation is default but includeCollation flag is true', () => { + const indexA = { + key: { a: 1 }, + collation: { + locale: 'en_US', + caseLevel: false, + caseFirst: 'off', + strength: 1, + numericOrdering: false, + alternate: 'non-ignorable', + maxVariable: 'punct', + normalization: false, + backwards: false, + }, + }; + + const indexB = { + key: { a: 1 }, + }; + + expect(isIndexEqual(indexA, indexB, true)).toBe(false); }); it('should return false when keys match but collation is not default', () => { @@ -146,6 +169,6 @@ describe('isIndexEqual', () => { key: { a: 1 }, }; - expect(isIndexEqual(indexA, indexB)).toBe(false); + expect(isIndexEqual(indexA, indexB, false)).toBe(false); }); }); diff --git a/src/is-index-equal.ts b/src/is-index-equal.ts index d5bdb63..983e8c7 100644 --- a/src/is-index-equal.ts +++ b/src/is-index-equal.ts @@ -2,6 +2,23 @@ import { deepStrictEqual } from 'node:assert'; import type { Index } from './types.js'; import { CollationOptions, Document } from 'mongodb'; +const defaultCollation: CollationOptions = { + locale: 'en_US', + caseLevel: false, + caseFirst: 'off', + strength: 1, + numericOrdering: false, + alternate: 'non-ignorable', + maxVariable: 'punct', + normalization: false, + backwards: false, +}; + +function isDefaultCollation(collation: Document | undefined): boolean { + if (!collation) return false; + return JSON.stringify(collation) === JSON.stringify(defaultCollation); +} + /** * Compare two indexes to check if they are equal. * @@ -21,43 +38,17 @@ import { CollationOptions, Document } from 'mongodb'; * * - If both `key` and options (except for `name`) are equal, the indexes are considered equal. */ - -const defaultCollation: CollationOptions = { - locale: 'en_US', - caseLevel: false, - caseFirst: 'off', - strength: 1, - numericOrdering: false, - alternate: 'non-ignorable', - maxVariable: 'punct', - normalization: false, - backwards: false, -}; - -function isDefaultCollation(collation: Document | undefined): boolean { - if (!collation) return false; - - const collationEntries = Object.entries(collation); - const defaultEntries = Object.entries(defaultCollation); - - if (collationEntries.length !== defaultEntries.length) return false; - - for (const [key, value] of collationEntries) { - if (defaultCollation[key as keyof CollationOptions] !== value) return false; - } - - return true; -} - -export const isIndexEqual = (a: Index, b: Index): boolean => { +export const isIndexEqual = (a: Index, b: Index, includeCollations = false): boolean => { const aKey = a.key; const aOptions = { ...a, key: undefined, name: undefined }; const bKey = b.key; const bOptions = { ...b, key: undefined, name: undefined }; const isKeyEqual = JSON.stringify(aKey) === JSON.stringify(bKey); - if (isDefaultCollation(aOptions.collation)) delete aOptions.collation; - if (isDefaultCollation(bOptions.collation)) delete bOptions.collation; + if (!includeCollations) { + if (isDefaultCollation(aOptions.collation)) delete aOptions.collation; + if (isDefaultCollation(bOptions.collation)) delete bOptions.collation; + } try { deepStrictEqual(aOptions, bOptions); diff --git a/src/overrides/should-ignore-index-in-dump.ts b/src/overrides/should-ignore-index-in-dump.ts index 4743e2f..e9630d6 100644 --- a/src/overrides/should-ignore-index-in-dump.ts +++ b/src/overrides/should-ignore-index-in-dump.ts @@ -1,5 +1,12 @@ import { isIndexEqual } from '../is-index-equal.js'; -import type { CollectionName, DatabaseName, IgnoreInAllCollections, IgnoreList, Index } from '../types.js'; +import type { + CollectionName, + CompareOptions, + DatabaseName, + IgnoreInAllCollections, + IgnoreList, + Index +} from '../types.js'; // TODO: Verify unique indexes, they should probably be ignored for now. @@ -212,9 +219,9 @@ const ignoreList: IgnoreList = { }, }; -export const shouldIgnoreIndexInDump = (dumpDbName: DatabaseName, collectionName: CollectionName, dumpIndex: Index): boolean => { +export const shouldIgnoreIndexInDump = (dumpDbName: DatabaseName, collectionName: CollectionName, dumpIndex: Index, options?: CompareOptions): boolean => { // Check if the index should be ignored in all collections - if (ignoreInAllCollections.some(ignore => isIndexEqual(ignore, dumpIndex))) { + if (ignoreInAllCollections.some(ignore => isIndexEqual(ignore, dumpIndex, options?.includeCollations))) { return true; } @@ -226,7 +233,7 @@ export const shouldIgnoreIndexInDump = (dumpDbName: DatabaseName, collectionName } // Check if the index is in the ignore list for the specific collection - if (ignoreCollection?.indexes.some(ignore => isIndexEqual(ignore, dumpIndex))) { + if (ignoreCollection?.indexes.some(ignore => isIndexEqual(ignore, dumpIndex, options?.includeCollations))) { return true; } @@ -235,7 +242,7 @@ export const shouldIgnoreIndexInDump = (dumpDbName: DatabaseName, collectionName ...dumpIndex, // eslint-disable-next-line @typescript-eslint/no-explicit-any expireAfterSeconds: 'ANY' as any, - }))) { + }, options?.includeCollations))) { return true; } diff --git a/src/overrides/should-ignore-index-in-target.ts b/src/overrides/should-ignore-index-in-target.ts index 09d2cd9..395b03f 100644 --- a/src/overrides/should-ignore-index-in-target.ts +++ b/src/overrides/should-ignore-index-in-target.ts @@ -1,5 +1,12 @@ import { isIndexEqual } from '../is-index-equal.js'; -import type { CollectionName, DatabaseName, IgnoreInAllCollections, IgnoreList, Index } from '../types.js'; +import type { + CollectionName, + CompareOptions, + DatabaseName, + IgnoreInAllCollections, + IgnoreList, + Index +} from '../types.js'; // TODO: Verify unique indexes, they should probably be ignored for now. @@ -55,9 +62,9 @@ const ignoreList: IgnoreList = { }, }; -export const shouldIgnoreIndexInTarget = (dumpDbName: DatabaseName, collectionName: CollectionName, targetIndex: Index): boolean => { +export const shouldIgnoreIndexInTarget = (dumpDbName: DatabaseName, collectionName: CollectionName, targetIndex: Index, options?: CompareOptions): boolean => { // Check if the index should be ignored in all collections - if (ignoreInAllCollections.some(ignore => isIndexEqual(ignore, targetIndex))) { + if (ignoreInAllCollections.some(ignore => isIndexEqual(ignore, targetIndex, options?.includeCollations))) { return true; } @@ -69,7 +76,7 @@ export const shouldIgnoreIndexInTarget = (dumpDbName: DatabaseName, collectionNa } // Check if the index is in the ignore list for the specific collection - if (ignoreCollection?.indexes.some(ignore => isIndexEqual(ignore, targetIndex))) { + if (ignoreCollection?.indexes.some(ignore => isIndexEqual(ignore, targetIndex, options?.includeCollations))) { return true; } @@ -78,7 +85,7 @@ export const shouldIgnoreIndexInTarget = (dumpDbName: DatabaseName, collectionNa ...targetIndex, // eslint-disable-next-line @typescript-eslint/no-explicit-any expireAfterSeconds: 'ANY' as any, - }))) { + }, options?.includeCollations))) { return true; } diff --git a/src/types.ts b/src/types.ts index 5e0245d..64cbbea 100644 --- a/src/types.ts +++ b/src/types.ts @@ -72,6 +72,7 @@ export interface CompareOptions extends Partial { uri: string; product: Product; dbMap?: DbMapRaw; + includeCollations?: boolean; } export interface StatsOptions extends Partial { From 6f7f8bc29e25736b254e5b9329091a7b60ccf9d1 Mon Sep 17 00:00:00 2001 From: alinashklyar Date: Mon, 28 Jul 2025 17:41:38 +0300 Subject: [PATCH 08/11] pull request requested fixes --- package.json | 2 +- src/compare-dump.ts | 4 +- src/index.ts | 2 +- src/is-index-equal.spec.ts | 45 ++++++++++++++----- src/is-index-equal.ts | 14 ++++-- src/overrides/should-ignore-index-in-dump.ts | 18 ++++---- .../should-ignore-index-in-target.ts | 18 ++++---- src/types.ts | 2 +- 8 files changed, 68 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index 8291bdd..3ef7a9c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "index-alignment", - "version": "0.2.0", + "version": "0.3.0", "main": "dist/index.js", "private": true, "type": "module", diff --git a/src/compare-dump.ts b/src/compare-dump.ts index 7155ad2..1ffd053 100644 --- a/src/compare-dump.ts +++ b/src/compare-dump.ts @@ -14,7 +14,7 @@ const compareCollections = (desired: CollectionIndexes, actual?: CollectionIndex // Skip indexes that should be ignored in the dump if (shouldIgnoreIndexInDump(dumpDbName, desired.collectionName, desiredIndex, options)) return false; - const match = actual?.indexes.find(actualIndex => isIndexEqual(desiredIndex, actualIndex, options?.includeCollations)); + const match = actual?.indexes.find(actualIndex => isIndexEqual(desiredIndex, actualIndex, options)); return !match; }); @@ -22,7 +22,7 @@ const compareCollections = (desired: CollectionIndexes, actual?: CollectionIndex // Skip indexes that should be ignored in the target if (shouldIgnoreIndexInTarget(dumpDbName, actual.collectionName, actualIndex, options)) return false; - const match = desired.indexes.find(desiredIndex => isIndexEqual(desiredIndex, actualIndex, options?.includeCollations)); + const match = desired.indexes.find(desiredIndex => isIndexEqual(desiredIndex, actualIndex, options)); return !match; }); diff --git a/src/index.ts b/src/index.ts index dbec82e..d0d73a8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -94,7 +94,7 @@ program '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', defaultDbMap, ) - .option('--includeCollations', 'Include collations difference in the comparison', false) + .option('--compareGitopsCollations', 'Compare collations for GitOps product. Only takes effect if --product=gitops', false) .action(compare); program diff --git a/src/is-index-equal.spec.ts b/src/is-index-equal.spec.ts index 52d0bdf..f803a8e 100644 --- a/src/is-index-equal.spec.ts +++ b/src/is-index-equal.spec.ts @@ -12,13 +12,13 @@ describe('isIndexEqual', () => { it('should return true', () => { const a = { key: { a: 1 } }; const b = { key: { a: 1 } }; - expect(isIndexEqual(a, b, false)).toBe(true); + expect(isIndexEqual(a, b)).toBe(true); }); it('should ignore index names', () => { const a = { name: 'index1', key: { a: 1 } }; const b = { name: 'index2', key: { a: 1 } }; - expect(isIndexEqual(a, b, false)).toBe(true); + expect(isIndexEqual(a, b)).toBe(true); }); it.each([ @@ -56,7 +56,7 @@ describe('isIndexEqual', () => { }, ] satisfies TestCase[])('should ignore order of non-key properties', (input) => { expect(JSON.stringify(input.a)).not.toEqual(JSON.stringify(input.b)); // validate testcase itself - expect(isIndexEqual(input.a, input.b, false)).toBe(true); + expect(isIndexEqual(input.a, input.b)).toBe(true); }); }); @@ -64,13 +64,13 @@ describe('isIndexEqual', () => { it('should return false when keys are different', () => { const a = { key: { a: 1 } }; const b = { key: { b: 1 } }; - expect(isIndexEqual(a, b, false)).toBe(false); + expect(isIndexEqual(a, b)).toBe(false); }); it('should ignore index names', () => { const a = { name: 'same', key: { a: 1 } }; const b = { name: 'same', key: { b: 1 } }; - expect(isIndexEqual(a, b, false)).toBe(false); + expect(isIndexEqual(a, b)).toBe(false); }); it.each([ @@ -99,11 +99,11 @@ describe('isIndexEqual', () => { ] satisfies TestCase[])('should return false when keys are same, but options are different', (input) => { expect(JSON.stringify(input.a)).not.toEqual(JSON.stringify(input.b)); // validate testcase itself expect(JSON.stringify(input.a.key)).toEqual(JSON.stringify(input.b.key)); // validate testcase itself - expect(isIndexEqual(input.a, input.b, false)).toBe(false); + expect(isIndexEqual(input.a, input.b)).toBe(false); }); }); - it('should return true when keys match and collation is default', () => { + it('should return true when keys match and collation is default (for gitops)', () => { const indexA = { key: { a: 1 }, collation: { @@ -123,10 +123,10 @@ describe('isIndexEqual', () => { key: { a: 1 }, }; - expect(isIndexEqual(indexA, indexB, false)).toBe(true); + expect(isIndexEqual(indexA, indexB, { product: 'gitops', uri: '' })).toBe(true); }); - it('should return false when keys match and collation is default but includeCollation flag is true', () => { + it('should return false when keys match and collation is default (for classic)', () => { const indexA = { key: { a: 1 }, collation: { @@ -146,7 +146,30 @@ describe('isIndexEqual', () => { key: { a: 1 }, }; - expect(isIndexEqual(indexA, indexB, true)).toBe(false); + expect(isIndexEqual(indexA, indexB, { product: 'classic', uri: '' })).toBe(false); + }); + + it('should return false when keys match and collation is default but compareGitopsCollations flag is true', () => { + const indexA = { + key: { a: 1 }, + collation: { + locale: 'en_US', + caseLevel: false, + caseFirst: 'off', + strength: 1, + numericOrdering: false, + alternate: 'non-ignorable', + maxVariable: 'punct', + normalization: false, + backwards: false, + }, + }; + + const indexB = { + key: { a: 1 }, + }; + + expect(isIndexEqual(indexA, indexB, { product: 'gitops', uri: '', compareGitopsCollations: true })).toBe(false); }); it('should return false when keys match but collation is not default', () => { @@ -169,6 +192,6 @@ describe('isIndexEqual', () => { key: { a: 1 }, }; - expect(isIndexEqual(indexA, indexB, false)).toBe(false); + expect(isIndexEqual(indexA, indexB, { product: 'gitops', uri: '' })).toBe(false); }); }); diff --git a/src/is-index-equal.ts b/src/is-index-equal.ts index 983e8c7..ce06149 100644 --- a/src/is-index-equal.ts +++ b/src/is-index-equal.ts @@ -1,7 +1,12 @@ import { deepStrictEqual } from 'node:assert'; -import type { Index } from './types.js'; +import type { CompareOptions, Index } from './types.js'; import { CollationOptions, Document } from 'mongodb'; +/** + * Default collation specified for collections in GitOps product + * (https://github.com/codefresh-io/argo-platform/blob/aa831b539c8434156c323db881ee7d44db87ac13/libs/db/src/helpers/helpers.ts#L81), + * extended with implicit collation options. + */ const defaultCollation: CollationOptions = { locale: 'en_US', caseLevel: false, @@ -37,15 +42,18 @@ function isDefaultCollation(collation: Document | undefined): boolean { * that's why we use `deepStrictEqual` ({@link https://nodejs.org/api/assert.html#assertdeepstrictequalactual-expected-message|docs}). * * - If both `key` and options (except for `name`) are equal, the indexes are considered equal. + * + * includeCollations parameter allows to include collation options in the comparison. + * This check is temporary disabled by default due to a misalignment between production and on-prem. ({@link https://codefresh-io.atlassian.net/browse/CR-29948}) */ -export const isIndexEqual = (a: Index, b: Index, includeCollations = false): boolean => { +export const isIndexEqual = (a: Index, b: Index, options?: CompareOptions): boolean => { const aKey = a.key; const aOptions = { ...a, key: undefined, name: undefined }; const bKey = b.key; const bOptions = { ...b, key: undefined, name: undefined }; const isKeyEqual = JSON.stringify(aKey) === JSON.stringify(bKey); - if (!includeCollations) { + if (options?.product === 'gitops' && !options?.compareGitopsCollations) { if (isDefaultCollation(aOptions.collation)) delete aOptions.collation; if (isDefaultCollation(bOptions.collation)) delete bOptions.collation; } diff --git a/src/overrides/should-ignore-index-in-dump.ts b/src/overrides/should-ignore-index-in-dump.ts index e9630d6..d9a3091 100644 --- a/src/overrides/should-ignore-index-in-dump.ts +++ b/src/overrides/should-ignore-index-in-dump.ts @@ -1,11 +1,11 @@ import { isIndexEqual } from '../is-index-equal.js'; import type { - CollectionName, - CompareOptions, - DatabaseName, - IgnoreInAllCollections, - IgnoreList, - Index + CollectionName, + CompareOptions, + DatabaseName, + IgnoreInAllCollections, + IgnoreList, + Index, } from '../types.js'; // TODO: Verify unique indexes, they should probably be ignored for now. @@ -221,7 +221,7 @@ const ignoreList: IgnoreList = { export const shouldIgnoreIndexInDump = (dumpDbName: DatabaseName, collectionName: CollectionName, dumpIndex: Index, options?: CompareOptions): boolean => { // Check if the index should be ignored in all collections - if (ignoreInAllCollections.some(ignore => isIndexEqual(ignore, dumpIndex, options?.includeCollations))) { + if (ignoreInAllCollections.some(ignore => isIndexEqual(ignore, dumpIndex, options))) { return true; } @@ -233,7 +233,7 @@ export const shouldIgnoreIndexInDump = (dumpDbName: DatabaseName, collectionName } // Check if the index is in the ignore list for the specific collection - if (ignoreCollection?.indexes.some(ignore => isIndexEqual(ignore, dumpIndex, options?.includeCollations))) { + if (ignoreCollection?.indexes.some(ignore => isIndexEqual(ignore, dumpIndex, options))) { return true; } @@ -242,7 +242,7 @@ export const shouldIgnoreIndexInDump = (dumpDbName: DatabaseName, collectionName ...dumpIndex, // eslint-disable-next-line @typescript-eslint/no-explicit-any expireAfterSeconds: 'ANY' as any, - }, options?.includeCollations))) { + }, options))) { return true; } diff --git a/src/overrides/should-ignore-index-in-target.ts b/src/overrides/should-ignore-index-in-target.ts index 395b03f..8c042d0 100644 --- a/src/overrides/should-ignore-index-in-target.ts +++ b/src/overrides/should-ignore-index-in-target.ts @@ -1,11 +1,11 @@ import { isIndexEqual } from '../is-index-equal.js'; import type { - CollectionName, - CompareOptions, - DatabaseName, - IgnoreInAllCollections, - IgnoreList, - Index + CollectionName, + CompareOptions, + DatabaseName, + IgnoreInAllCollections, + IgnoreList, + Index, } from '../types.js'; // TODO: Verify unique indexes, they should probably be ignored for now. @@ -64,7 +64,7 @@ const ignoreList: IgnoreList = { export const shouldIgnoreIndexInTarget = (dumpDbName: DatabaseName, collectionName: CollectionName, targetIndex: Index, options?: CompareOptions): boolean => { // Check if the index should be ignored in all collections - if (ignoreInAllCollections.some(ignore => isIndexEqual(ignore, targetIndex, options?.includeCollations))) { + if (ignoreInAllCollections.some(ignore => isIndexEqual(ignore, targetIndex, options))) { return true; } @@ -76,7 +76,7 @@ export const shouldIgnoreIndexInTarget = (dumpDbName: DatabaseName, collectionNa } // Check if the index is in the ignore list for the specific collection - if (ignoreCollection?.indexes.some(ignore => isIndexEqual(ignore, targetIndex, options?.includeCollations))) { + if (ignoreCollection?.indexes.some(ignore => isIndexEqual(ignore, targetIndex, options))) { return true; } @@ -85,7 +85,7 @@ export const shouldIgnoreIndexInTarget = (dumpDbName: DatabaseName, collectionNa ...targetIndex, // eslint-disable-next-line @typescript-eslint/no-explicit-any expireAfterSeconds: 'ANY' as any, - }, options?.includeCollations))) { + }, options))) { return true; } diff --git a/src/types.ts b/src/types.ts index 64cbbea..1cac4d2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -72,7 +72,7 @@ export interface CompareOptions extends Partial { uri: string; product: Product; dbMap?: DbMapRaw; - includeCollations?: boolean; + compareGitopsCollations?: boolean; } export interface StatsOptions extends Partial { From 20bbf4dda73751bec8768004e9227bc8ed5556a2 Mon Sep 17 00:00:00 2001 From: alinashklyar Date: Mon, 28 Jul 2025 17:44:06 +0300 Subject: [PATCH 09/11] marked includeCollations as param in docs --- src/is-index-equal.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/is-index-equal.ts b/src/is-index-equal.ts index ce06149..a3a0063 100644 --- a/src/is-index-equal.ts +++ b/src/is-index-equal.ts @@ -43,8 +43,9 @@ function isDefaultCollation(collation: Document | undefined): boolean { * * - If both `key` and options (except for `name`) are equal, the indexes are considered equal. * - * includeCollations parameter allows to include collation options in the comparison. - * This check is temporary disabled by default due to a misalignment between production and on-prem. ({@link https://codefresh-io.atlassian.net/browse/CR-29948}) + * `includeCollations` parameter allows to include collation options in the comparison for gitops. + * This check is temporary disabled by default due to a misalignment between production and on-prem environments. + * ({@link https://codefresh-io.atlassian.net/browse/CR-29948}) */ export const isIndexEqual = (a: Index, b: Index, options?: CompareOptions): boolean => { const aKey = a.key; From 55033437d1c68904e39032561d63faa6692b1f49 Mon Sep 17 00:00:00 2001 From: alinashklyar Date: Mon, 28 Jul 2025 18:09:19 +0300 Subject: [PATCH 10/11] fix docs --- src/is-index-equal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/is-index-equal.ts b/src/is-index-equal.ts index a3a0063..89992b9 100644 --- a/src/is-index-equal.ts +++ b/src/is-index-equal.ts @@ -43,7 +43,7 @@ function isDefaultCollation(collation: Document | undefined): boolean { * * - If both `key` and options (except for `name`) are equal, the indexes are considered equal. * - * `includeCollations` parameter allows to include collation options in the comparison for gitops. + * `options.compareGitopsCollations` parameter allows to include collation options in the comparison for gitops. * This check is temporary disabled by default due to a misalignment between production and on-prem environments. * ({@link https://codefresh-io.atlassian.net/browse/CR-29948}) */ From 6fe6004db67eff4405bf565fe9364dabc532536f Mon Sep 17 00:00:00 2001 From: Zhenya Tikhonov Date: Tue, 5 Aug 2025 16:08:01 +0400 Subject: [PATCH 11/11] ci: trigger ci