From b16aac90e0c27cd6a779c2bde8bfcebcbc52ed80 Mon Sep 17 00:00:00 2001 From: Daniel Helm Date: Fri, 16 Aug 2024 21:18:06 -0500 Subject: [PATCH 1/2] fix default working dir, fix ingress logic, add color and links --- .gitignore | 1 + package.json | 3 +- src/commands/test/contracts.ts | 197 ++++++++++++++++++--------------- src/commands/test/ingress.ts | 110 ++++++++++-------- 4 files changed, 174 insertions(+), 137 deletions(-) diff --git a/.gitignore b/.gitignore index 737438b..a36cfe1 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ pnpm-lock.yaml !.yarn/versions charts +.vscode diff --git a/package.json b/package.json index f65244a..f158234 100644 --- a/package.json +++ b/package.json @@ -84,5 +84,6 @@ "test": "mocha --forbid-only \"test/**/*.test.ts\"", "version": "oclif readme && git add README.md" }, - "types": "dist/index.d.ts" + "types": "dist/index.d.ts", + "packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72" } diff --git a/src/commands/test/contracts.ts b/src/commands/test/contracts.ts index 1b2b917..68605d8 100644 --- a/src/commands/test/contracts.ts +++ b/src/commands/test/contracts.ts @@ -3,7 +3,7 @@ import {ethers} from 'ethers' import {parseTomlConfig} from '../../utils/config-parser.js' import path from 'path' import cliProgress from 'cli-progress' -import { L1Contracts, L2Contracts, DeployedContract } from '../../data/contracts.js' +import {L1Contracts, L2Contracts, DeployedContract} from '../../data/contracts.js' interface ContractsConfig { [key: string]: string @@ -16,12 +16,12 @@ export default class TestContracts extends Command { config: Flags.string({ char: 'c', description: 'Path to config.toml file', - default: './charts/scroll-stack/config.toml', + default: './config.toml', }), contracts: Flags.string({ char: 't', description: 'Path to configs-contracts.toml file', - default: './charts/scroll-stack/configs-contracts.toml', + default: './config-contracts.toml', }), pod: Flags.boolean({ char: 'p', @@ -55,17 +55,21 @@ export default class TestContracts extends Command { // Check if RPC URLs are defined if (!l1RpcUrl || !l2RpcUrl) { - this.error(`Missing RPC URL(s) in ${configPath}. Please ensure L1_RPC_ENDPOINT and L2_RPC_ENDPOINT (for pod mode) or EXTERNAL_RPC_URI_L1 and EXTERNAL_RPC_URI_L2 (for non-pod mode) are defined.`); + this.error( + `Missing RPC URL(s) in ${configPath}. Please ensure L1_RPC_ENDPOINT and L2_RPC_ENDPOINT (for pod mode) or EXTERNAL_RPC_URI_L1 and EXTERNAL_RPC_URI_L2 (for non-pod mode) are defined.`, + ) } // Check if owner address is defined if (!owner) { - this.error(`Missing OWNER_ADDR in ${configPath}. Please ensure it is defined in the accounts section.`); + this.error(`Missing OWNER_ADDR in ${configPath}. Please ensure it is defined in the accounts section.`) } // Check if contractsConfig is empty if (Object.keys(contractsConfig).length === 0) { - this.error(`Contract configuration in ${contractsPath} is empty. Please ensure it contains the necessary contract addresses.`); + this.error( + `Contract configuration in ${contractsPath} is empty. Please ensure it contains the necessary contract addresses.`, + ) } const l1Provider = new ethers.JsonRpcProvider(l1RpcUrl) @@ -73,32 +77,33 @@ export default class TestContracts extends Command { // Check that config has a value for each required contract name - const l1Addresses : DeployedContract[] = L1Contracts.map(contract => { - const address = contractsConfig[contract.name]; + const l1Addresses: DeployedContract[] = L1Contracts.map((contract) => { + const address = contractsConfig[contract.name] if (!address) { - this.log(`Missing address for contract: ${contract.name}`); + this.log(`Missing address for contract: ${contract.name}`) } - return {...contract, address}; - }).filter(address => address !== undefined); + return {...contract, address} + }).filter((address) => address !== undefined) - const l2Addresses : DeployedContract[] = L2Contracts.map(contract => { - const address = contractsConfig[contract.name]; + const l2Addresses: DeployedContract[] = L2Contracts.map((contract) => { + const address = contractsConfig[contract.name] if (!address) { - this.log(`Missing address for contract: ${contract.name}`); + this.log(`Missing address for contract: ${contract.name}`) } - return {...contract, address}; - }).filter(address => address !== undefined); + return {...contract, address} + }).filter((address) => address !== undefined) try { - // Check Deployments - - const multibarDeployment = new cliProgress.MultiBar({ - clearOnComplete: false, - hideCursor: true, - format: ' {bar} | {percentage}% | {value}/{total} | {name}', - }, cliProgress.Presets.shades_classic) + const multibarDeployment = new cliProgress.MultiBar( + { + clearOnComplete: false, + hideCursor: true, + format: ' {bar} | {percentage}% | {value}/{total} | {name}', + }, + cliProgress.Presets.shades_classic, + ) let l1BarDeploy = multibarDeployment.create(l1Addresses.length, 0, {name: 'Checking L1 contract deployment...'}) let l2BarDeploy = multibarDeployment.create(l2Addresses.length, 0, {name: 'Checking L2 contract deployment...'}) @@ -106,101 +111,118 @@ export default class TestContracts extends Command { const notDeployed: DeployedContract[] = [] await Promise.all([ - this.checkContractDeployment(l1Provider, l1Addresses, l1BarDeploy, notDeployed ), - this.checkContractDeployment(l2Provider, l2Addresses, l2BarDeploy, notDeployed ), + this.checkContractDeployment(l1Provider, l1Addresses, l1BarDeploy, notDeployed), + this.checkContractDeployment(l2Provider, l2Addresses, l2BarDeploy, notDeployed), ]) - // Check Initializations - const multibarInitialization = new cliProgress.MultiBar({ - clearOnComplete: false, - hideCursor: true, - format: ' {bar} | {percentage}% | {value}/{total} | {name}', - }, cliProgress.Presets.shades_classic) - - const l1AddressesToInitialize = l1Addresses.filter(contract => contract.initializes && !notDeployed.some(nd => nd.name === contract.name)); - const l2AddressesToInitialize = l2Addresses.filter(contract => contract.initializes && !notDeployed.some(nd => nd.name === contract.name)); - - const l1BarInit = multibarDeployment.create(l1AddressesToInitialize.length, 0, {name: 'Checking L1 contract initialization...'}) - const l2BarInit = multibarDeployment.create(l2AddressesToInitialize.length, 0, {name: 'Checking L2 contract initialization...'}) + const multibarInitialization = new cliProgress.MultiBar( + { + clearOnComplete: false, + hideCursor: true, + format: ' {bar} | {percentage}% | {value}/{total} | {name}', + }, + cliProgress.Presets.shades_classic, + ) + + const l1AddressesToInitialize = l1Addresses.filter( + (contract) => contract.initializes && !notDeployed.some((nd) => nd.name === contract.name), + ) + const l2AddressesToInitialize = l2Addresses.filter( + (contract) => contract.initializes && !notDeployed.some((nd) => nd.name === contract.name), + ) + + const l1BarInit = multibarDeployment.create(l1AddressesToInitialize.length, 0, { + name: 'Checking L1 contract initialization...', + }) + const l2BarInit = multibarDeployment.create(l2AddressesToInitialize.length, 0, { + name: 'Checking L2 contract initialization...', + }) const notInitialized: DeployedContract[] = [] await Promise.all([ - this.checkContractInitialization(l1Provider, l1AddressesToInitialize, l1BarInit, notInitialized ), - this.checkContractInitialization(l2Provider, l2AddressesToInitialize, l2BarInit, notInitialized ), + this.checkContractInitialization(l1Provider, l1AddressesToInitialize, l1BarInit, notInitialized), + this.checkContractInitialization(l2Provider, l2AddressesToInitialize, l2BarInit, notInitialized), ]) - // Check Owner - const multibarOwner = new cliProgress.MultiBar({ - clearOnComplete: false, - hideCursor: true, - format: ' {bar} | {percentage}% | {value}/{total} | {name}', - }, cliProgress.Presets.shades_classic) - - const l1AddressesWithOwner = l1Addresses.filter(contract => contract.owned && !notDeployed.some(nd => nd.name === contract.name)); - const l2AddressesWithOwner = l2Addresses.filter(contract => contract.owned && !notDeployed.some(nd => nd.name === contract.name)); - - const l1BarOwner = multibarOwner.create(l1AddressesWithOwner.length, 0, {name: 'Checking L1 contract ownership...'}) - const l2BarOwner = multibarOwner.create(l2AddressesWithOwner.length, 0, {name: 'Checking L2 contract ownership...'}) + const multibarOwner = new cliProgress.MultiBar( + { + clearOnComplete: false, + hideCursor: true, + format: ' {bar} | {percentage}% | {value}/{total} | {name}', + }, + cliProgress.Presets.shades_classic, + ) + + const l1AddressesWithOwner = l1Addresses.filter( + (contract) => contract.owned && !notDeployed.some((nd) => nd.name === contract.name), + ) + const l2AddressesWithOwner = l2Addresses.filter( + (contract) => contract.owned && !notDeployed.some((nd) => nd.name === contract.name), + ) + + const l1BarOwner = multibarOwner.create(l1AddressesWithOwner.length, 0, { + name: 'Checking L1 contract ownership...', + }) + const l2BarOwner = multibarOwner.create(l2AddressesWithOwner.length, 0, { + name: 'Checking L2 contract ownership...', + }) const notOwned: DeployedContract[] = [] await Promise.all([ - this.checkContractOwner(l1Provider, l1AddressesWithOwner, l1BarOwner, owner, notOwned ), - this.checkContractOwner(l2Provider, l2AddressesWithOwner, l2BarOwner, owner, notOwned ), + this.checkContractOwner(l1Provider, l1AddressesWithOwner, l1BarOwner, owner, notOwned), + this.checkContractOwner(l2Provider, l2AddressesWithOwner, l2BarOwner, owner, notOwned), ]) - multibarDeployment.stop() multibarInitialization.stop() multibarOwner.stop() // Print results // Print results for correctly deployed, initialized, and owned contracts - const correctlyConfigured = [...l1Addresses, ...l2Addresses].filter(contract => - !notDeployed.some(nd => nd.name === contract.name) && - (!contract.initializes || !notInitialized.some(ni => ni.name === contract.name)) && - (!contract.owned || !notOwned.some(no => no.name === contract.name)) - ); - - this.log('\nCorrectly configured contracts:'); - correctlyConfigured.forEach(contract => { - let status = 'Deployed'; - if (contract.initializes) status += ', Initialized'; - if (contract.owned) status += ', Correctly Owned'; - this.log(`- ${contract.name} (${contract.address}): ${status}`); - }); + const correctlyConfigured = [...l1Addresses, ...l2Addresses].filter( + (contract) => + !notDeployed.some((nd) => nd.name === contract.name) && + (!contract.initializes || !notInitialized.some((ni) => ni.name === contract.name)) && + (!contract.owned || !notOwned.some((no) => no.name === contract.name)), + ) + + this.log('\nCorrectly configured contracts:') + correctlyConfigured.forEach((contract) => { + let status = 'Deployed' + if (contract.initializes) status += ', Initialized' + if (contract.owned) status += ', Correctly Owned' + this.log(`- ${contract.name} (${contract.address}): ${status}`) + }) if (notDeployed.length > 0) { this.log('\nContracts not deployed:') - notDeployed.forEach(contract => this.log(`- ${contract.name} (${contract.address})`)) + notDeployed.forEach((contract) => this.log(`- ${contract.name} (${contract.address})`)) } if (notInitialized.length > 0) { this.log('\nContracts not initialized:') - notInitialized.forEach(contract => this.log(`- ${contract.name} (${contract.address})`)) + notInitialized.forEach((contract) => this.log(`- ${contract.name} (${contract.address})`)) } if (notOwned.length > 0) { this.log('\nContracts without correct owner:') - notInitialized.forEach(contract => this.log(`- ${contract.name} (${contract.address})`)) + notInitialized.forEach((contract) => this.log(`- ${contract.name} (${contract.address})`)) } - if (notDeployed.length === 0 && notInitialized.length === 0 && notOwned.length === 0 ) { + if (notDeployed.length === 0 && notInitialized.length === 0 && notOwned.length === 0) { this.log('\nAll contracts are deployed, initialized and have owner set.') } - } catch (error) { this.error(`Failed to check contracts: ${error}`) } - } - private async checkContractDeployment( provider: ethers.Provider, contracts: DeployedContract[], @@ -212,10 +234,9 @@ export default class TestContracts extends Command { const code = await provider.getCode(c.address ?? '') if (code === '0x') { notDeployed.push(c) - } + } progressBar.increment() - } } @@ -228,16 +249,15 @@ export default class TestContracts extends Command { for (const c of contracts) { progressBar.update({name: `Checking ${c.name}...`}) try { - const initCount = await provider.getStorage(c?.address || '', 0); - if (parseInt(initCount) > 0 ) { + const initCount = await provider.getStorage(c?.address || '', 0) + if (parseInt(initCount) > 0) { notInitialized.push(c) - } + } } catch (error) { - this.error(`Error checking initialization for ${c.name}: ${error}`); + this.error(`Error checking initialization for ${c.name}: ${error}`) } progressBar.increment() - } } @@ -248,23 +268,22 @@ export default class TestContracts extends Command { expectedOwner: string, notOwned: DeployedContract[], ) { - const ownableABI = ['function owner() view returns (address)']; + const ownableABI = ['function owner() view returns (address)'] for (const c of contracts) { progressBar.update({name: `Checking ${c.name}...`}) if (c.owned && c.address) { - const contract = new ethers.Contract(c.address, ownableABI, provider); + const contract = new ethers.Contract(c.address, ownableABI, provider) try { - const owner = await contract.owner(); + const owner = await contract.owner() if (owner.toLowerCase() !== expectedOwner.toLowerCase()) { - notOwned.push(c); + notOwned.push(c) } } catch (error) { - this.error(`Error checking owner for ${c.name}: ${error}`); + this.error(`Error checking owner for ${c.name}: ${error}`) } } - progressBar.increment(); + progressBar.increment() } } - -} \ No newline at end of file +} diff --git a/src/commands/test/ingress.ts b/src/commands/test/ingress.ts index df1dd30..a303909 100644 --- a/src/commands/test/ingress.ts +++ b/src/commands/test/ingress.ts @@ -1,46 +1,20 @@ -import {Command, Flags} from '@oclif/core' import * as k8s from '@kubernetes/client-node' +import {Command, Flags} from '@oclif/core' +import chalk from 'chalk' +import terminalLink from 'terminal-link' export default class TestIngress extends Command { static override description = 'Check for required ingress hosts' static override flags = { dev: Flags.boolean({char: 'd', description: 'Include development ingresses'}), - namespace: Flags.string({char: 'n', description: 'Kubernetes namespace', default: 'default'}), - } - - private async getIngressHosts(namespace: string): Promise { - const kc = new k8s.KubeConfig() - kc.loadFromDefault() - const k8sApi = kc.makeApiClient(k8s.NetworkingV1Api) - - const response = await k8sApi.listNamespacedIngress(namespace) - const hosts: string[] = [] - - response.body.items.forEach(ingress => { - ingress.spec?.rules?.forEach(rule => { - if (rule.host) { - hosts.push(rule.host) - } - }) - }) - - return hosts - } - - private async checkHost(host: string): Promise { - try { - const response = await fetch(`http://${host}`) - return response.status === 200 - } catch (error) { - return false - } + namespace: Flags.string({char: 'n', default: 'default', description: 'Kubernetes namespace'}), } public async run(): Promise { const {flags} = await this.parse(TestIngress) - const requiredHosts = [ + const requiredNames = [ 'blockscout', 'bridge-history-api', 'frontends', @@ -50,34 +24,76 @@ export default class TestIngress extends Command { ] if (flags.dev) { - requiredHosts.push('l1-devnet', 'l1-explorer') + requiredNames.push('l1-devnet', 'l1-explorer') } try { - const actualHosts = await this.getIngressHosts(flags.namespace) - - this.log(`Found ingress hosts in namespace '${flags.namespace}':`) - actualHosts.forEach(host => this.log(`- ${host}`)) + const actualIngresses = await this.getIngresses(flags.namespace) + + this.log(chalk.cyan(`Found ingresses in namespace '${flags.namespace}':`)) + for (const [name, host] of Object.entries(actualIngresses)) { + this.log(`- ${chalk.green(name)}: ${terminalLink(host, `https://${host}`)}`) + } - const missingHosts = requiredHosts.filter(host => !actualHosts.includes(host)) + const missingNames = requiredNames.filter((name) => !Object.prototype.hasOwnProperty.call(actualIngresses, name)) - if (missingHosts.length > 0) { - this.log('\nMissing ingress hosts:') - missingHosts.forEach(host => this.log(`- ${host}`)) - this.error('Some required ingress hosts are missing!') + if (missingNames.length > 0) { + this.log(chalk.yellow('\nMissing ingresses:')) + for (const name of missingNames) this.log(chalk.red(`- ${name}`)) + this.error(chalk.red('Some required ingresses are missing!')) } else { - this.log('\nAll required ingress hosts are present.') + this.log(chalk.green('\nAll required ingresses are present.')) } - this.log('\nChecking connectivity to ingress hosts:') - for (const host of actualHosts) { + this.log(chalk.cyan('\nChecking connectivity to ingress hosts:')) + for (const [name, host] of Object.entries(actualIngresses)) { const isReachable = await this.checkHost(host) if (!isReachable) { - this.log(`- ${host} is not reachable or did not return a 200 status`) + this.log( + chalk.red( + `- ${name} (${terminalLink(host, `https://${host}`)}) is not reachable or did not return a 200 status`, + ), + ) + } else { + this.log(chalk.green(`- ${name} (${terminalLink(host, `https://${host}`)}) is reachable`)) } } + + this.log(chalk.cyan('\nList of ingresses (name: host):')) + for (const [name, host] of Object.entries(actualIngresses)) { + this.log(`- ${chalk.green(name)}: ${terminalLink(host, `https://${host}`)}`) + } } catch (error) { - this.error('Failed to retrieve ingress hosts: ' + error) + this.error(chalk.red('Failed to retrieve ingresses: ' + error)) + } + } + + private async checkHost(host: string): Promise { + try { + const response = await fetch(`https://${host}`) + return response.status === 200 + } catch { + return false + } + } + + private async getIngresses(namespace: string): Promise> { + const kc = new k8s.KubeConfig() + kc.loadFromDefault() + const k8sApi = kc.makeApiClient(k8s.NetworkingV1Api) + + const response = await k8sApi.listNamespacedIngress(namespace) + const ingresses: Record = {} + + for (const ingress of response.body.items) { + if (ingress.metadata?.name && ingress.spec?.rules && ingress.spec.rules.length > 0) { + const rule = ingress.spec.rules[0] + if (rule.host) { + ingresses[ingress.metadata.name] = rule.host + } + } } + + return ingresses } -} \ No newline at end of file +} From 92d0866d283bf598d953f39d8906b021bf7ab247 Mon Sep 17 00:00:00 2001 From: Daniel Helm Date: Fri, 16 Aug 2024 21:19:00 -0500 Subject: [PATCH 2/2] linter cleanup --- src/commands/test/contracts.ts | 38 +++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/commands/test/contracts.ts b/src/commands/test/contracts.ts index 68605d8..70c6f1a 100644 --- a/src/commands/test/contracts.ts +++ b/src/commands/test/contracts.ts @@ -1,9 +1,10 @@ import {Command, Flags} from '@oclif/core' +import cliProgress from 'cli-progress' import {ethers} from 'ethers' +import path from 'node:path' + +import {DeployedContract, L1Contracts, L2Contracts} from '../../data/contracts.js' import {parseTomlConfig} from '../../utils/config-parser.js' -import path from 'path' -import cliProgress from 'cli-progress' -import {L1Contracts, L2Contracts, DeployedContract} from '../../data/contracts.js' interface ContractsConfig { [key: string]: string @@ -15,18 +16,18 @@ export default class TestContracts extends Command { static flags = { config: Flags.string({ char: 'c', - description: 'Path to config.toml file', default: './config.toml', + description: 'Path to config.toml file', }), contracts: Flags.string({ char: 't', - description: 'Path to configs-contracts.toml file', default: './config-contracts.toml', + description: 'Path to configs-contracts.toml file', }), pod: Flags.boolean({ char: 'p', - description: 'Run inside Kubernetes pod', default: false, + description: 'Run inside Kubernetes pod', }), } @@ -82,6 +83,7 @@ export default class TestContracts extends Command { if (!address) { this.log(`Missing address for contract: ${contract.name}`) } + return {...contract, address} }).filter((address) => address !== undefined) @@ -90,6 +92,7 @@ export default class TestContracts extends Command { if (!address) { this.log(`Missing address for contract: ${contract.name}`) } + return {...contract, address} }).filter((address) => address !== undefined) @@ -99,14 +102,14 @@ export default class TestContracts extends Command { const multibarDeployment = new cliProgress.MultiBar( { clearOnComplete: false, - hideCursor: true, format: ' {bar} | {percentage}% | {value}/{total} | {name}', + hideCursor: true, }, cliProgress.Presets.shades_classic, ) - let l1BarDeploy = multibarDeployment.create(l1Addresses.length, 0, {name: 'Checking L1 contract deployment...'}) - let l2BarDeploy = multibarDeployment.create(l2Addresses.length, 0, {name: 'Checking L2 contract deployment...'}) + const l1BarDeploy = multibarDeployment.create(l1Addresses.length, 0, {name: 'Checking L1 contract deployment...'}) + const l2BarDeploy = multibarDeployment.create(l2Addresses.length, 0, {name: 'Checking L2 contract deployment...'}) const notDeployed: DeployedContract[] = [] @@ -120,8 +123,8 @@ export default class TestContracts extends Command { const multibarInitialization = new cliProgress.MultiBar( { clearOnComplete: false, - hideCursor: true, format: ' {bar} | {percentage}% | {value}/{total} | {name}', + hideCursor: true, }, cliProgress.Presets.shades_classic, ) @@ -152,8 +155,8 @@ export default class TestContracts extends Command { const multibarOwner = new cliProgress.MultiBar( { clearOnComplete: false, - hideCursor: true, format: ' {bar} | {percentage}% | {value}/{total} | {name}', + hideCursor: true, }, cliProgress.Presets.shades_classic, ) @@ -193,26 +196,26 @@ export default class TestContracts extends Command { ) this.log('\nCorrectly configured contracts:') - correctlyConfigured.forEach((contract) => { + for (const contract of correctlyConfigured) { let status = 'Deployed' if (contract.initializes) status += ', Initialized' if (contract.owned) status += ', Correctly Owned' this.log(`- ${contract.name} (${contract.address}): ${status}`) - }) + } if (notDeployed.length > 0) { this.log('\nContracts not deployed:') - notDeployed.forEach((contract) => this.log(`- ${contract.name} (${contract.address})`)) + for (const contract of notDeployed) this.log(`- ${contract.name} (${contract.address})`) } if (notInitialized.length > 0) { this.log('\nContracts not initialized:') - notInitialized.forEach((contract) => this.log(`- ${contract.name} (${contract.address})`)) + for (const contract of notInitialized) this.log(`- ${contract.name} (${contract.address})`) } if (notOwned.length > 0) { this.log('\nContracts without correct owner:') - notInitialized.forEach((contract) => this.log(`- ${contract.name} (${contract.address})`)) + for (const contract of notInitialized) this.log(`- ${contract.name} (${contract.address})`) } if (notDeployed.length === 0 && notInitialized.length === 0 && notOwned.length === 0) { @@ -250,7 +253,7 @@ export default class TestContracts extends Command { progressBar.update({name: `Checking ${c.name}...`}) try { const initCount = await provider.getStorage(c?.address || '', 0) - if (parseInt(initCount) > 0) { + if (Number.parseInt(initCount) > 0) { notInitialized.push(c) } } catch (error) { @@ -283,6 +286,7 @@ export default class TestContracts extends Command { this.error(`Error checking owner for ${c.name}: ${error}`) } } + progressBar.increment() } }