From 1dc408ca3b3b1ca4bc40bf352e554088749de79c Mon Sep 17 00:00:00 2001 From: Michal Kimle Date: Tue, 29 Nov 2022 10:27:52 +0100 Subject: [PATCH 1/4] Remove Airnode's short address from the receipt --- packages/airnode-deployer/src/cli/commands.test.ts | 1 - packages/airnode-deployer/src/utils/files.ts | 4 +--- .../test/fixtures/invalid-receipt.json | 1 - .../airnode-validator/src/receipt/receipt.test.ts | 1 - packages/airnode-validator/src/receipt/receipt.ts | 11 +---------- .../test/fixtures/receipt.valid.json | 1 - 6 files changed, 2 insertions(+), 17 deletions(-) diff --git a/packages/airnode-deployer/src/cli/commands.test.ts b/packages/airnode-deployer/src/cli/commands.test.ts index 46eb3d72e9..f90427d961 100644 --- a/packages/airnode-deployer/src/cli/commands.test.ts +++ b/packages/airnode-deployer/src/cli/commands.test.ts @@ -23,7 +23,6 @@ jest.mock('../utils', () => ({ const gcpReceipt: receipt.Receipt = { airnodeWallet: { airnodeAddress: '0xF347ADEd76F7AC2013e379078738aBfF75780C2e', - airnodeAddressShort: 'f347ade', airnodeXpub: 'xpub6CZqcAR5RtRPYYGbJe7MzFGbJkJ86ub9KtYvTtenPQRgxFXNCmR7woXjV8SdFPWrTBooAqWVLKe19KWBnaktkwUsvSEfH18HyxeNZQRJq8k', }, diff --git a/packages/airnode-deployer/src/utils/files.ts b/packages/airnode-deployer/src/utils/files.ts index 4f1ee6526c..f269731817 100644 --- a/packages/airnode-deployer/src/utils/files.ts +++ b/packages/airnode-deployer/src/utils/files.ts @@ -6,7 +6,7 @@ import { goSync } from '@api3/promise-utils'; import { logAndReturnError } from './infrastructure'; import { hashDeployment } from './cli'; import * as logger from '../utils/logger'; -import { deriveAirnodeAddress, deriveAirnodeXpub, shortenAirnodeAddress } from '../utils'; +import { deriveAirnodeAddress, deriveAirnodeXpub } from '../utils'; export function parseSecretsFile(secretsPath: string) { logger.debug('Parsing secrets file'); @@ -20,13 +20,11 @@ export function parseSecretsFile(secretsPath: string) { export function writeReceiptFile(receiptFilename: string, config: Config, timestamp: string, success: boolean) { const mnemonic = config.nodeSettings.airnodeWalletMnemonic; const airnodeAddress = deriveAirnodeAddress(mnemonic); - const airnodeAddressShort = shortenAirnodeAddress(airnodeAddress); const { stage, nodeVersion } = config.nodeSettings; const cloudProvider = config.nodeSettings.cloudProvider as CloudProvider; const receipt: receipt.Receipt = { airnodeWallet: { airnodeAddress, - airnodeAddressShort, airnodeXpub: deriveAirnodeXpub(mnemonic), }, deployment: { diff --git a/packages/airnode-deployer/test/fixtures/invalid-receipt.json b/packages/airnode-deployer/test/fixtures/invalid-receipt.json index b72fb86511..eb7d48809d 100644 --- a/packages/airnode-deployer/test/fixtures/invalid-receipt.json +++ b/packages/airnode-deployer/test/fixtures/invalid-receipt.json @@ -1,7 +1,6 @@ { "airnodeWallet": { "airnodeAddress": "0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace", - "airnodeAddressShort": "a30ca71", "airnodeXpub": "xpub6C8tvRgYkjNVaGMtpyZf4deBcUQHf7vgWUraVxY6gYiZhBYbPkFkLLWJzUUeVFdkKpVtatmXHX8kB76xgfmTpVZWbVWdq1rneaAY6a8RtbY" }, "deployment": { diff --git a/packages/airnode-validator/src/receipt/receipt.test.ts b/packages/airnode-validator/src/receipt/receipt.test.ts index 622179d3b9..bcf72480ee 100644 --- a/packages/airnode-validator/src/receipt/receipt.test.ts +++ b/packages/airnode-validator/src/receipt/receipt.test.ts @@ -29,7 +29,6 @@ it(`doesn't allow extraneous properties`, () => { describe('airnodeWalletSchema', () => { const airnodeWallet: AirnodeWallet = { airnodeAddress: '0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace', - airnodeAddressShort: 'a30ca71', airnodeXpub: 'xpub6C8tvRgYkjNVaGMtpyZf4deBcUQHf7vgWUraVxY6gYiZhBYbPkFkLLWJzUUeVFdkKpVtatmXHX8kB76xgfmTpVZWbVWdq1rneaAY6a8RtbY', }; diff --git a/packages/airnode-validator/src/receipt/receipt.ts b/packages/airnode-validator/src/receipt/receipt.ts index 81bf7e3893..1f3cf29fc9 100644 --- a/packages/airnode-validator/src/receipt/receipt.ts +++ b/packages/airnode-validator/src/receipt/receipt.ts @@ -11,11 +11,10 @@ const ISO_DATE_REGEX = export const airnodeWalletSchema = z .object({ airnodeAddress: z.string(), - airnodeAddressShort: z.string(), airnodeXpub: z.string(), }) .strict() - .superRefine(({ airnodeAddress, airnodeAddressShort }, ctx) => { + .superRefine(({ airnodeAddress }, ctx) => { if (!ethers.utils.isAddress(airnodeAddress)) { ctx.addIssue({ code: z.ZodIssueCode.custom, @@ -25,14 +24,6 @@ export const airnodeWalletSchema = z return; } - - if (airnodeAddress.substring(2, 9).toLowerCase() !== airnodeAddressShort) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: `Short Airnode address doesn't match Airnode address`, - path: ['airnodeAddressShort'], - }); - } }); export const deploymentSchema = z diff --git a/packages/airnode-validator/test/fixtures/receipt.valid.json b/packages/airnode-validator/test/fixtures/receipt.valid.json index ad474381f7..7dfd63a0e0 100644 --- a/packages/airnode-validator/test/fixtures/receipt.valid.json +++ b/packages/airnode-validator/test/fixtures/receipt.valid.json @@ -1,7 +1,6 @@ { "airnodeWallet": { "airnodeAddress": "0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace", - "airnodeAddressShort": "a30ca71", "airnodeXpub": "xpub6C8tvRgYkjNVaGMtpyZf4deBcUQHf7vgWUraVxY6gYiZhBYbPkFkLLWJzUUeVFdkKpVtatmXHX8kB76xgfmTpVZWbVWdq1rneaAY6a8RtbY" }, "deployment": { From 6ac7cfacc46ddd5b38e379e99348303657349371 Mon Sep 17 00:00:00 2001 From: Michal Kimle Date: Tue, 29 Nov 2022 15:31:21 +0100 Subject: [PATCH 2/4] Move deployment ID derivation function to `airnode-node` --- .../src/infrastructure/index.ts | 14 +++---- .../airnode-deployer/src/utils/cli.test.ts | 31 -------------- packages/airnode-deployer/src/utils/cli.ts | 37 ----------------- packages/airnode-deployer/src/utils/files.ts | 5 +-- packages/airnode-node/src/index.ts | 1 + .../airnode-node/src/workers/index.test.ts | 32 +++++++++++++++ packages/airnode-node/src/workers/index.ts | 40 +++++++++++++++++++ 7 files changed, 80 insertions(+), 80 deletions(-) diff --git a/packages/airnode-deployer/src/infrastructure/index.ts b/packages/airnode-deployer/src/infrastructure/index.ts index b32c456599..44de850429 100644 --- a/packages/airnode-deployer/src/infrastructure/index.ts +++ b/packages/airnode-deployer/src/infrastructure/index.ts @@ -11,6 +11,8 @@ import { Config, evm, availableCloudProviders, + deriveDeploymentId, + deriveDeploymentVersionId, } from '@api3/airnode-node'; import { consoleLog } from '@api3/airnode-utilities'; import { go } from '@api3/promise-utils'; @@ -36,13 +38,7 @@ import { } from '../utils/infrastructure'; import { version as nodeVersion } from '../../package.json'; import { deriveAirnodeAddress, shortenAirnodeAddress } from '../utils'; -import { - airnodeAddressReadable, - cloudProviderReadable, - hashDeployment, - hashDeploymentVersion, - timestampReadable, -} from '../utils/cli'; +import { airnodeAddressReadable, cloudProviderReadable, timestampReadable } from '../utils/cli'; export const TF_STATE_FILENAME = 'default.tfstate'; @@ -486,10 +482,10 @@ async function fetchDeployments(cloudProviderType: CloudProvider['type'], deploy const cloudProvider = interpolatedConfig.nodeSettings.cloudProvider as CloudProvider; const airnodeVersion = interpolatedConfig.nodeSettings.nodeVersion; - const id = hashDeployment(cloudProvider, airnodeAddress, stage, airnodeVersion); + const id = deriveDeploymentId(cloudProvider, airnodeAddress, stage, airnodeVersion); const deploymentVersions = Object.keys(stageDirectory.children).map((versionTimestamp) => ({ - id: hashDeploymentVersion(cloudProvider, airnodeAddress, stage, airnodeVersion, versionTimestamp), + id: deriveDeploymentVersionId(cloudProvider, airnodeAddress, stage, airnodeVersion, versionTimestamp), timestamp: versionTimestamp, })); const deployment = { diff --git a/packages/airnode-deployer/src/utils/cli.test.ts b/packages/airnode-deployer/src/utils/cli.test.ts index 532a5ad19c..d7da391611 100644 --- a/packages/airnode-deployer/src/utils/cli.test.ts +++ b/packages/airnode-deployer/src/utils/cli.test.ts @@ -2,11 +2,9 @@ import { CloudProvider } from '@api3/airnode-node'; import { airnodeAddressReadable, cloudProviderReadable, - hashDeployment, timestampReadable, longArguments, printableArguments, - hashDeploymentVersion, } from './cli'; describe('longArguments', () => { @@ -31,35 +29,6 @@ describe('printableArguments', () => { }); }); -describe('hashDeployment', () => { - it('creates a unique hash from deployment details', () => { - const cloudProvider = { - type: 'aws', - region: 'us-east-1', - } as CloudProvider; - const airnodeAddress = '0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace'; - const stage = 'dev'; - const airnodeVersion = '0.9.5'; - - expect(hashDeployment(cloudProvider, airnodeAddress, stage, airnodeVersion)).toEqual('aws521d7174'); - }); -}); - -describe('hashDeploymentVersion', () => { - it('creates a unique hash from deployment version details', () => { - const cloudProvider = { - type: 'aws', - region: 'us-east-1', - } as CloudProvider; - const airnodeAddress = '0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace'; - const stage = 'dev'; - const airnodeVersion = '0.9.5'; - const timestamp = '1664256335137'; - - expect(hashDeploymentVersion(cloudProvider, airnodeAddress, stage, airnodeVersion, timestamp)).toEqual('e2d3286d'); - }); -}); - describe('cloudProviderReadable', () => { it('returns a human-readble cloud provider identification', () => { const cloudProvider = { diff --git a/packages/airnode-deployer/src/utils/cli.ts b/packages/airnode-deployer/src/utils/cli.ts index 6c395e449f..232228a80a 100644 --- a/packages/airnode-deployer/src/utils/cli.ts +++ b/packages/airnode-deployer/src/utils/cli.ts @@ -1,4 +1,3 @@ -import * as crypto from 'crypto'; import { format } from 'date-fns-tz'; import { CloudProvider } from '@api3/airnode-node'; import join from 'lodash/join'; @@ -15,42 +14,6 @@ export function printableArguments(args: string[]) { ); } -function cloudProviderHashElements(cloudProvider: CloudProvider) { - const hashElements = [cloudProvider.type, cloudProvider.region]; - if (cloudProvider.type === 'gcp') { - hashElements.push(cloudProvider.projectId); - } - - return hashElements; -} - -export function hashDeployment( - cloudProvider: CloudProvider, - airnodeAddress: string, - stage: string, - airnodeVersion: string -) { - return `${cloudProvider.type}${crypto - .createHash('sha256') - .update([...cloudProviderHashElements(cloudProvider), airnodeAddress, stage, airnodeVersion].join('')) - .digest('hex') - .substring(0, 8)}`; -} - -export function hashDeploymentVersion( - cloudProvider: CloudProvider, - airnodeAddress: string, - stage: string, - airnodeVersion: string, - timestamp: string -) { - return crypto - .createHash('sha256') - .update([...cloudProviderHashElements(cloudProvider), airnodeAddress, stage, airnodeVersion, timestamp].join('')) - .digest('hex') - .substring(0, 8); -} - export function cloudProviderReadable(cloudProvider: CloudProvider) { return `${cloudProvider.type.toUpperCase()} (${cloudProvider.region})`; } diff --git a/packages/airnode-deployer/src/utils/files.ts b/packages/airnode-deployer/src/utils/files.ts index f269731817..022b43e3bd 100644 --- a/packages/airnode-deployer/src/utils/files.ts +++ b/packages/airnode-deployer/src/utils/files.ts @@ -1,10 +1,9 @@ import * as fs from 'fs'; import * as dotenv from 'dotenv'; -import { CloudProvider, Config } from '@api3/airnode-node'; +import { CloudProvider, Config, deriveDeploymentId } from '@api3/airnode-node'; import { parseReceipt, receipt } from '@api3/airnode-validator'; import { goSync } from '@api3/promise-utils'; import { logAndReturnError } from './infrastructure'; -import { hashDeployment } from './cli'; import * as logger from '../utils/logger'; import { deriveAirnodeAddress, deriveAirnodeXpub } from '../utils'; @@ -28,7 +27,7 @@ export function writeReceiptFile(receiptFilename: string, config: Config, timest airnodeXpub: deriveAirnodeXpub(mnemonic), }, deployment: { - deploymentId: hashDeployment(cloudProvider, airnodeAddress, stage, nodeVersion), + deploymentId: deriveDeploymentId(cloudProvider, airnodeAddress, stage, nodeVersion), cloudProvider, stage, nodeVersion, diff --git a/packages/airnode-node/src/index.ts b/packages/airnode-node/src/index.ts index a6772009ac..9a9ae5e37a 100644 --- a/packages/airnode-node/src/index.ts +++ b/packages/airnode-node/src/index.ts @@ -8,5 +8,6 @@ export * from './types'; export * from './version'; export * from './config'; export * from './validation'; +export * from './workers'; export * from './workers/local-gateways'; export * from './constants'; diff --git a/packages/airnode-node/src/workers/index.test.ts b/packages/airnode-node/src/workers/index.test.ts index ef10382950..43e0c43f87 100644 --- a/packages/airnode-node/src/workers/index.test.ts +++ b/packages/airnode-node/src/workers/index.test.ts @@ -9,6 +9,7 @@ jest.mock('./local-handlers', () => ({ })); import * as fixtures from '../../test/fixtures'; +import { CloudProvider } from '../config'; import { WorkerParameters } from '../types'; import * as workers from './index'; @@ -47,3 +48,34 @@ describe('spawn', () => { expect(spawnLocalMock).toHaveBeenCalledWith(parameters.payload); }); }); + +describe('deriveDeploymentId', () => { + it('creates a unique hash from deployment details', () => { + const cloudProvider = { + type: 'aws', + region: 'us-east-1', + } as CloudProvider; + const airnodeAddress = '0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace'; + const stage = 'dev'; + const airnodeVersion = '0.9.5'; + + expect(workers.deriveDeploymentId(cloudProvider, airnodeAddress, stage, airnodeVersion)).toEqual('aws521d7174'); + }); +}); + +describe('deriveDeploymentVersionId', () => { + it('creates a unique hash from deployment version details', () => { + const cloudProvider = { + type: 'aws', + region: 'us-east-1', + } as CloudProvider; + const airnodeAddress = '0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace'; + const stage = 'dev'; + const airnodeVersion = '0.9.5'; + const timestamp = '1664256335137'; + + expect(workers.deriveDeploymentVersionId(cloudProvider, airnodeAddress, stage, airnodeVersion, timestamp)).toEqual( + 'e2d3286d' + ); + }); +}); diff --git a/packages/airnode-node/src/workers/index.ts b/packages/airnode-node/src/workers/index.ts index b34b31f21d..20a4d379cd 100644 --- a/packages/airnode-node/src/workers/index.ts +++ b/packages/airnode-node/src/workers/index.ts @@ -1,7 +1,11 @@ +import * as crypto from 'crypto'; import * as aws from './cloud-platforms/aws'; import * as gcp from './cloud-platforms/gcp'; import * as localHandlers from './local-handlers'; import { WorkerParameters, WorkerResponse } from '../types'; +import { CloudProvider } from '../config'; + +const DEPLOYMENT_ID_LENGTH = 8; export function spawn(params: WorkerParameters): Promise { switch (params.cloudProvider.type) { @@ -22,3 +26,39 @@ export function spawn(params: WorkerParameters): Promise { } } } + +function cloudProviderHashElements(cloudProvider: CloudProvider) { + const hashElements = [cloudProvider.type, cloudProvider.region]; + if (cloudProvider.type === 'gcp') { + hashElements.push(cloudProvider.projectId); + } + + return hashElements; +} + +export function deriveDeploymentId( + cloudProvider: CloudProvider, + airnodeAddress: string, + stage: string, + airnodeVersion: string +) { + return `${cloudProvider.type}${crypto + .createHash('sha256') + .update([...cloudProviderHashElements(cloudProvider), airnodeAddress, stage, airnodeVersion].join('')) + .digest('hex') + .substring(0, DEPLOYMENT_ID_LENGTH)}`; +} + +export function deriveDeploymentVersionId( + cloudProvider: CloudProvider, + airnodeAddress: string, + stage: string, + airnodeVersion: string, + timestamp: string +) { + return crypto + .createHash('sha256') + .update([...cloudProviderHashElements(cloudProvider), airnodeAddress, stage, airnodeVersion, timestamp].join('')) + .digest('hex') + .substring(0, DEPLOYMENT_ID_LENGTH); +} From 82273813137f66e03b50f37b3a40f11f84691dd5 Mon Sep 17 00:00:00 2001 From: Michal Kimle Date: Thu, 1 Dec 2022 13:49:23 +0100 Subject: [PATCH 3/4] Replace Airnode address short with deployment ID --- .changeset/unlucky-eggs-drive.md | 7 +++ .../src/infrastructure/index.test.ts | 45 +++++++---------- .../src/infrastructure/index.ts | 48 +++++++------------ packages/airnode-deployer/src/utils/evm.ts | 12 ----- .../terraform/aws/variables.tf | 18 +++---- .../terraform/gcp/variables.tf | 18 +++---- .../src/adapters/http/worker.test.ts | 8 ++-- .../airnode-node/src/coordinator/state.ts | 6 ++- packages/airnode-node/src/evm/wallet.test.ts | 8 ---- packages/airnode-node/src/evm/wallet.ts | 5 -- packages/airnode-node/src/evm/workers.test.ts | 10 ++-- .../src/handlers/start-coordinator.test.ts | 2 +- .../src/handlers/start-coordinator.ts | 3 +- .../src/providers/actions.test.ts | 4 +- .../airnode-node/src/providers/state.test.ts | 4 +- packages/airnode-node/src/providers/state.ts | 4 +- .../airnode-node/src/providers/worker.test.ts | 6 +-- packages/airnode-node/src/types.ts | 5 +- .../src/workers/cloud-platforms/aws.test.ts | 8 ++-- .../src/workers/cloud-platforms/aws.ts | 2 +- .../src/workers/cloud-platforms/gcp.test.ts | 4 +- .../src/workers/cloud-platforms/gcp.ts | 2 +- packages/airnode-node/src/workers/index.ts | 13 +++-- .../test/fixtures/config/worker-options.ts | 3 +- 24 files changed, 95 insertions(+), 150 deletions(-) create mode 100644 .changeset/unlucky-eggs-drive.md diff --git a/.changeset/unlucky-eggs-drive.md b/.changeset/unlucky-eggs-drive.md new file mode 100644 index 0000000000..545c6baeab --- /dev/null +++ b/.changeset/unlucky-eggs-drive.md @@ -0,0 +1,7 @@ +--- +'@api3/airnode-deployer': minor +'@api3/airnode-node': minor +'@api3/airnode-validator': minor +--- + +Replace Airnode's short address with deployment ID diff --git a/packages/airnode-deployer/src/infrastructure/index.test.ts b/packages/airnode-deployer/src/infrastructure/index.test.ts index fbc7dd8e5d..415ee42c52 100644 --- a/packages/airnode-deployer/src/infrastructure/index.test.ts +++ b/packages/airnode-deployer/src/infrastructure/index.test.ts @@ -246,8 +246,7 @@ describe('prepareCloudProviderAirnodeApplyDestoryArguments', () => { describe('prepareAirnodeApplyDestroyArguments', () => { const variables = { - airnodeAddressShort: 'd0624e6', - stage: 'dev', + deploymentId: 'aws7195b548', configPath: '/some/path/config.json', secretsPath: '/some/path/secrets.env', handlerDir: '/some/path/handlers', @@ -257,8 +256,7 @@ describe('prepareAirnodeApplyDestroyArguments', () => { it('returns cloud provider agnostic Terraform variables', () => { const expectedArguments = [ - ['var', 'airnode_address_short', variables.airnodeAddressShort], - ['var', 'stage', variables.stage], + ['var', 'deployment_id', variables.deploymentId], ['var', 'configuration_file', variables.configPath], ['var', 'secrets_file', variables.secretsPath], ['var', 'handler_dir', variables.handlerDir], @@ -272,14 +270,11 @@ describe('prepareAirnodeApplyDestroyArguments', () => { it('sets the missing optional arguments correctly', () => { const onlyRequiredVariables = pick(variables, [ - 'airnodeAddressShort', - 'stage', 'handlerDir', 'disableConcurrencyReservations', ]) as infrastructure.AirnodeApplyDestroyVariables; const expectedArguments = [ - ['var', 'airnode_address_short', onlyRequiredVariables.airnodeAddressShort], - ['var', 'stage', onlyRequiredVariables.stage], + ['var', 'deployment_id', onlyRequiredVariables.deploymentId], ['var', 'configuration_file', 'NULL'], ['var', 'secrets_file', 'NULL'], ['var', 'handler_dir', onlyRequiredVariables.handlerDir], @@ -354,7 +349,7 @@ describe('terraformAirnodeApply', () => { ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform apply -var="aws_region=us-east-1" -var="airnode_address_short=a30ca71" -var="stage=dev" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -var="http_gateway_enabled=true" -var="http_max_concurrency=20" -var="http_signed_data_gateway_enabled=true" -var="http_signed_data_max_concurrency=20" -auto-approve`, + `terraform apply -var="aws_region=us-east-1" -var="deployment_id=aws40207f25" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -var="http_gateway_enabled=true" -var="http_max_concurrency=20" -var="http_signed_data_gateway_enabled=true" -var="http_signed_data_max_concurrency=20" -auto-approve`, execOptions ); }); @@ -380,12 +375,12 @@ describe('terraformAirnodeApply', () => { ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform import -var="gcp_region=us-east1" -var="gcp_project=airnode-test-123456" -var="airnode_bucket=airnode-123456789" -var="deployment_bucket_dir=airnode-address/stage/timestamp" -var="airnode_address_short=a30ca71" -var="stage=dev" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -var="http_gateway_enabled=true" -var="http_max_concurrency=20" -var="http_signed_data_gateway_enabled=true" -var="http_signed_data_max_concurrency=20" module.startCoordinator.google_app_engine_application.app[0] airnode-test-123456`, + `terraform import -var="gcp_region=us-east1" -var="gcp_project=airnode-test-123456" -var="airnode_bucket=airnode-123456789" -var="deployment_bucket_dir=airnode-address/stage/timestamp" -var="deployment_id=gcp1fc73e56" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -var="http_gateway_enabled=true" -var="http_max_concurrency=20" -var="http_signed_data_gateway_enabled=true" -var="http_signed_data_max_concurrency=20" module.startCoordinator.google_app_engine_application.app[0] airnode-test-123456`, { ignoreError: true } ); expect(exec).toHaveBeenNthCalledWith( 3, - `terraform apply -var="gcp_region=us-east1" -var="gcp_project=airnode-test-123456" -var="airnode_bucket=airnode-123456789" -var="deployment_bucket_dir=airnode-address/stage/timestamp" -var="airnode_address_short=a30ca71" -var="stage=dev" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -var="http_gateway_enabled=true" -var="http_max_concurrency=20" -var="http_signed_data_gateway_enabled=true" -var="http_signed_data_max_concurrency=20" -auto-approve`, + `terraform apply -var="gcp_region=us-east1" -var="gcp_project=airnode-test-123456" -var="airnode_bucket=airnode-123456789" -var="deployment_bucket_dir=airnode-address/stage/timestamp" -var="deployment_id=gcp1fc73e56" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -var="http_gateway_enabled=true" -var="http_max_concurrency=20" -var="http_signed_data_gateway_enabled=true" -var="http_signed_data_max_concurrency=20" -auto-approve`, execOptions ); }); @@ -419,7 +414,7 @@ describe('terraformAirnodeApply', () => { ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform apply -var="aws_region=us-east-1" -var="airnode_address_short=a30ca71" -var="stage=dev" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -auto-approve`, + `terraform apply -var="aws_region=us-east-1" -var="deployment_id=aws40207f25" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -auto-approve`, execOptions ); }); @@ -487,7 +482,7 @@ describe('deployAirnode', () => { ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform apply -var="aws_region=us-east-1" -var="airnode_address_short=a30ca71" -var="stage=dev" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -var="http_gateway_enabled=true" -var="http_max_concurrency=20" -var="http_signed_data_gateway_enabled=true" -var="http_signed_data_max_concurrency=20" -auto-approve`, + `terraform apply -var="aws_region=us-east-1" -var="deployment_id=aws40207f25" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -var="http_gateway_enabled=true" -var="http_max_concurrency=20" -var="http_signed_data_gateway_enabled=true" -var="http_signed_data_max_concurrency=20" -auto-approve`, { cwd: 'tmpDir' } ); expect(exec).toHaveBeenNthCalledWith(3, 'terraform output -json -no-color', { cwd: 'tmpDir' }); @@ -527,7 +522,7 @@ describe('deployAirnode', () => { ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform apply -var="aws_region=us-east-1" -var="airnode_address_short=a30ca71" -var="stage=dev" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -var="http_gateway_enabled=true" -var="http_max_concurrency=20" -var="http_signed_data_gateway_enabled=true" -var="http_signed_data_max_concurrency=20" -auto-approve`, + `terraform apply -var="aws_region=us-east-1" -var="deployment_id=aws40207f25" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -var="http_gateway_enabled=true" -var="http_max_concurrency=20" -var="http_signed_data_gateway_enabled=true" -var="http_signed_data_max_concurrency=20" -auto-approve`, { cwd: 'tmpDir' } ); expect(exec).toHaveBeenNthCalledWith(3, 'terraform output -json -no-color', { cwd: 'tmpDir' }); @@ -570,7 +565,7 @@ describe('deployAirnode', () => { ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform apply -var="aws_region=us-east-1" -var="airnode_address_short=a30ca71" -var="stage=dev" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -var="http_gateway_enabled=true" -var="http_max_concurrency=20" -var="http_signed_data_gateway_enabled=true" -var="http_signed_data_max_concurrency=20" -auto-approve`, + `terraform apply -var="aws_region=us-east-1" -var="deployment_id=aws40207f25" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -var="http_gateway_enabled=true" -var="http_max_concurrency=20" -var="http_signed_data_gateway_enabled=true" -var="http_signed_data_max_concurrency=20" -auto-approve`, { cwd: 'tmpDir' } ); expect(exec).toHaveBeenNthCalledWith(3, 'terraform output -json -no-color', { cwd: 'tmpDir' }); @@ -638,8 +633,7 @@ describe('terraformAirnodeDestroy', () => { region: 'europe-central-1', } as AwsCloudProvider; const handlerDir = path.resolve(`${__dirname}/../../.webpack`); - const airnodeAddressShort = 'a30ca71'; - const stage = 'dev'; + const deploymentId = 'aws7195b548'; const bucket = { name: 'airnode-123456789', region: 'us-east-1', @@ -650,14 +644,7 @@ describe('terraformAirnodeDestroy', () => { const commandOutput = 'example command output'; exec.mockImplementation(() => ({ stdout: commandOutput })); - await infrastructure.terraformAirnodeDestroy( - execOptions, - cloudProvider, - airnodeAddressShort, - stage, - bucket, - bucketPath - ); + await infrastructure.terraformAirnodeDestroy(execOptions, cloudProvider, deploymentId, bucket, bucketPath); expect(exec).toHaveBeenNthCalledWith( 1, `terraform init -backend-config="region=us-east-1" -backend-config="bucket=airnode-123456789" -backend-config="key=airnode-address/stage/timestamp/default.tfstate" -from-module=${terraformDir}/aws`, @@ -665,7 +652,7 @@ describe('terraformAirnodeDestroy', () => { ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform destroy -var="aws_region=europe-central-1" -var="airnode_address_short=a30ca71" -var="stage=dev" -var="configuration_file=NULL" -var="secrets_file=NULL" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=NULL" -input=false -no-color -auto-approve`, + `terraform destroy -var="aws_region=europe-central-1" -var="deployment_id=aws7195b548" -var="configuration_file=NULL" -var="secrets_file=NULL" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=NULL" -input=false -no-color -auto-approve`, execOptions ); }); @@ -734,7 +721,7 @@ describe('removeAirnode', () => { ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform destroy -var="aws_region=us-east-1" -var="airnode_address_short=d0624e6" -var="stage=dev" -var="configuration_file=NULL" -var="secrets_file=NULL" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=NULL" -input=false -no-color -auto-approve`, + `terraform destroy -var="aws_region=us-east-1" -var="deployment_id=${happyPathDeploymentId}" -var="configuration_file=NULL" -var="secrets_file=NULL" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=NULL" -input=false -no-color -auto-approve`, { cwd: 'tmpDir' } ); expect(awsGetBucketDirectoryStructureSpy).toHaveBeenNthCalledWith(2, bucket.name); @@ -765,7 +752,7 @@ describe('removeAirnode', () => { ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform destroy -var="aws_region=us-east-1" -var="airnode_address_short=a30ca71" -var="stage=dev" -var="configuration_file=NULL" -var="secrets_file=NULL" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=NULL" -input=false -no-color -auto-approve`, + `terraform destroy -var="aws_region=us-east-1" -var="deployment_id=${deploymentId}" -var="configuration_file=NULL" -var="secrets_file=NULL" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=NULL" -input=false -no-color -auto-approve`, { cwd: 'tmpDir' } ); expect(awsGetBucketDirectoryStructureSpy).toHaveBeenNthCalledWith(2, bucket.name); @@ -805,7 +792,7 @@ describe('removeAirnode', () => { ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform destroy -var="aws_region=us-east-1" -var="airnode_address_short=a30ca71" -var="stage=dev" -var="configuration_file=NULL" -var="secrets_file=NULL" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=NULL" -input=false -no-color -auto-approve`, + `terraform destroy -var="aws_region=us-east-1" -var="deployment_id=${deploymentId}" -var="configuration_file=NULL" -var="secrets_file=NULL" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=NULL" -input=false -no-color -auto-approve`, { cwd: 'tmpDir' } ); expect(awsGetBucketDirectoryStructureSpy).toHaveBeenNthCalledWith(2, bucket.name); diff --git a/packages/airnode-deployer/src/infrastructure/index.ts b/packages/airnode-deployer/src/infrastructure/index.ts index 44de850429..d115ebeed2 100644 --- a/packages/airnode-deployer/src/infrastructure/index.ts +++ b/packages/airnode-deployer/src/infrastructure/index.ts @@ -37,7 +37,7 @@ import { Bucket, } from '../utils/infrastructure'; import { version as nodeVersion } from '../../package.json'; -import { deriveAirnodeAddress, shortenAirnodeAddress } from '../utils'; +import { deriveAirnodeAddress } from '../utils'; import { airnodeAddressReadable, cloudProviderReadable, timestampReadable } from '../utils/cli'; export const TF_STATE_FILENAME = 'default.tfstate'; @@ -188,8 +188,7 @@ export function prepareCloudProviderAirnodeApplyDestoryArguments( } export type AirnodeApplyDestroyVariables = { - airnodeAddressShort: string; - stage: string; + deploymentId: string; configPath?: string; secretsPath?: string; handlerDir: string; @@ -198,19 +197,11 @@ export type AirnodeApplyDestroyVariables = { }; export function prepareAirnodeApplyDestroyArguments(variables: AirnodeApplyDestroyVariables): CommandArg[] { - const { - airnodeAddressShort, - stage, - configPath, - secretsPath, - handlerDir, - disableConcurrencyReservations, - airnodeWalletPrivateKey, - } = variables; + const { deploymentId, configPath, secretsPath, handlerDir, disableConcurrencyReservations, airnodeWalletPrivateKey } = + variables; return [ - ['var', 'airnode_address_short', airnodeAddressShort], - ['var', 'stage', stage], + ['var', 'deployment_id', deploymentId], ['var', 'configuration_file', configPath ? path.resolve(configPath) : 'NULL'], ['var', 'secrets_file', secretsPath ? path.resolve(secretsPath) : 'NULL'], ['var', 'handler_dir', handlerDir], @@ -245,17 +236,22 @@ export async function terraformAirnodeApply( configPath: string, secretsPath: string ) { - const { airnodeWalletMnemonic, stage, httpGateway, httpSignedDataGateway } = config.nodeSettings; + const { + airnodeWalletMnemonic, + stage, + httpGateway, + httpSignedDataGateway, + nodeVersion: configNodeVersion, + } = config.nodeSettings; const cloudProvider = config.nodeSettings.cloudProvider as CloudProvider; - const airnodeAddressShort = shortenAirnodeAddress(deriveAirnodeAddress(airnodeWalletMnemonic)); + const airnodeAddress = deriveAirnodeAddress(airnodeWalletMnemonic); const airnodeWalletPrivateKey = evm.getAirnodeWallet(config).privateKey; const maxConcurrency = config.chains.reduce((concurrency: number, chain) => concurrency + chain.maxConcurrency, 0); await terraformAirnodeInit(execOptions, cloudProvider, bucket, bucketDeploymentPath); const commonArguments = prepareAirnodeApplyDestroyArguments({ - airnodeAddressShort, - stage, + deploymentId: deriveDeploymentId(cloudProvider, airnodeAddress, stage, configNodeVersion), configPath, secretsPath, handlerDir, @@ -395,16 +391,14 @@ export const deployAirnode = async (config: Config, configPath: string, secretsP export async function terraformAirnodeDestroy( execOptions: child.ExecOptions, cloudProvider: CloudProvider, - airnodeAddressShort: string, - stage: string, + deploymentId: string, bucket: Bucket, bucketDeploymentPath: string ) { await terraformAirnodeInit(execOptions, cloudProvider, bucket, bucketDeploymentPath); const commonArguments = prepareAirnodeApplyDestroyArguments({ - airnodeAddressShort, - stage, + deploymentId, handlerDir, disableConcurrencyReservations: cloudProvider.disableConcurrencyReservations, }); @@ -560,17 +554,9 @@ export async function removeAirnode(deploymentId: string) { } logger.debug('Removing Airnode via Terraform recipes'); - const airnodeAddressShort = shortenAirnodeAddress(airnodeAddress); const airnodeTmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'airnode')); const execOptions = { cwd: airnodeTmpDir }; - await terraformAirnodeDestroy( - execOptions, - cloudProvider, - airnodeAddressShort, - stage, - bucket, - bucketLatestDeploymentPath - ); + await terraformAirnodeDestroy(execOptions, cloudProvider, deploymentId, bucket, bucketLatestDeploymentPath); fs.rmSync(airnodeTmpDir, { recursive: true }); // Refreshing the bucket content because the source code archives were removed by Terraform diff --git a/packages/airnode-deployer/src/utils/evm.ts b/packages/airnode-deployer/src/utils/evm.ts index 9cfba7b82f..0993280e5e 100644 --- a/packages/airnode-deployer/src/utils/evm.ts +++ b/packages/airnode-deployer/src/utils/evm.ts @@ -1,6 +1,4 @@ import * as ethers from 'ethers'; -import { goSync } from '@api3/promise-utils'; -import { logAndReturnError } from './infrastructure'; import * as logger from '../utils/logger'; export function deriveAirnodeAddress(mnemonic: string) { @@ -14,13 +12,3 @@ export function deriveAirnodeXpub(mnemonic: string) { const airnodeHdNode = ethers.utils.HDNode.fromMnemonic(mnemonic).derivePath("m/44'/60'/0'"); return airnodeHdNode.neuter().extendedKey; } - -export function shortenAirnodeAddress(airnodeAddress: string) { - logger.debug('Shortening Airnode Address'); - - const goGetAddress = goSync(() => ethers.utils.getAddress(airnodeAddress)); - if (!goGetAddress.success) throw logAndReturnError('"airnodeAddress" is not a valid hex string'); - - // NOTE: AWS doesn't allow uppercase letters in S3 bucket and lambda function names - return airnodeAddress.substring(2, 9).toLowerCase(); -} diff --git a/packages/airnode-deployer/terraform/aws/variables.tf b/packages/airnode-deployer/terraform/aws/variables.tf index cb877db775..d9a9b863de 100644 --- a/packages/airnode-deployer/terraform/aws/variables.tf +++ b/packages/airnode-deployer/terraform/aws/variables.tf @@ -1,12 +1,11 @@ locals { # Be aware when using `name-prefix` for naming resources - # as it can be up to 32 characters long: + # as it can be up to 19 characters long: # # infrastructure_name - "airnode" - 7 characters - # airnode_address_short - 7 characters - # stage - up to 16 characters - # dashes between - 2 characters - name_prefix = "${var.infrastructure_name}-${var.airnode_address_short}-${var.stage}" + # deployment_id - 11 characters + # dash between - 1 character + name_prefix = "${var.infrastructure_name}-${var.deployment_id}" } variable "aws_region" { @@ -14,18 +13,13 @@ variable "aws_region" { default = "us-east-1" } -variable "stage" { - description = "Infrastructure environment" - default = "testing" -} - variable "infrastructure_name" { description = "Infrastructure name" default = "airnode" } -variable "airnode_address_short" { - description = "Airnode address (short)" +variable "deployment_id" { + description = "ID of the deployment" } variable "configuration_file" { diff --git a/packages/airnode-deployer/terraform/gcp/variables.tf b/packages/airnode-deployer/terraform/gcp/variables.tf index 1a1a1bc1ae..48a5e7e068 100644 --- a/packages/airnode-deployer/terraform/gcp/variables.tf +++ b/packages/airnode-deployer/terraform/gcp/variables.tf @@ -1,12 +1,11 @@ locals { # Be aware when using `name-prefix` for naming resources - # as it can be up to 32 characters long: + # as it can be up to 19 characters long: # # infrastructure_name - "airnode" - 7 characters - # airnode_address_short - 7 characters - # stage - up to 16 characters - # dashes between - 2 characters - name_prefix = "${var.infrastructure_name}-${var.airnode_address_short}-${var.stage}" + # deployment_id - 11 characters + # dash between - 1 character + name_prefix = "${var.infrastructure_name}-${var.deployment_id}" } variable "gcp_project" { @@ -18,18 +17,13 @@ variable "gcp_region" { default = "us-east1" } -variable "stage" { - description = "Infrastructure environment" - default = "testing" -} - variable "infrastructure_name" { description = "Infrastructure name" default = "airnode" } -variable "airnode_address_short" { - description = "Airnode address (short)" +variable "deployment_id" { + description = "ID of the deployment" } variable "configuration_file" { diff --git a/packages/airnode-node/src/adapters/http/worker.test.ts b/packages/airnode-node/src/adapters/http/worker.test.ts index a421d3a228..1139423264 100644 --- a/packages/airnode-node/src/adapters/http/worker.test.ts +++ b/packages/airnode-node/src/adapters/http/worker.test.ts @@ -31,7 +31,7 @@ describe('spawnNewApiCall', () => { expect(invokeMock).toHaveBeenCalledTimes(1); expect(invokeMock).toHaveBeenCalledWith( { - FunctionName: 'airnode-19255a4-test-run', + FunctionName: 'airnode-local02cce763-run', Payload: JSON.stringify({ aggregatedApiCall, logOptions, functionName: 'callApi' }), }, expect.anything() @@ -52,7 +52,7 @@ describe('spawnNewApiCall', () => { expect(invokeMock).toHaveBeenCalledTimes(1); expect(invokeMock).toHaveBeenCalledWith( { - FunctionName: 'airnode-19255a4-test-run', + FunctionName: 'airnode-local02cce763-run', Payload: JSON.stringify({ aggregatedApiCall, logOptions, functionName: 'callApi' }), }, expect.anything() @@ -74,7 +74,7 @@ describe('spawnNewApiCall', () => { expect(invokeMock).toHaveBeenCalledTimes(1); expect(invokeMock).toHaveBeenCalledWith( { - FunctionName: 'airnode-19255a4-test-run', + FunctionName: 'airnode-local02cce763-run', Payload: JSON.stringify({ aggregatedApiCall, logOptions, functionName: 'callApi' }), }, expect.anything() @@ -95,7 +95,7 @@ describe('spawnNewApiCall', () => { expect(invokeMock).toHaveBeenCalledTimes(1); expect(invokeMock).toHaveBeenCalledWith( { - FunctionName: 'airnode-19255a4-test-run', + FunctionName: 'airnode-local02cce763-run', Payload: JSON.stringify({ aggregatedApiCall, logOptions, functionName: 'callApi' }), }, expect.anything() diff --git a/packages/airnode-node/src/coordinator/state.ts b/packages/airnode-node/src/coordinator/state.ts index 407b459a3d..2ed344849a 100644 --- a/packages/airnode-node/src/coordinator/state.ts +++ b/packages/airnode-node/src/coordinator/state.ts @@ -1,15 +1,17 @@ import * as wallet from '../evm/wallet'; import { CoordinatorSettings, CoordinatorState } from '../types'; +import { deriveDeploymentId } from '../workers'; import { Config } from '../config'; import { CoordinatorStateWithApiResponses, RegularAggregatedApiCallsWithResponseById } from '..'; export function create(config: Config, coordinatorId: string): CoordinatorState { const airnodeAddress = wallet.getAirnodeWalletFromPrivateKey().address; - const airnodeAddressShort = wallet.getAirnodeAddressShort(airnodeAddress); + const { cloudProvider, stage, nodeVersion } = config.nodeSettings; + const deploymentId = deriveDeploymentId(cloudProvider, airnodeAddress, stage, nodeVersion); const settings: CoordinatorSettings = { airnodeAddress, - airnodeAddressShort, + deploymentId, logFormat: config.nodeSettings.logFormat, logLevel: config.nodeSettings.logLevel, stage: config.nodeSettings.stage, diff --git a/packages/airnode-node/src/evm/wallet.test.ts b/packages/airnode-node/src/evm/wallet.test.ts index 34dd544118..886c7769df 100644 --- a/packages/airnode-node/src/evm/wallet.test.ts +++ b/packages/airnode-node/src/evm/wallet.test.ts @@ -21,14 +21,6 @@ describe('getAirnodeWallet', () => { }); }); -describe('getAirnodeAddressShort', () => { - it('returns a shortened airnodeAddress', () => { - const airnodeWallet = wallet.getAirnodeWallet(config); - const res = wallet.getAirnodeAddressShort(airnodeWallet.address); - expect(res).toEqual('a30ca71'); - }); -}); - describe('deriveSponsorWallet', () => { it('returns the wallet for the given sponsor', () => { const masterHDNode = wallet.getMasterHDNode(config); diff --git a/packages/airnode-node/src/evm/wallet.ts b/packages/airnode-node/src/evm/wallet.ts index c405e5b383..59ea3084e2 100644 --- a/packages/airnode-node/src/evm/wallet.ts +++ b/packages/airnode-node/src/evm/wallet.ts @@ -47,11 +47,6 @@ export function getExtendedPublicKey(masterHDNode: ethers.utils.HDNode): string return masterHDNode.derivePath("m/44'/60'/0'").neuter().extendedKey; } -export function getAirnodeAddressShort(airnodeAddress: string): string { - // NOTE: AWS doesn't allow uppercase letters in S3 bucket and lambda function names - return airnodeAddress.substring(2, 9).toLowerCase(); -} - export function deriveSponsorWallet( masterHDNode: ethers.utils.HDNode, sponsorAddress: string, diff --git a/packages/airnode-node/src/evm/workers.test.ts b/packages/airnode-node/src/evm/workers.test.ts index 11969425b3..7027648bd0 100644 --- a/packages/airnode-node/src/evm/workers.test.ts +++ b/packages/airnode-node/src/evm/workers.test.ts @@ -11,7 +11,7 @@ import * as fixtures from '../../test/fixtures'; const workers = ['spawnNewProvider', 'spawnProviderRequestProcessor'] as ReadonlyArray; -const serverlessFunctionName = 'airnode-19255a4-test-run'; +const serverlessFunctionName = 'airnode-local02cce763-run'; const functionNames = { spawnNewProvider: 'initializeProvider', spawnProviderRequestProcessor: 'processTransactions', @@ -27,7 +27,7 @@ workers.forEach((workerType) => { it('handles remote AWS calls', async () => { const state = fixtures.buildEVMProviderSponsorState(); - invokeMock.mockImplementationOnce((params, callback) => + invokeMock.mockImplementationOnce((_params, callback) => callback(null, { Payload: JSON.stringify({ body: JSON.stringify({ ok: true, data: state }) }) }) ); const workerOpts = fixtures.buildWorkerOptions({ @@ -48,7 +48,7 @@ workers.forEach((workerType) => { it('returns an error if the worker rejects', async () => { const state = fixtures.buildEVMProviderSponsorState(); - invokeMock.mockImplementationOnce((params, callback) => callback(new Error('Something went wrong'), null)); + invokeMock.mockImplementationOnce((_params, callback) => callback(new Error('Something went wrong'), null)); const workerOpts = fixtures.buildWorkerOptions({ cloudProvider: { type: 'aws', region: 'us-east-1', disableConcurrencyReservations: false }, }); @@ -74,7 +74,7 @@ workers.forEach((workerType) => { it('returns an error if the response has an error log', async () => { const state = fixtures.buildEVMProviderSponsorState(); const errorLog = logger.pend('ERROR', 'Something went wrong'); - invokeMock.mockImplementationOnce((params, callback) => + invokeMock.mockImplementationOnce((_params, callback) => callback(null, { Payload: JSON.stringify({ body: JSON.stringify({ ok: false, errorLog }) }) }) ); const workerOpts = fixtures.buildWorkerOptions({ @@ -95,7 +95,7 @@ workers.forEach((workerType) => { it('returns an error if the response is not ok', async () => { const state = fixtures.buildEVMProviderSponsorState(); - invokeMock.mockImplementationOnce((params, callback) => + invokeMock.mockImplementationOnce((_params, callback) => callback(null, { Payload: JSON.stringify({ body: JSON.stringify({ ok: false }) }) }) ); const workerOpts = fixtures.buildWorkerOptions({ diff --git a/packages/airnode-node/src/handlers/start-coordinator.test.ts b/packages/airnode-node/src/handlers/start-coordinator.test.ts index 9cd77876e0..adfba1c179 100644 --- a/packages/airnode-node/src/handlers/start-coordinator.test.ts +++ b/packages/airnode-node/src/handlers/start-coordinator.test.ts @@ -170,7 +170,7 @@ describe('startCoordinator', () => { await startCoordinator(config, coordinatorId); // cached requests should not trigger an API call - expect(callApisSpy).toHaveBeenCalledWith([], expect.objectContaining({ stage: 'test' })); + expect(callApisSpy).toHaveBeenCalledWith([], expect.objectContaining({ deploymentId: 'locale78fb65c' })); expect(blockWithTransactionsSpy).toHaveBeenCalled(); // API call was submitted diff --git a/packages/airnode-node/src/handlers/start-coordinator.ts b/packages/airnode-node/src/handlers/start-coordinator.ts index 2012d7bc12..fa77461973 100644 --- a/packages/airnode-node/src/handlers/start-coordinator.ts +++ b/packages/airnode-node/src/handlers/start-coordinator.ts @@ -45,8 +45,7 @@ function getWorkerOptions(state: CoordinatorState): WorkerOptions { return { cloudProvider: config.nodeSettings.cloudProvider, - airnodeAddressShort: settings.airnodeAddressShort, - stage: config.nodeSettings.stage, + deploymentId: settings.deploymentId, }; } diff --git a/packages/airnode-node/src/providers/actions.test.ts b/packages/airnode-node/src/providers/actions.test.ts index aa51ce61aa..d656e5a2ca 100644 --- a/packages/airnode-node/src/providers/actions.test.ts +++ b/packages/airnode-node/src/providers/actions.test.ts @@ -124,7 +124,7 @@ describe('initialize', () => { }, settings: { airnodeAddress: '0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace', - airnodeAddressShort: 'a30ca71', + deploymentId: 'locale78fb65c', authorizers: { requesterEndpointAuthorizers: [ethers.constants.AddressZero], crossChainRequesterAuthorizers: [], @@ -178,7 +178,7 @@ describe('initialize', () => { }, settings: { airnodeAddress: '0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace', - airnodeAddressShort: 'a30ca71', + deploymentId: 'locale78fb65c', authorizers: { requesterEndpointAuthorizers: [ethers.constants.AddressZero], crossChainRequesterAuthorizers: [], diff --git a/packages/airnode-node/src/providers/state.test.ts b/packages/airnode-node/src/providers/state.test.ts index 0e9408ceba..ed0fd8f971 100644 --- a/packages/airnode-node/src/providers/state.test.ts +++ b/packages/airnode-node/src/providers/state.test.ts @@ -53,7 +53,7 @@ describe('create', () => { }, settings: { airnodeAddress: '0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace', - airnodeAddressShort: 'a30ca71', + deploymentId: 'locale78fb65c', authorizers: { requesterEndpointAuthorizers: [ethers.constants.AddressZero], crossChainRequesterAuthorizers: [], @@ -148,7 +148,7 @@ describe('create', () => { }, settings: { airnodeAddress: '0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace', - airnodeAddressShort: 'a30ca71', + deploymentId: 'locale78fb65c', authorizers: { requesterEndpointAuthorizers: [ethers.constants.AddressZero], crossChainRequesterAuthorizers: [], diff --git a/packages/airnode-node/src/providers/state.ts b/packages/airnode-node/src/providers/state.ts index a06a39ca9b..a164c75b7e 100644 --- a/packages/airnode-node/src/providers/state.ts +++ b/packages/airnode-node/src/providers/state.ts @@ -6,6 +6,7 @@ import { EVMProviderState, ProviderSettings, ProviderState, ProviderStates, EVMP import { BLOCK_COUNT_HISTORY_LIMIT, BLOCK_MIN_CONFIRMATIONS } from '../constants'; import { groupRequestsBySponsorAddress } from '../requests/grouping'; import { ChainConfig, ChainType, Config } from '../config'; +import { deriveDeploymentId } from '../workers'; export function buildEVMState( coordinatorId: string, @@ -17,10 +18,11 @@ export function buildEVMState( const masterHDNode = evm.getMasterHDNode(config); const chainProviderUrl = chain.providers[chainProviderName].url; const provider = evm.buildEVMProvider(chainProviderUrl, chain.id); + const { cloudProvider, stage, nodeVersion } = config.nodeSettings; const providerSettings: ProviderSettings = { airnodeAddress, - airnodeAddressShort: evm.getAirnodeAddressShort(airnodeAddress), + deploymentId: deriveDeploymentId(cloudProvider, airnodeAddress, stage, nodeVersion), authorizers: chain.authorizers, authorizations: chain.authorizations, // The number of blocks to look back for events to process diff --git a/packages/airnode-node/src/providers/worker.test.ts b/packages/airnode-node/src/providers/worker.test.ts index d0fdab061b..9bbe316da2 100644 --- a/packages/airnode-node/src/providers/worker.test.ts +++ b/packages/airnode-node/src/providers/worker.test.ts @@ -26,8 +26,7 @@ describe('spawnNewProvider', () => { disableConcurrencyReservations: false, }, payload: { state, functionName: 'initializeProvider' }, - airnodeAddressShort: '19255a4', - stage: 'test', + deploymentId: 'local02cce763', }); }); }); @@ -52,8 +51,7 @@ describe('spawnTransactionsProcessor', () => { disableConcurrencyReservations: false, }, payload: { state, functionName: 'processTransactions' }, - airnodeAddressShort: '19255a4', - stage: 'test', + deploymentId: 'local02cce763', }); }); }); diff --git a/packages/airnode-node/src/types.ts b/packages/airnode-node/src/types.ts index 30affde38e..8cafb116e3 100644 --- a/packages/airnode-node/src/types.ts +++ b/packages/airnode-node/src/types.ts @@ -164,7 +164,7 @@ export interface RegularAggregatedApiCallsWithResponseById { export interface CoordinatorSettings { readonly airnodeAddress: string; - readonly airnodeAddressShort: string; + readonly deploymentId: string; readonly logFormat: LogFormat; readonly logLevel: LogLevel; readonly stage: string; @@ -319,8 +319,7 @@ export type ApiCallPayload = RegularApiCallPayload | HttpApiCallPayload | HttpSi // =========================================== export interface WorkerOptions { readonly cloudProvider: LocalOrCloudProvider; - readonly airnodeAddressShort: string; - readonly stage: string; + readonly deploymentId: string; } export interface InitializeProviderPayload { diff --git a/packages/airnode-node/src/workers/cloud-platforms/aws.test.ts b/packages/airnode-node/src/workers/cloud-platforms/aws.test.ts index 6030eacf6c..caddcad7cf 100644 --- a/packages/airnode-node/src/workers/cloud-platforms/aws.test.ts +++ b/packages/airnode-node/src/workers/cloud-platforms/aws.test.ts @@ -16,7 +16,7 @@ describe('spawn', () => { it('derives the function name, invokes and returns the response', async () => { const lambda = new AWS.Lambda(); const invoke = lambda.invoke as jest.Mock; - invoke.mockImplementationOnce((params, callback) => + invoke.mockImplementationOnce((_params, callback) => callback(null, { Payload: JSON.stringify({ body: JSON.stringify({ value: 7777 }) }) }) ); const state = fixtures.buildEVMProviderState(); @@ -33,7 +33,7 @@ describe('spawn', () => { expect(invoke).toHaveBeenCalledTimes(1); expect(invoke).toHaveBeenCalledWith( { - FunctionName: 'airnode-19255a4-test-run', + FunctionName: 'airnode-local02cce763-run', Payload: JSON.stringify({ functionName: 'initializeProvider', state, logOptions }), }, expect.any(Function) @@ -43,7 +43,7 @@ describe('spawn', () => { it('throws an error if the lambda returns an error', async () => { const lambda = new AWS.Lambda(); const invoke = lambda.invoke as jest.Mock; - invoke.mockImplementationOnce((params, callback) => callback(new Error('Something went wrong'), null)); + invoke.mockImplementationOnce((_params, callback) => callback(new Error('Something went wrong'), null)); const state = fixtures.buildEVMProviderState(); const workerOpts = fixtures.buildWorkerOptions({ cloudProvider: { type: 'aws', region: 'us-east-1', disableConcurrencyReservations: false }, @@ -57,7 +57,7 @@ describe('spawn', () => { expect(invoke).toHaveBeenCalledTimes(1); expect(invoke).toHaveBeenCalledWith( { - FunctionName: 'airnode-19255a4-test-run', + FunctionName: 'airnode-local02cce763-run', Payload: JSON.stringify({ functionName: 'initializeProvider', state, logOptions }), }, expect.any(Function) diff --git a/packages/airnode-node/src/workers/cloud-platforms/aws.ts b/packages/airnode-node/src/workers/cloud-platforms/aws.ts index 1b81e5eb19..00d7150338 100644 --- a/packages/airnode-node/src/workers/cloud-platforms/aws.ts +++ b/packages/airnode-node/src/workers/cloud-platforms/aws.ts @@ -10,7 +10,7 @@ export function spawn(params: WorkerParameters): Promise { const lambda = new AWS.Lambda(); // AWS doesn't allow uppercase letters in lambda function names - const resolvedName = `airnode-${params.airnodeAddressShort}-${params.stage}-run`; + const resolvedName = `airnode-${params.deploymentId}-run`; const options = { FunctionName: resolvedName, diff --git a/packages/airnode-node/src/workers/cloud-platforms/gcp.test.ts b/packages/airnode-node/src/workers/cloud-platforms/gcp.test.ts index 6f67d88830..b027103329 100644 --- a/packages/airnode-node/src/workers/cloud-platforms/gcp.test.ts +++ b/packages/airnode-node/src/workers/cloud-platforms/gcp.test.ts @@ -31,7 +31,7 @@ describe('spawn', () => { ...workerOpts, payload: { functionName: 'initializeProvider', state, logOptions }, }; - const url = 'https://us-east1-projectId123.cloudfunctions.net/airnode-19255a4-test-run'; + const url = 'https://us-east1-projectId123.cloudfunctions.net/airnode-local02cce763-run'; const res = await gcp.spawn(parameters); expect(res).toEqual({ value: 7777 }); @@ -63,7 +63,7 @@ describe('spawn', () => { ...workerOpts, payload: { functionName: 'initializeProvider', state, logOptions }, }; - const url = 'https://us-east1-projectId123.cloudfunctions.net/airnode-19255a4-test-run'; + const url = 'https://us-east1-projectId123.cloudfunctions.net/airnode-local02cce763-run'; await expect(gcp.spawn(parameters)).rejects.toThrow(new Error('Something went wrong')); diff --git a/packages/airnode-node/src/workers/cloud-platforms/gcp.ts b/packages/airnode-node/src/workers/cloud-platforms/gcp.ts index 96dee094c9..40e5803e44 100644 --- a/packages/airnode-node/src/workers/cloud-platforms/gcp.ts +++ b/packages/airnode-node/src/workers/cloud-platforms/gcp.ts @@ -5,7 +5,7 @@ import { WorkerParameters, WorkerResponse } from '../../types'; export async function spawn(params: WorkerParameters): Promise { const auth = new GoogleAuth(); - const resolvedName = `airnode-${params.airnodeAddressShort}-${params.stage}-run`; + const resolvedName = `airnode-${params.deploymentId}-run`; const cloudProvider = params.cloudProvider as GcpCloudProvider; const url = `https://${cloudProvider.region}-${cloudProvider.projectId}.cloudfunctions.net/${resolvedName}`; diff --git a/packages/airnode-node/src/workers/index.ts b/packages/airnode-node/src/workers/index.ts index 20a4d379cd..ba2b26f2f2 100644 --- a/packages/airnode-node/src/workers/index.ts +++ b/packages/airnode-node/src/workers/index.ts @@ -3,7 +3,7 @@ import * as aws from './cloud-platforms/aws'; import * as gcp from './cloud-platforms/gcp'; import * as localHandlers from './local-handlers'; import { WorkerParameters, WorkerResponse } from '../types'; -import { CloudProvider } from '../config'; +import { LocalOrCloudProvider } from '../config'; const DEPLOYMENT_ID_LENGTH = 8; @@ -27,8 +27,11 @@ export function spawn(params: WorkerParameters): Promise { } } -function cloudProviderHashElements(cloudProvider: CloudProvider) { - const hashElements = [cloudProvider.type, cloudProvider.region]; +function cloudProviderHashElements(cloudProvider: LocalOrCloudProvider) { + const hashElements: string[] = [cloudProvider.type]; + if (cloudProvider.type !== 'local') { + hashElements.push(cloudProvider.region); + } if (cloudProvider.type === 'gcp') { hashElements.push(cloudProvider.projectId); } @@ -37,7 +40,7 @@ function cloudProviderHashElements(cloudProvider: CloudProvider) { } export function deriveDeploymentId( - cloudProvider: CloudProvider, + cloudProvider: LocalOrCloudProvider, airnodeAddress: string, stage: string, airnodeVersion: string @@ -50,7 +53,7 @@ export function deriveDeploymentId( } export function deriveDeploymentVersionId( - cloudProvider: CloudProvider, + cloudProvider: LocalOrCloudProvider, airnodeAddress: string, stage: string, airnodeVersion: string, diff --git a/packages/airnode-node/test/fixtures/config/worker-options.ts b/packages/airnode-node/test/fixtures/config/worker-options.ts index dbad504d15..7200abdbd6 100644 --- a/packages/airnode-node/test/fixtures/config/worker-options.ts +++ b/packages/airnode-node/test/fixtures/config/worker-options.ts @@ -6,8 +6,7 @@ export function buildWorkerOptions(options?: Partial): WorkerOpti type: 'local', gatewayServerPort: 3000, }, - airnodeAddressShort: '19255a4', - stage: 'test', + deploymentId: 'local02cce763', ...options, }; } From 6951537f1fa91500bd56bdd213c9fd147727ad72 Mon Sep 17 00:00:00 2001 From: Michal Kimle Date: Thu, 8 Dec 2022 13:01:53 +0100 Subject: [PATCH 4/4] Write deployment tests in more maintainable way --- .../src/infrastructure/index.test.ts | 310 ++++++++++++++++-- 1 file changed, 287 insertions(+), 23 deletions(-) diff --git a/packages/airnode-deployer/src/infrastructure/index.test.ts b/packages/airnode-deployer/src/infrastructure/index.test.ts index 415ee42c52..626c5e4ebf 100644 --- a/packages/airnode-deployer/src/infrastructure/index.test.ts +++ b/packages/airnode-deployer/src/infrastructure/index.test.ts @@ -71,7 +71,15 @@ describe('execTerraform', () => { await infrastructure.execTerraform(execOptions, command, args, options); expect(exec).toHaveBeenCalledWith( - `terraform apply -var="name=value" -switch=true -flag additional_option1 additional_option2`, + [ + `terraform`, + `apply`, + `-var="name=value"`, + `-switch=true`, + `-flag`, + `additional_option1`, + `additional_option2`, + ].join(' '), execOptions ); }); @@ -305,7 +313,14 @@ describe('terraformAirnodeInit', () => { await infrastructure.terraformAirnodeInit(execOptions, cloudProvider, bucket, bucketPath); expect(exec).toHaveBeenCalledWith( - `terraform init -backend-config="region=us-east-1" -backend-config="bucket=airnode-123456789" -backend-config="key=airnode-address/stage/timestamp/default.tfstate" -from-module=${terraformDir}/aws`, + [ + `terraform`, + `init`, + `-backend-config="region=us-east-1"`, + `-backend-config="bucket=airnode-123456789"`, + `-backend-config="key=airnode-address/stage/timestamp/default.tfstate"`, + `-from-module=${terraformDir}/aws`, + ].join(' '), execOptions ); }); @@ -344,12 +359,37 @@ describe('terraformAirnodeApply', () => { expect(exec).toHaveBeenNthCalledWith( 1, - `terraform init -backend-config="region=us-east-1" -backend-config="bucket=airnode-123456789" -backend-config="key=airnode-address/stage/timestamp/default.tfstate" -from-module=${terraformDir}/aws`, + [ + `terraform`, + `init`, + `-backend-config="region=us-east-1"`, + `-backend-config="bucket=airnode-123456789"`, + `-backend-config="key=airnode-address/stage/timestamp/default.tfstate"`, + `-from-module=${terraformDir}/aws`, + ].join(' '), execOptions ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform apply -var="aws_region=us-east-1" -var="deployment_id=aws40207f25" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -var="http_gateway_enabled=true" -var="http_max_concurrency=20" -var="http_signed_data_gateway_enabled=true" -var="http_signed_data_max_concurrency=20" -auto-approve`, + [ + `terraform`, + `apply`, + `-var="aws_region=us-east-1"`, + `-var="deployment_id=aws40207f25"`, + `-var="configuration_file=${configPath}"`, + `-var="secrets_file=${secretsPath}"`, + `-var="handler_dir=${handlerDir}"`, + `-var="disable_concurrency_reservation=false"`, + `-var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca"`, + `-input=false`, + `-no-color`, + `-var="max_concurrency=100"`, + `-var="http_gateway_enabled=true"`, + `-var="http_max_concurrency=20"`, + `-var="http_signed_data_gateway_enabled=true"`, + `-var="http_signed_data_max_concurrency=20"`, + `-auto-approve`, + ].join(' '), execOptions ); }); @@ -370,17 +410,65 @@ describe('terraformAirnodeApply', () => { await infrastructure.terraformAirnodeApply(execOptions, gcpConfig, bucket, bucketPath, configPath, secretsPath); expect(exec).toHaveBeenNthCalledWith( 1, - `terraform init -backend-config="bucket=airnode-123456789" -backend-config="prefix=airnode-address/stage/timestamp" -from-module=${terraformDir}/gcp`, + [ + `terraform`, + `init`, + `-backend-config="bucket=airnode-123456789"`, + `-backend-config="prefix=airnode-address/stage/timestamp"`, + `-from-module=${terraformDir}/gcp`, + ].join(' '), execOptions ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform import -var="gcp_region=us-east1" -var="gcp_project=airnode-test-123456" -var="airnode_bucket=airnode-123456789" -var="deployment_bucket_dir=airnode-address/stage/timestamp" -var="deployment_id=gcp1fc73e56" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -var="http_gateway_enabled=true" -var="http_max_concurrency=20" -var="http_signed_data_gateway_enabled=true" -var="http_signed_data_max_concurrency=20" module.startCoordinator.google_app_engine_application.app[0] airnode-test-123456`, + [ + `terraform`, + `import`, + `-var="gcp_region=us-east1"`, + `-var="gcp_project=airnode-test-123456"`, + `-var="airnode_bucket=airnode-123456789"`, + `-var="deployment_bucket_dir=airnode-address/stage/timestamp"`, + `-var="deployment_id=gcp1fc73e56"`, + `-var="configuration_file=${configPath}"`, + `-var="secrets_file=${secretsPath}"`, + `-var="handler_dir=${handlerDir}"`, + `-var="disable_concurrency_reservation=false"`, + `-var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca"`, + `-input=false`, + `-no-color`, + `-var="max_concurrency=100"`, + `-var="http_gateway_enabled=true"`, + `-var="http_max_concurrency=20"`, + `-var="http_signed_data_gateway_enabled=true"`, + `-var="http_signed_data_max_concurrency=20"`, + `module.startCoordinator.google_app_engine_application.app[0] airnode-test-123456`, + ].join(' '), { ignoreError: true } ); expect(exec).toHaveBeenNthCalledWith( 3, - `terraform apply -var="gcp_region=us-east1" -var="gcp_project=airnode-test-123456" -var="airnode_bucket=airnode-123456789" -var="deployment_bucket_dir=airnode-address/stage/timestamp" -var="deployment_id=gcp1fc73e56" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -var="http_gateway_enabled=true" -var="http_max_concurrency=20" -var="http_signed_data_gateway_enabled=true" -var="http_signed_data_max_concurrency=20" -auto-approve`, + [ + `terraform`, + `apply`, + `-var="gcp_region=us-east1"`, + `-var="gcp_project=airnode-test-123456"`, + `-var="airnode_bucket=airnode-123456789"`, + `-var="deployment_bucket_dir=airnode-address/stage/timestamp"`, + `-var="deployment_id=gcp1fc73e56"`, + `-var="configuration_file=${configPath}"`, + `-var="secrets_file=${secretsPath}"`, + `-var="handler_dir=${handlerDir}"`, + `-var="disable_concurrency_reservation=false"`, + `-var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca"`, + `-input=false`, + `-no-color`, + `-var="max_concurrency=100"`, + `-var="http_gateway_enabled=true"`, + `-var="http_max_concurrency=20"`, + `-var="http_signed_data_gateway_enabled=true"`, + `-var="http_signed_data_max_concurrency=20"`, + '-auto-approve', + ].join(' '), execOptions ); }); @@ -409,12 +497,33 @@ describe('terraformAirnodeApply', () => { ); expect(exec).toHaveBeenNthCalledWith( 1, - `terraform init -backend-config="region=us-east-1" -backend-config="bucket=airnode-123456789" -backend-config="key=airnode-address/stage/timestamp/default.tfstate" -from-module=${terraformDir}/aws`, + [ + `terraform`, + `init`, + `-backend-config="region=us-east-1"`, + `-backend-config="bucket=airnode-123456789"`, + `-backend-config="key=airnode-address/stage/timestamp/default.tfstate"`, + `-from-module=${terraformDir}/aws`, + ].join(' '), execOptions ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform apply -var="aws_region=us-east-1" -var="deployment_id=aws40207f25" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -auto-approve`, + [ + `terraform`, + `apply`, + `-var="aws_region=us-east-1"`, + `-var="deployment_id=aws40207f25"`, + `-var="configuration_file=${configPath}"`, + `-var="secrets_file=${secretsPath}"`, + `-var="handler_dir=${handlerDir}"`, + `-var="disable_concurrency_reservation=false"`, + `-var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca"`, + `-input=false`, + `-no-color`, + `-var="max_concurrency=100"`, + `-auto-approve`, + ].join(' '), execOptions ); }); @@ -477,12 +586,37 @@ describe('deployAirnode', () => { ); expect(exec).toHaveBeenNthCalledWith( 1, - `terraform init -backend-config="region=europe-central-1" -backend-config="bucket=airnode-123456789" -backend-config="key=0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace/dev/1662730904/default.tfstate" -from-module=${terraformDir}/aws`, + [ + `terraform`, + `init`, + `-backend-config="region=europe-central-1"`, + `-backend-config="bucket=airnode-123456789"`, + `-backend-config="key=0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace/dev/1662730904/default.tfstate"`, + `-from-module=${terraformDir}/aws`, + ].join(' '), { cwd: 'tmpDir' } ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform apply -var="aws_region=us-east-1" -var="deployment_id=aws40207f25" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -var="http_gateway_enabled=true" -var="http_max_concurrency=20" -var="http_signed_data_gateway_enabled=true" -var="http_signed_data_max_concurrency=20" -auto-approve`, + [ + `terraform`, + `apply`, + `-var="aws_region=us-east-1"`, + `-var="deployment_id=aws40207f25"`, + `-var="configuration_file=${configPath}"`, + `-var="secrets_file=${secretsPath}"`, + `-var="handler_dir=${handlerDir}"`, + `-var="disable_concurrency_reservation=false"`, + `-var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca"`, + `-input=false`, + `-no-color`, + `-var="max_concurrency=100"`, + `-var="http_gateway_enabled=true"`, + `-var="http_max_concurrency=20"`, + `-var="http_signed_data_gateway_enabled=true"`, + `-var="http_signed_data_max_concurrency=20"`, + `-auto-approve`, + ].join(' '), { cwd: 'tmpDir' } ); expect(exec).toHaveBeenNthCalledWith(3, 'terraform output -json -no-color', { cwd: 'tmpDir' }); @@ -517,12 +651,37 @@ describe('deployAirnode', () => { ); expect(exec).toHaveBeenNthCalledWith( 1, - `terraform init -backend-config="region=europe-central-1" -backend-config="bucket=airnode-987654321" -backend-config="key=0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace/dev/1662730904/default.tfstate" -from-module=${terraformDir}/aws`, + [ + `terraform`, + `init`, + `-backend-config="region=europe-central-1"`, + `-backend-config="bucket=airnode-987654321"`, + `-backend-config="key=0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace/dev/1662730904/default.tfstate"`, + `-from-module=${terraformDir}/aws`, + ].join(' '), { cwd: 'tmpDir' } ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform apply -var="aws_region=us-east-1" -var="deployment_id=aws40207f25" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -var="http_gateway_enabled=true" -var="http_max_concurrency=20" -var="http_signed_data_gateway_enabled=true" -var="http_signed_data_max_concurrency=20" -auto-approve`, + [ + `terraform`, + `apply`, + `-var="aws_region=us-east-1"`, + `-var="deployment_id=aws40207f25"`, + `-var="configuration_file=${configPath}"`, + `-var="secrets_file=${secretsPath}"`, + `-var="handler_dir=${handlerDir}"`, + `-var="disable_concurrency_reservation=false"`, + `-var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca"`, + `-input=false`, + `-no-color`, + `-var="max_concurrency=100"`, + `-var="http_gateway_enabled=true"`, + `-var="http_max_concurrency=20"`, + `-var="http_signed_data_gateway_enabled=true"`, + `-var="http_signed_data_max_concurrency=20"`, + `-auto-approve`, + ].join(' '), { cwd: 'tmpDir' } ); expect(exec).toHaveBeenNthCalledWith(3, 'terraform output -json -no-color', { cwd: 'tmpDir' }); @@ -560,12 +719,37 @@ describe('deployAirnode', () => { ); expect(exec).toHaveBeenNthCalledWith( 1, - `terraform init -backend-config="region=europe-central-1" -backend-config="bucket=airnode-123456789" -backend-config="key=0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace/dev/1662730904/default.tfstate" -from-module=${terraformDir}/aws`, + [ + `terraform`, + `init`, + `-backend-config="region=europe-central-1"`, + `-backend-config="bucket=airnode-123456789"`, + `-backend-config="key=0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace/dev/1662730904/default.tfstate"`, + `-from-module=${terraformDir}/aws`, + ].join(' '), { cwd: 'tmpDir' } ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform apply -var="aws_region=us-east-1" -var="deployment_id=aws40207f25" -var="configuration_file=${configPath}" -var="secrets_file=${secretsPath}" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca" -input=false -no-color -var="max_concurrency=100" -var="http_gateway_enabled=true" -var="http_max_concurrency=20" -var="http_signed_data_gateway_enabled=true" -var="http_signed_data_max_concurrency=20" -auto-approve`, + [ + `terraform`, + `apply`, + `-var="aws_region=us-east-1"`, + `-var="deployment_id=aws40207f25"`, + `-var="configuration_file=${configPath}"`, + `-var="secrets_file=${secretsPath}"`, + `-var="handler_dir=${handlerDir}"`, + `-var="disable_concurrency_reservation=false"`, + `-var="airnode_wallet_private_key=0xd627c727db73ed7067cbc1e15295f7004b83c01d243aa90711d549cda6bd5bca"`, + `-input=false`, + `-no-color`, + `-var="max_concurrency=100"`, + `-var="http_gateway_enabled=true"`, + `-var="http_max_concurrency=20"`, + `-var="http_signed_data_gateway_enabled=true"`, + `-var="http_signed_data_max_concurrency=20"`, + `-auto-approve`, + ].join(' '), { cwd: 'tmpDir' } ); expect(exec).toHaveBeenNthCalledWith(3, 'terraform output -json -no-color', { cwd: 'tmpDir' }); @@ -647,12 +831,32 @@ describe('terraformAirnodeDestroy', () => { await infrastructure.terraformAirnodeDestroy(execOptions, cloudProvider, deploymentId, bucket, bucketPath); expect(exec).toHaveBeenNthCalledWith( 1, - `terraform init -backend-config="region=us-east-1" -backend-config="bucket=airnode-123456789" -backend-config="key=airnode-address/stage/timestamp/default.tfstate" -from-module=${terraformDir}/aws`, + [ + `terraform`, + `init`, + `-backend-config="region=us-east-1"`, + `-backend-config="bucket=airnode-123456789"`, + `-backend-config="key=airnode-address/stage/timestamp/default.tfstate"`, + `-from-module=${terraformDir}/aws`, + ].join(' '), execOptions ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform destroy -var="aws_region=europe-central-1" -var="deployment_id=aws7195b548" -var="configuration_file=NULL" -var="secrets_file=NULL" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=NULL" -input=false -no-color -auto-approve`, + [ + `terraform`, + `destroy`, + `-var="aws_region=europe-central-1"`, + `-var="deployment_id=aws7195b548"`, + `-var="configuration_file=NULL"`, + `-var="secrets_file=NULL"`, + `-var="handler_dir=${handlerDir}"`, + `-var="disable_concurrency_reservation=false"`, + `-var="airnode_wallet_private_key=NULL"`, + `-input=false`, + `-no-color`, + `-auto-approve`, + ].join(' '), execOptions ); }); @@ -716,12 +920,32 @@ describe('removeAirnode', () => { ); expect(exec).toHaveBeenNthCalledWith( 1, - `terraform init -backend-config="region=us-east-1" -backend-config="bucket=airnode-123456789" -backend-config="key=0xd0624E6C2C8A1DaEdE9Fa7E9C409167ed5F256c6/dev/1662558010204/default.tfstate" -from-module=${terraformDir}/aws`, + [ + `terraform`, + `init`, + `-backend-config="region=us-east-1"`, + `-backend-config="bucket=airnode-123456789"`, + `-backend-config="key=0xd0624E6C2C8A1DaEdE9Fa7E9C409167ed5F256c6/dev/1662558010204/default.tfstate"`, + `-from-module=${terraformDir}/aws`, + ].join(' '), { cwd: 'tmpDir' } ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform destroy -var="aws_region=us-east-1" -var="deployment_id=${happyPathDeploymentId}" -var="configuration_file=NULL" -var="secrets_file=NULL" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=NULL" -input=false -no-color -auto-approve`, + [ + `terraform`, + `destroy`, + `-var="aws_region=us-east-1"`, + `-var="deployment_id=${happyPathDeploymentId}"`, + `-var="configuration_file=NULL"`, + `-var="secrets_file=NULL"`, + `-var="handler_dir=${handlerDir}"`, + `-var="disable_concurrency_reservation=false"`, + `-var="airnode_wallet_private_key=NULL"`, + `-input=false`, + `-no-color`, + `-auto-approve`, + ].join(' '), { cwd: 'tmpDir' } ); expect(awsGetBucketDirectoryStructureSpy).toHaveBeenNthCalledWith(2, bucket.name); @@ -747,12 +971,32 @@ describe('removeAirnode', () => { ); expect(exec).toHaveBeenNthCalledWith( 1, - `terraform init -backend-config="region=us-east-1" -backend-config="bucket=airnode-123456789" -backend-config="key=0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace/dev/1662559204554/default.tfstate" -from-module=${terraformDir}/aws`, + [ + `terraform`, + `init`, + `-backend-config="region=us-east-1"`, + `-backend-config="bucket=airnode-123456789"`, + `-backend-config="key=0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace/dev/1662559204554/default.tfstate"`, + `-from-module=${terraformDir}/aws`, + ].join(' '), { cwd: 'tmpDir' } ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform destroy -var="aws_region=us-east-1" -var="deployment_id=${deploymentId}" -var="configuration_file=NULL" -var="secrets_file=NULL" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=NULL" -input=false -no-color -auto-approve`, + [ + `terraform`, + `destroy`, + `-var="aws_region=us-east-1"`, + `-var="deployment_id=${deploymentId}"`, + `-var="configuration_file=NULL"`, + `-var="secrets_file=NULL"`, + `-var="handler_dir=${handlerDir}"`, + `-var="disable_concurrency_reservation=false"`, + `-var="airnode_wallet_private_key=NULL"`, + `-input=false`, + `-no-color`, + `-auto-approve`, + ].join(' '), { cwd: 'tmpDir' } ); expect(awsGetBucketDirectoryStructureSpy).toHaveBeenNthCalledWith(2, bucket.name); @@ -787,12 +1031,32 @@ describe('removeAirnode', () => { ); expect(exec).toHaveBeenNthCalledWith( 1, - `terraform init -backend-config="region=us-east-1" -backend-config="bucket=airnode-123456789" -backend-config="key=0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace/dev/1662559204554/default.tfstate" -from-module=${terraformDir}/aws`, + [ + `terraform`, + `init`, + `-backend-config="region=us-east-1"`, + `-backend-config="bucket=airnode-123456789"`, + `-backend-config="key=0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace/dev/1662559204554/default.tfstate"`, + `-from-module=${terraformDir}/aws`, + ].join(' '), { cwd: 'tmpDir' } ); expect(exec).toHaveBeenNthCalledWith( 2, - `terraform destroy -var="aws_region=us-east-1" -var="deployment_id=${deploymentId}" -var="configuration_file=NULL" -var="secrets_file=NULL" -var="handler_dir=${handlerDir}" -var="disable_concurrency_reservation=false" -var="airnode_wallet_private_key=NULL" -input=false -no-color -auto-approve`, + [ + `terraform`, + `destroy`, + `-var="aws_region=us-east-1"`, + `-var="deployment_id=${deploymentId}"`, + `-var="configuration_file=NULL"`, + `-var="secrets_file=NULL"`, + `-var="handler_dir=${handlerDir}"`, + `-var="disable_concurrency_reservation=false"`, + `-var="airnode_wallet_private_key=NULL"`, + `-input=false`, + `-no-color`, + `-auto-approve`, + ].join(' '), { cwd: 'tmpDir' } ); expect(awsGetBucketDirectoryStructureSpy).toHaveBeenNthCalledWith(2, bucket.name);