From 2ee3de94d196c1650e72906273b8f03583fbe2a7 Mon Sep 17 00:00:00 2001 From: hkobew Date: Tue, 24 Sep 2024 19:55:24 -0400 Subject: [PATCH 01/14] forcefully delete testDir after test --- packages/core/src/test/awsService/ec2/sshKeyPair.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/test/awsService/ec2/sshKeyPair.test.ts b/packages/core/src/test/awsService/ec2/sshKeyPair.test.ts index b6872264823..533072e22f0 100644 --- a/packages/core/src/test/awsService/ec2/sshKeyPair.test.ts +++ b/packages/core/src/test/awsService/ec2/sshKeyPair.test.ts @@ -33,7 +33,7 @@ describe('SshKeyUtility', async function () { }) after(async function () { - await keyPair.delete() + await fs.delete(temporaryDirectory, { recursive: true, force: true }) clock.uninstall() sinon.restore() }) From 92efb95e43c8eac8bd8ffa9c7e769250bc4d62c6 Mon Sep 17 00:00:00 2001 From: hkobew Date: Wed, 25 Sep 2024 14:31:34 -0400 Subject: [PATCH 02/14] add more barriers to flaky test --- packages/core/src/awsService/ec2/sshKeyPair.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/core/src/awsService/ec2/sshKeyPair.ts b/packages/core/src/awsService/ec2/sshKeyPair.ts index 1b7a461655d..0fd8a7bc1b2 100644 --- a/packages/core/src/awsService/ec2/sshKeyPair.ts +++ b/packages/core/src/awsService/ec2/sshKeyPair.ts @@ -17,7 +17,7 @@ export class SshKeyPair { private deleted: boolean = false private constructor( - private keyPath: string, + private readonly keyPath: string, lifetime: number ) { this.publicKeyPath = `${keyPath}.pub` @@ -30,9 +30,9 @@ export class SshKeyPair { public static async getSshKeyPair(keyPath: string, lifetime: number) { // Overwrite key if already exists - if (await fs.existsFile(keyPath)) { - await fs.delete(keyPath) - } + await fs.delete(keyPath, { force: true }) + await fs.delete(`${keyPath}.pub`, { force: true }) + await SshKeyPair.generateSshKeyPair(keyPath) return new SshKeyPair(keyPath, lifetime) } @@ -72,6 +72,10 @@ export class SshKeyPair { } public async delete(): Promise { + if (await fs.existsDir(this.keyPath)) { + throw new Error('ec2: key path points to directory, not file') + } + await fs.delete(this.keyPath) await fs.delete(this.publicKeyPath) From c86daa393650310645f25231b840c16b045f5cbe Mon Sep 17 00:00:00 2001 From: hkobew Date: Wed, 25 Sep 2024 14:58:06 -0400 Subject: [PATCH 03/14] add option to not overwrite the keys --- packages/core/src/awsService/ec2/sshKeyPair.ts | 8 +++++--- packages/core/src/test/awsService/ec2/model.test.ts | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/core/src/awsService/ec2/sshKeyPair.ts b/packages/core/src/awsService/ec2/sshKeyPair.ts index 0fd8a7bc1b2..1be74d086dd 100644 --- a/packages/core/src/awsService/ec2/sshKeyPair.ts +++ b/packages/core/src/awsService/ec2/sshKeyPair.ts @@ -28,10 +28,12 @@ export class SshKeyPair { }) } - public static async getSshKeyPair(keyPath: string, lifetime: number) { + public static async getSshKeyPair(keyPath: string, lifetime: number, overwrite: boolean = true) { // Overwrite key if already exists - await fs.delete(keyPath, { force: true }) - await fs.delete(`${keyPath}.pub`, { force: true }) + if (overwrite) { + await fs.delete(keyPath, { force: true }) + await fs.delete(`${keyPath}.pub`, { force: true }) + } await SshKeyPair.generateSshKeyPair(keyPath) return new SshKeyPair(keyPath, lifetime) diff --git a/packages/core/src/test/awsService/ec2/model.test.ts b/packages/core/src/test/awsService/ec2/model.test.ts index 98c0103c06e..b91cba397cd 100644 --- a/packages/core/src/test/awsService/ec2/model.test.ts +++ b/packages/core/src/test/awsService/ec2/model.test.ts @@ -139,8 +139,8 @@ describe('Ec2ConnectClient', function () { instanceId: 'test-id', region: 'test-region', } - const mockKeys = await SshKeyPair.getSshKeyPair('', 30000) - await client.sendSshKeyToInstance(testSelection, mockKeys, '') + const mockKeys = await SshKeyPair.getSshKeyPair('fakeDir', 30000, false) + await client.sendSshKeyToInstance(testSelection, mockKeys, 'test-user') sinon.assert.calledWith(sendCommandStub, testSelection.instanceId, 'AWS-RunShellScript') sinon.restore() }) From 6e3ea879791098c4f67de2542527585b73bd2ba3 Mon Sep 17 00:00:00 2001 From: hkobew Date: Wed, 25 Sep 2024 15:38:43 -0400 Subject: [PATCH 04/14] refactor to allow onStdOut parameter --- packages/core/src/awsService/ec2/sshKeyPair.ts | 18 +++++++++++------- packages/core/src/shared/utilities/pathFind.ts | 6 ++++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/core/src/awsService/ec2/sshKeyPair.ts b/packages/core/src/awsService/ec2/sshKeyPair.ts index 1be74d086dd..9b9ed5a1aef 100644 --- a/packages/core/src/awsService/ec2/sshKeyPair.ts +++ b/packages/core/src/awsService/ec2/sshKeyPair.ts @@ -8,6 +8,7 @@ import { ToolkitError } from '../../shared/errors' import { tryRun } from '../../shared/utilities/pathFind' import { Timeout } from '../../shared/utilities/timeoutUtils' import { findAsync } from '../../shared/utilities/collectionUtils' +import { ChildProcess } from '../../shared/utilities/childProcess' type sshKeyType = 'rsa' | 'ed25519' @@ -29,12 +30,6 @@ export class SshKeyPair { } public static async getSshKeyPair(keyPath: string, lifetime: number, overwrite: boolean = true) { - // Overwrite key if already exists - if (overwrite) { - await fs.delete(keyPath, { force: true }) - await fs.delete(`${keyPath}.pub`, { force: true }) - } - await SshKeyPair.generateSshKeyPair(keyPath) return new SshKeyPair(keyPath, lifetime) } @@ -52,7 +47,16 @@ export class SshKeyPair { * @param keyType type of key to generate. */ public static async tryKeyGen(keyPath: string, keyType: sshKeyType): Promise { - return !(await tryRun('ssh-keygen', ['-t', keyType, '-N', '', '-q', '-f', keyPath], 'yes', 'unknown key type')) + const overrideKeys = async (proc: ChildProcess, text: string) => { + await proc.send('yes') + } + return !(await tryRun( + 'ssh-keygen', + ['-t', keyType, '-N', '', '-q', '-f', keyPath], + 'yes', + 'unknown key type', + overrideKeys + )) } public static async tryKeyTypes(keyPath: string, keyTypes: sshKeyType[]): Promise { diff --git a/packages/core/src/shared/utilities/pathFind.ts b/packages/core/src/shared/utilities/pathFind.ts index 830ad89285f..eea5dd2ff27 100644 --- a/packages/core/src/shared/utilities/pathFind.ts +++ b/packages/core/src/shared/utilities/pathFind.ts @@ -30,10 +30,12 @@ export async function tryRun( p: string, args: string[], logging: 'yes' | 'no' | 'noresult' = 'yes', - expected?: string + expected?: string, + onStdout?: (proc: ChildProcess, text: string) => void ): Promise { const proc = new ChildProcess(p, args, { logging: 'no' }) - const r = await proc.run() + const options = onStdout ? { onStdout: (text: string) => onStdout(proc, text) } : {} + const r = await proc.run(options) const ok = r.exitCode === 0 && (expected === undefined || r.stdout.includes(expected)) if (logging === 'noresult') { getLogger().info('tryRun: %s: %s', ok ? 'ok' : 'failed', proc) From 0b6d47a89574b5d69a994311980486f43f54349a Mon Sep 17 00:00:00 2001 From: hkobew Date: Wed, 25 Sep 2024 15:40:15 -0400 Subject: [PATCH 05/14] remove custom error wrapper --- packages/core/src/awsService/ec2/sshKeyPair.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/core/src/awsService/ec2/sshKeyPair.ts b/packages/core/src/awsService/ec2/sshKeyPair.ts index 9b9ed5a1aef..5951f514f2a 100644 --- a/packages/core/src/awsService/ec2/sshKeyPair.ts +++ b/packages/core/src/awsService/ec2/sshKeyPair.ts @@ -78,10 +78,6 @@ export class SshKeyPair { } public async delete(): Promise { - if (await fs.existsDir(this.keyPath)) { - throw new Error('ec2: key path points to directory, not file') - } - await fs.delete(this.keyPath) await fs.delete(this.publicKeyPath) From 9be0f8e8bdeb45c65b3b7b3bf7ba2a7e9adf04b8 Mon Sep 17 00:00:00 2001 From: hkobew Date: Wed, 25 Sep 2024 15:53:31 -0400 Subject: [PATCH 06/14] redo import --- packages/core/src/awsService/ec2/sshKeyPair.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/awsService/ec2/sshKeyPair.ts b/packages/core/src/awsService/ec2/sshKeyPair.ts index 5951f514f2a..3be0a4b47f5 100644 --- a/packages/core/src/awsService/ec2/sshKeyPair.ts +++ b/packages/core/src/awsService/ec2/sshKeyPair.ts @@ -47,7 +47,7 @@ export class SshKeyPair { * @param keyType type of key to generate. */ public static async tryKeyGen(keyPath: string, keyType: sshKeyType): Promise { - const overrideKeys = async (proc: ChildProcess, text: string) => { + const overrideKeys = async (proc: ChildProcess, _t: string) => { await proc.send('yes') } return !(await tryRun( From 4a30b24042322170767e92aa24d500ffb9fe5461 Mon Sep 17 00:00:00 2001 From: hkobew Date: Wed, 25 Sep 2024 15:58:09 -0400 Subject: [PATCH 07/14] remove childprocess import --- packages/core/src/awsService/ec2/sshKeyPair.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/awsService/ec2/sshKeyPair.ts b/packages/core/src/awsService/ec2/sshKeyPair.ts index 3be0a4b47f5..b7bf3268bcd 100644 --- a/packages/core/src/awsService/ec2/sshKeyPair.ts +++ b/packages/core/src/awsService/ec2/sshKeyPair.ts @@ -8,7 +8,6 @@ import { ToolkitError } from '../../shared/errors' import { tryRun } from '../../shared/utilities/pathFind' import { Timeout } from '../../shared/utilities/timeoutUtils' import { findAsync } from '../../shared/utilities/collectionUtils' -import { ChildProcess } from '../../shared/utilities/childProcess' type sshKeyType = 'rsa' | 'ed25519' @@ -47,7 +46,7 @@ export class SshKeyPair { * @param keyType type of key to generate. */ public static async tryKeyGen(keyPath: string, keyType: sshKeyType): Promise { - const overrideKeys = async (proc: ChildProcess, _t: string) => { + const overrideKeys = async (proc: any, _t: string) => { await proc.send('yes') } return !(await tryRun( From 07c17b24528b08a5b37feb5fbe97475785e94eab Mon Sep 17 00:00:00 2001 From: hkobew Date: Wed, 25 Sep 2024 16:22:16 -0400 Subject: [PATCH 08/14] remove optional overwrite parameter --- packages/core/src/awsService/ec2/sshKeyPair.ts | 2 +- packages/core/src/test/awsService/ec2/model.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/awsService/ec2/sshKeyPair.ts b/packages/core/src/awsService/ec2/sshKeyPair.ts index b7bf3268bcd..c4333a056ce 100644 --- a/packages/core/src/awsService/ec2/sshKeyPair.ts +++ b/packages/core/src/awsService/ec2/sshKeyPair.ts @@ -28,7 +28,7 @@ export class SshKeyPair { }) } - public static async getSshKeyPair(keyPath: string, lifetime: number, overwrite: boolean = true) { + public static async getSshKeyPair(keyPath: string, lifetime: number) { await SshKeyPair.generateSshKeyPair(keyPath) return new SshKeyPair(keyPath, lifetime) } diff --git a/packages/core/src/test/awsService/ec2/model.test.ts b/packages/core/src/test/awsService/ec2/model.test.ts index b91cba397cd..36a7895a35f 100644 --- a/packages/core/src/test/awsService/ec2/model.test.ts +++ b/packages/core/src/test/awsService/ec2/model.test.ts @@ -139,7 +139,7 @@ describe('Ec2ConnectClient', function () { instanceId: 'test-id', region: 'test-region', } - const mockKeys = await SshKeyPair.getSshKeyPair('fakeDir', 30000, false) + const mockKeys = await SshKeyPair.getSshKeyPair('fakeDir', 30000) await client.sendSshKeyToInstance(testSelection, mockKeys, 'test-user') sinon.assert.calledWith(sendCommandStub, testSelection.instanceId, 'AWS-RunShellScript') sinon.restore() From ca6c65f7d2dc74bb6b802463edeabbd0df3aaaad Mon Sep 17 00:00:00 2001 From: hkobew Date: Wed, 25 Sep 2024 16:25:55 -0400 Subject: [PATCH 09/14] attempt to import child process --- packages/core/src/awsService/ec2/sshKeyPair.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/awsService/ec2/sshKeyPair.ts b/packages/core/src/awsService/ec2/sshKeyPair.ts index c4333a056ce..0d17c643a36 100644 --- a/packages/core/src/awsService/ec2/sshKeyPair.ts +++ b/packages/core/src/awsService/ec2/sshKeyPair.ts @@ -8,6 +8,7 @@ import { ToolkitError } from '../../shared/errors' import { tryRun } from '../../shared/utilities/pathFind' import { Timeout } from '../../shared/utilities/timeoutUtils' import { findAsync } from '../../shared/utilities/collectionUtils' +import { ChildProcess } from '../../shared/utilities/childProcess' type sshKeyType = 'rsa' | 'ed25519' @@ -46,7 +47,7 @@ export class SshKeyPair { * @param keyType type of key to generate. */ public static async tryKeyGen(keyPath: string, keyType: sshKeyType): Promise { - const overrideKeys = async (proc: any, _t: string) => { + const overrideKeys = async (proc: ChildProcess, _t: string) => { await proc.send('yes') } return !(await tryRun( From c3ef6e11cb83d6e6bf5bd6c9f928a79eb48829d5 Mon Sep 17 00:00:00 2001 From: hkobew Date: Wed, 25 Sep 2024 16:29:10 -0400 Subject: [PATCH 10/14] make any type, because import fails --- packages/core/src/awsService/ec2/sshKeyPair.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/awsService/ec2/sshKeyPair.ts b/packages/core/src/awsService/ec2/sshKeyPair.ts index 0d17c643a36..c4333a056ce 100644 --- a/packages/core/src/awsService/ec2/sshKeyPair.ts +++ b/packages/core/src/awsService/ec2/sshKeyPair.ts @@ -8,7 +8,6 @@ import { ToolkitError } from '../../shared/errors' import { tryRun } from '../../shared/utilities/pathFind' import { Timeout } from '../../shared/utilities/timeoutUtils' import { findAsync } from '../../shared/utilities/collectionUtils' -import { ChildProcess } from '../../shared/utilities/childProcess' type sshKeyType = 'rsa' | 'ed25519' @@ -47,7 +46,7 @@ export class SshKeyPair { * @param keyType type of key to generate. */ public static async tryKeyGen(keyPath: string, keyType: sshKeyType): Promise { - const overrideKeys = async (proc: ChildProcess, _t: string) => { + const overrideKeys = async (proc: any, _t: string) => { await proc.send('yes') } return !(await tryRun( From 0b9290bf9c377ece8485b275871c9d9920cccd42 Mon Sep 17 00:00:00 2001 From: hkobew Date: Fri, 27 Sep 2024 10:12:50 -0400 Subject: [PATCH 11/14] adjust tryRun to take full options --- packages/core/src/awsService/ec2/sshKeyPair.ts | 15 ++++++--------- .../core/src/shared/utilities/childProcess.ts | 5 ++++- packages/core/src/shared/utilities/pathFind.ts | 7 +++---- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/packages/core/src/awsService/ec2/sshKeyPair.ts b/packages/core/src/awsService/ec2/sshKeyPair.ts index c4333a056ce..9cb4499416c 100644 --- a/packages/core/src/awsService/ec2/sshKeyPair.ts +++ b/packages/core/src/awsService/ec2/sshKeyPair.ts @@ -8,6 +8,7 @@ import { ToolkitError } from '../../shared/errors' import { tryRun } from '../../shared/utilities/pathFind' import { Timeout } from '../../shared/utilities/timeoutUtils' import { findAsync } from '../../shared/utilities/collectionUtils' +import { RunParameterContext } from '../../shared/utilities/childProcess' type sshKeyType = 'rsa' | 'ed25519' @@ -46,16 +47,12 @@ export class SshKeyPair { * @param keyType type of key to generate. */ public static async tryKeyGen(keyPath: string, keyType: sshKeyType): Promise { - const overrideKeys = async (proc: any, _t: string) => { - await proc.send('yes') + const overrideKeys = async (_t: string, context: RunParameterContext) => { + await context.send('yes') } - return !(await tryRun( - 'ssh-keygen', - ['-t', keyType, '-N', '', '-q', '-f', keyPath], - 'yes', - 'unknown key type', - overrideKeys - )) + return !(await tryRun('ssh-keygen', ['-t', keyType, '-N', '', '-q', '-f', keyPath], 'yes', 'unknown key type', { + onStdout: overrideKeys, + })) } public static async tryKeyTypes(keyPath: string, keyTypes: sshKeyType[]): Promise { diff --git a/packages/core/src/shared/utilities/childProcess.ts b/packages/core/src/shared/utilities/childProcess.ts index 3ca9b51a7d3..d19860b787d 100644 --- a/packages/core/src/shared/utilities/childProcess.ts +++ b/packages/core/src/shared/utilities/childProcess.ts @@ -8,11 +8,13 @@ import * as crossSpawn from 'cross-spawn' import * as logger from '../logger' import { Timeout, CancellationError, waitUntil } from './timeoutUtils' -interface RunParameterContext { +export interface RunParameterContext { /** Reports an error parsed from the stdin/stdout streams. */ reportError(err: string | Error): void /** Attempts to stop the running process. See {@link ChildProcess.stop}. */ stop(force?: boolean, signal?: string): void + /** Send string to stdin */ + send(text: string): Promise /** The active `Timeout` object (if applicable). */ readonly timeout?: Timeout /** The logger being used by the process. */ @@ -160,6 +162,7 @@ export class ChildProcess { timeout, logger: this.#log, stop: this.stop.bind(this), + send: this.send.bind(this), reportError: (err) => errorHandler(err instanceof Error ? err : new Error(err)), } diff --git a/packages/core/src/shared/utilities/pathFind.ts b/packages/core/src/shared/utilities/pathFind.ts index eea5dd2ff27..2c9ff99b780 100644 --- a/packages/core/src/shared/utilities/pathFind.ts +++ b/packages/core/src/shared/utilities/pathFind.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode' import * as path from 'path' import fs from '../../shared/fs/fs' -import { ChildProcess } from './childProcess' +import { ChildProcess, ChildProcessOptions } from './childProcess' import { GitExtension } from '../extensions/git' import { Settings } from '../settings' import { getLogger } from '../logger/logger' @@ -31,11 +31,10 @@ export async function tryRun( args: string[], logging: 'yes' | 'no' | 'noresult' = 'yes', expected?: string, - onStdout?: (proc: ChildProcess, text: string) => void + opt?: ChildProcessOptions ): Promise { const proc = new ChildProcess(p, args, { logging: 'no' }) - const options = onStdout ? { onStdout: (text: string) => onStdout(proc, text) } : {} - const r = await proc.run(options) + const r = await proc.run(opt) const ok = r.exitCode === 0 && (expected === undefined || r.stdout.includes(expected)) if (logging === 'noresult') { getLogger().info('tryRun: %s: %s', ok ? 'ok' : 'failed', proc) From ab810f9174ae2dbf73a2a8e0d5a7f2c525b3c218 Mon Sep 17 00:00:00 2001 From: hkobew Date: Fri, 27 Sep 2024 10:19:43 -0400 Subject: [PATCH 12/14] fix Q giving stupid import --- packages/core/src/awsService/ec2/sshKeyPair.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/awsService/ec2/sshKeyPair.ts b/packages/core/src/awsService/ec2/sshKeyPair.ts index 9cb4499416c..237090de869 100644 --- a/packages/core/src/awsService/ec2/sshKeyPair.ts +++ b/packages/core/src/awsService/ec2/sshKeyPair.ts @@ -8,7 +8,7 @@ import { ToolkitError } from '../../shared/errors' import { tryRun } from '../../shared/utilities/pathFind' import { Timeout } from '../../shared/utilities/timeoutUtils' import { findAsync } from '../../shared/utilities/collectionUtils' -import { RunParameterContext } from '../../shared/utilities/childProcess' +import { RunParameterContext } from '../../shared/utilities/processUtils' type sshKeyType = 'rsa' | 'ed25519' From a4b01f102e0f61782298dc175fd7f1ae5c061b96 Mon Sep 17 00:00:00 2001 From: Hweinstock <42325418+Hweinstock@users.noreply.github.com> Date: Mon, 30 Sep 2024 09:34:11 -0400 Subject: [PATCH 13/14] change name to proc Co-authored-by: Justin M. Keyes --- packages/core/src/awsService/ec2/sshKeyPair.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/awsService/ec2/sshKeyPair.ts b/packages/core/src/awsService/ec2/sshKeyPair.ts index 237090de869..0bc01b12367 100644 --- a/packages/core/src/awsService/ec2/sshKeyPair.ts +++ b/packages/core/src/awsService/ec2/sshKeyPair.ts @@ -47,8 +47,8 @@ export class SshKeyPair { * @param keyType type of key to generate. */ public static async tryKeyGen(keyPath: string, keyType: sshKeyType): Promise { - const overrideKeys = async (_t: string, context: RunParameterContext) => { - await context.send('yes') + const overrideKeys = async (_t: string, proc: RunParameterContext) => { + await proc.send('yes') } return !(await tryRun('ssh-keygen', ['-t', keyType, '-N', '', '-q', '-f', keyPath], 'yes', 'unknown key type', { onStdout: overrideKeys, From c556308180e1a1b0c2802bc7e07fe022f567ecee Mon Sep 17 00:00:00 2001 From: hkobew Date: Mon, 30 Sep 2024 16:11:51 -0400 Subject: [PATCH 14/14] push fix by back a month --- packages/core/src/test/techdebt.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/test/techdebt.test.ts b/packages/core/src/test/techdebt.test.ts index 4818da88070..d83a8d68c85 100644 --- a/packages/core/src/test/techdebt.test.ts +++ b/packages/core/src/test/techdebt.test.ts @@ -53,6 +53,6 @@ describe('tech debt', function () { // Monitor telemtry to determine removal or snooze // toolkit_showNotification.id = sessionSeparation // auth_modifyConnection.action = deleteProfile OR auth_modifyConnection.source contains CodeCatalyst - fixByDate('2024-9-30', 'Remove the edge case code from the commit that this test is a part of.') + fixByDate('2024-10-30', 'Remove the edge case code from the commit that this test is a part of.') }) })