diff --git a/CHANGELOG.md b/CHANGELOG.md index 8234aac..8ffa52f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed +- `Invoker` resolves service account ids from Parameter Store (`accountsIdsByService`) instead of Secrets Manager, with a temporary fallback to the secret when `DEVOPS_ACCOUNT_ID` is absent + +### Added +- `ssm:GetParameter` permission to `invokePermissions` for reading the shared accounts-ids parameter ## [6.3.1] - 2026-06-19 ### Fixed diff --git a/lib/helpers/accounts-ids-provider.js b/lib/helpers/accounts-ids-provider.js new file mode 100644 index 0000000..3121ede --- /dev/null +++ b/lib/helpers/accounts-ids-provider.js @@ -0,0 +1,87 @@ +'use strict'; + +const { SSMClient, GetParameterCommand } = require('@aws-sdk/client-ssm'); +const { AwsSecretsManager } = require('@janiscommerce/aws-secrets-manager'); + +const logger = require('lllog')(); + +const LambdaError = require('../lambda-error'); + +const isLocalEnv = require('./is-local-env'); + +const PARAMETER_NAME = 'accountsIdsByService'; + +module.exports = class AccountsIdsProvider { + + /** + * Build the Parameter Store ARN for the shared Devops parameter + * @private + * @returns {string} + */ + static get parameterArn() { + return `arn:aws:ssm:${process.env.AWS_REGION}:${process.env.DEVOPS_ACCOUNT_ID}:parameter/${PARAMETER_NAME}`; + } + + /** + * Fetch accounts-ids-by-service from Parameter Store. + * Falls back to Secrets Manager (with a warning) only when DEVOPS_ACCOUNT_ID is absent. + * @throws {LambdaError} If the parameter/secret value is missing or the SSM call fails + */ + static async fetch() { + + if(this.accountsIds) + return; + + if(isLocalEnv()) { + this.accountsIds = {}; + return; + } + + if(!process.env.DEVOPS_ACCOUNT_ID) { + logger.warn('DEVOPS_ACCOUNT_ID env var is not set, falling back to Secrets Manager', { + hint: 'Update the service plugin to get the env var injected' + }); + await this.fetchFromSecret(); + return; + } + + await this.fetchFromParameterStore(); + } + + /** + * @private + */ + static async fetchFromParameterStore() { + + const client = new SSMClient(); + + try { + const { Parameter } = await client.send(new GetParameterCommand({ Name: this.parameterArn })); + this.accountsIds = (Parameter && Parameter.Value && JSON.parse(Parameter.Value)) || false; + } catch(err) { + logger.error('Failed to fetch accountsIdsByService from Parameter Store', { error: err.message }); + this.accountsIds = false; + } + + if(this.accountsIds === false) + throw new LambdaError('Secret is missing', LambdaError.codes.JANIS_SECRET_MISSING); + } + + /** + * @private + */ + static async fetchFromSecret() { + + try { + const secretHandler = AwsSecretsManager.secret('AccountsIdsByService'); + this.accountsIds = await secretHandler.getValue(); + this.accountsIds = this.accountsIds || false; + } catch(err) { + logger.error('Failed to fetch AccountsIdsByService from Secrets Manager', { error: err.message }); + this.accountsIds = false; + } + + if(this.accountsIds === false) + throw new LambdaError('Secret is missing', LambdaError.codes.JANIS_SECRET_MISSING); + } +}; diff --git a/lib/helpers/secret-fetcher.js b/lib/helpers/secret-fetcher.js deleted file mode 100644 index 3daa4d8..0000000 --- a/lib/helpers/secret-fetcher.js +++ /dev/null @@ -1,51 +0,0 @@ -'use strict'; - -const { AwsSecretsManager } = require('@janiscommerce/aws-secrets-manager'); - -const LambdaError = require('../lambda-error'); - -const isLocalEnv = require('./is-local-env'); - -module.exports = class SecretFetcher { - - /** - * Get the secret name of Janis Service - * @returns {string} - */ - static get secretName() { - return 'AccountsIdsByService'; - } - - static get localSecretValue() { - return {}; - } - - /** - * Request the secret value to AwsSecretManager - * @throws {LambdaError} If the secretValue is missing - */ - static async fetch() { - - if(this.secretValue) - return; - - if(isLocalEnv()) { - this.secretValue = this.localSecretValue; - return; - } - - try { - - const secretHandler = AwsSecretsManager.secret(this.secretName); - - this.secretValue = await secretHandler.getValue(); - this.secretValue = this.secretValue || false; - - } catch(err) { - this.secretValue = false; - } - - if(this.secretValue === false) - throw new LambdaError('Secret is missing', LambdaError.codes.JANIS_SECRET_MISSING); - } -}; diff --git a/lib/invoke-permissions.js b/lib/invoke-permissions.js index ecdd3ac..09d9bf7 100644 --- a/lib/invoke-permissions.js +++ b/lib/invoke-permissions.js @@ -16,5 +16,12 @@ module.exports = [ action: 'Sts:AssumeRole', resource: 'arn:aws:iam::*:role/LambdaRemoteInvoke' } + ], + [ + 'iamStatement', + { + action: 'ssm:GetParameter', + resource: 'arn:aws:ssm:*:*:parameter/accountsIdsByService' + } ] ]; diff --git a/lib/invoker.js b/lib/invoker.js index 6d9ac20..6c3517f 100644 --- a/lib/invoker.js +++ b/lib/invoker.js @@ -4,7 +4,7 @@ const { ApiSession } = require('@janiscommerce/api-session'); const LambdaError = require('./lambda-error'); -const SecretFetcher = require('./helpers/secret-fetcher'); +const AccountsIdsProvider = require('./helpers/accounts-ids-provider'); const LambdaInstance = require('./helpers/lambda-instance'); const getLambdaFunctionName = require('./helpers/get-lambda-function-name'); @@ -291,9 +291,9 @@ module.exports = class Invoker { if(isLocalEnv()) return; - await SecretFetcher.fetch(); + await AccountsIdsProvider.fetch(); - const { [serviceCode]: serviceAccountId } = SecretFetcher.secretValue; + const { [serviceCode]: serviceAccountId } = AccountsIdsProvider.accountsIds; if(!serviceAccountId) throw new LambdaError(`Service account ID not found for service code ${serviceCode}`, LambdaError.codes.NO_SERVICE_ACCOUNT_ID); diff --git a/package.json b/package.json index 9e8f942..e893c2a 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "dependencies": { "@aws-sdk/client-lambda": "^3.632.0", "@aws-sdk/client-sfn": "^3.632.0", + "@aws-sdk/client-ssm": "^3.1075.0", "@aws-sdk/client-sts": "^3.632.0", "@janiscommerce/api-session": "^3.3.1", "@janiscommerce/aws-secrets-manager": "^1.1.1", diff --git a/tests/helpers/accounts-ids-provider.js b/tests/helpers/accounts-ids-provider.js new file mode 100644 index 0000000..733e89f --- /dev/null +++ b/tests/helpers/accounts-ids-provider.js @@ -0,0 +1,203 @@ +'use strict'; + +const sinon = require('sinon'); +const assert = require('assert'); +const { mockClient } = require('aws-sdk-client-mock'); +const { SSMClient, GetParameterCommand } = require('@aws-sdk/client-ssm'); +const { AwsSecretsManager } = require('@janiscommerce/aws-secrets-manager'); +const lllog = require('lllog'); + +const AccountsIdsProvider = require('../../lib/helpers/accounts-ids-provider'); +const LambdaError = require('../../lib/lambda-error'); + +const loggerProto = Object.getPrototypeOf(lllog()); + +describe('Libraries', () => { + + describe('AccountsIdsProvider', () => { + + const fakeAccountIdsByService = { + pricing: '123456789012', + wms: '987654321098' + }; + + const fakeSecretHandler = () => ({ getValue: sinon.stub() }); + + let ssmClientMock; + + beforeEach(() => { + ssmClientMock = mockClient(SSMClient); + }); + + afterEach(() => { + sinon.restore(); + ssmClientMock.reset(); + delete AccountsIdsProvider.accountsIds; + delete process.env.JANIS_ENV; + delete process.env.DEVOPS_ACCOUNT_ID; + delete process.env.AWS_REGION; + }); + + context('When DEVOPS_ACCOUNT_ID is set (Parameter Store path)', () => { + + beforeEach(() => { + process.env.DEVOPS_ACCOUNT_ID = '111122223333'; + process.env.AWS_REGION = 'us-east-1'; + }); + + it('Should get accounts-ids from Parameter Store and cache the parsed value', async () => { + + ssmClientMock.on(GetParameterCommand).resolves({ + Parameter: { Value: JSON.stringify(fakeAccountIdsByService) } + }); + + await AccountsIdsProvider.fetch(); + + assert.deepStrictEqual(AccountsIdsProvider.accountsIds, fakeAccountIdsByService); + + const calls = ssmClientMock.commandCalls(GetParameterCommand); + assert.strictEqual(calls.length, 1); + assert.deepStrictEqual(calls[0].args[0].input, { + Name: 'arn:aws:ssm:us-east-1:111122223333:parameter/accountsIdsByService' + }); + }); + + it('Should use internal cache and call Parameter Store only once across multiple fetch() calls', async () => { + + ssmClientMock.on(GetParameterCommand).resolves({ + Parameter: { Value: JSON.stringify(fakeAccountIdsByService) } + }); + + await AccountsIdsProvider.fetch(); + await AccountsIdsProvider.fetch(); + await AccountsIdsProvider.fetch(); + + assert.deepStrictEqual(AccountsIdsProvider.accountsIds, fakeAccountIdsByService); + assert.strictEqual(ssmClientMock.commandCalls(GetParameterCommand).length, 1); + }); + + it('Should throw JANIS_SECRET_MISSING when Parameter Store returns empty Parameter', async () => { + + ssmClientMock.on(GetParameterCommand).resolves({ Parameter: null }); + + await assert.rejects(AccountsIdsProvider.fetch(), { + name: 'LambdaError', + code: LambdaError.codes.JANIS_SECRET_MISSING + }); + }); + + it('Should throw JANIS_SECRET_MISSING when Parameter Store returns Parameter without Value', async () => { + + ssmClientMock.on(GetParameterCommand).resolves({ Parameter: {} }); + + await assert.rejects(AccountsIdsProvider.fetch(), { + name: 'LambdaError', + code: LambdaError.codes.JANIS_SECRET_MISSING + }); + }); + + it('Should throw JANIS_SECRET_MISSING when GetParameterCommand fails — no fallback to Secrets Manager', async () => { + + ssmClientMock.on(GetParameterCommand).rejects(new Error('ParameterNotFound')); + + sinon.stub(loggerProto, 'error'); + sinon.spy(AwsSecretsManager, 'secret'); + + await assert.rejects(AccountsIdsProvider.fetch(), { + name: 'LambdaError', + code: LambdaError.codes.JANIS_SECRET_MISSING + }); + + sinon.assert.notCalled(AwsSecretsManager.secret); + }); + }); + + context('When DEVOPS_ACCOUNT_ID is missing (Secrets Manager fallback)', () => { + + it('Should emit a logger.warn and fall back to Secrets Manager', async () => { + + const secretHandler = fakeSecretHandler(); + + const warnStub = sinon.stub(loggerProto, 'warn'); + + sinon.stub(AwsSecretsManager, 'secret').returns(secretHandler); + secretHandler.getValue.resolves(fakeAccountIdsByService); + + await AccountsIdsProvider.fetch(); + + assert.deepStrictEqual(AccountsIdsProvider.accountsIds, fakeAccountIdsByService); + + sinon.assert.calledOnce(warnStub); + sinon.assert.calledOnceWithExactly(AwsSecretsManager.secret, 'AccountsIdsByService'); + sinon.assert.calledOnce(secretHandler.getValue); + assert.strictEqual(ssmClientMock.commandCalls(GetParameterCommand).length, 0); + }); + + it('Should treat an empty DEVOPS_ACCOUNT_ID as missing and fall back to Secrets Manager', async () => { + + process.env.DEVOPS_ACCOUNT_ID = ''; + + const secretHandler = fakeSecretHandler(); + + const warnStub = sinon.stub(loggerProto, 'warn'); + + sinon.stub(AwsSecretsManager, 'secret').returns(secretHandler); + secretHandler.getValue.resolves(fakeAccountIdsByService); + + await AccountsIdsProvider.fetch(); + + assert.deepStrictEqual(AccountsIdsProvider.accountsIds, fakeAccountIdsByService); + + sinon.assert.calledOnce(warnStub); + sinon.assert.calledOnceWithExactly(AwsSecretsManager.secret, 'AccountsIdsByService'); + assert.strictEqual(ssmClientMock.commandCalls(GetParameterCommand).length, 0); + }); + + it('Should throw JANIS_SECRET_MISSING when Secrets Manager returns empty value', async () => { + + const secretHandler = fakeSecretHandler(); + + sinon.stub(loggerProto, 'warn'); + sinon.stub(AwsSecretsManager, 'secret').returns(secretHandler); + secretHandler.getValue.resolves(); + + await assert.rejects(AccountsIdsProvider.fetch(), { + name: 'LambdaError', + code: LambdaError.codes.JANIS_SECRET_MISSING + }); + }); + + it('Should throw JANIS_SECRET_MISSING when Secrets Manager call fails', async () => { + + const secretHandler = fakeSecretHandler(); + + sinon.stub(loggerProto, 'warn'); + sinon.stub(loggerProto, 'error'); + sinon.stub(AwsSecretsManager, 'secret').returns(secretHandler); + secretHandler.getValue.rejects(); + + await assert.rejects(AccountsIdsProvider.fetch(), { + name: 'LambdaError', + code: LambdaError.codes.JANIS_SECRET_MISSING + }); + }); + }); + + context('When running in local environment', () => { + + it('Should return empty object without calling Parameter Store or Secrets Manager', async () => { + + process.env.JANIS_ENV = 'local'; + + sinon.spy(AwsSecretsManager, 'secret'); + + await AccountsIdsProvider.fetch(); + + assert.deepStrictEqual(AccountsIdsProvider.accountsIds, {}); + + sinon.assert.notCalled(AwsSecretsManager.secret); + assert.strictEqual(ssmClientMock.commandCalls(GetParameterCommand).length, 0); + }); + }); + }); +}); diff --git a/tests/helpers/secret-fetcher.js b/tests/helpers/secret-fetcher.js deleted file mode 100644 index 54bc125..0000000 --- a/tests/helpers/secret-fetcher.js +++ /dev/null @@ -1,114 +0,0 @@ -'use strict'; - -const sinon = require('sinon'); -const assert = require('assert'); - -const { AwsSecretsManager } = require('@janiscommerce/aws-secrets-manager'); - -const SecretFetcher = require('../../lib/helpers/secret-fetcher'); -const LambdaError = require('../../lib/lambda-error'); - -describe('Libraries', () => { - - describe('SecretFetcher', () => { - - const fakeAccountIdsByService = { - pricing: '123456789', - wms: '987654321' - }; - - const fakeSecretHandler = () => ({ getValue: sinon.stub() }); - - afterEach(() => { - sinon.restore(); - delete SecretFetcher.secretValue; - delete process.env.JANIS_ENV; - }); - - it('Should get the secret AccountIdsByService', async () => { - - const secretHandler = fakeSecretHandler(); - - sinon.stub(AwsSecretsManager, 'secret') - .returns(secretHandler); - - secretHandler.getValue.resolves(fakeAccountIdsByService); - - await SecretFetcher.fetch(); - - assert.deepStrictEqual(SecretFetcher.secretValue, fakeAccountIdsByService); - - sinon.assert.calledOnceWithExactly(AwsSecretsManager.secret, 'AccountsIdsByService'); - sinon.assert.calledOnce(secretHandler.getValue); - }); - - it('Should use internal cache and make request only once', async () => { - - const secretHandler = fakeSecretHandler(); - - sinon.stub(AwsSecretsManager, 'secret') - .returns(secretHandler); - - secretHandler.getValue.resolves(fakeAccountIdsByService); - - await SecretFetcher.fetch(); - await SecretFetcher.fetch(); - await SecretFetcher.fetch(); - await SecretFetcher.fetch(); - - assert.deepStrictEqual(SecretFetcher.secretValue, fakeAccountIdsByService); - - sinon.assert.calledOnceWithExactly(AwsSecretsManager.secret, 'AccountsIdsByService'); - sinon.assert.calledOnce(secretHandler.getValue); - }); - - it('Should reject when can\'t get the secret value', async () => { - - const secretHandler = fakeSecretHandler(); - - sinon.stub(AwsSecretsManager, 'secret') - .returns(secretHandler); - - secretHandler.getValue.resolves(); - - await assert.rejects(SecretFetcher.fetch(), { - name: 'LambdaError', - code: LambdaError.codes.JANIS_SECRET_MISSING - }); - - sinon.assert.calledOnceWithExactly(AwsSecretsManager.secret, 'AccountsIdsByService'); - sinon.assert.calledOnce(secretHandler.getValue); - }); - - it('Should reject when fails at getting the secret value', async () => { - - const secretHandler = fakeSecretHandler(); - - sinon.stub(AwsSecretsManager, 'secret') - .returns(secretHandler); - - secretHandler.getValue.rejects(); - - await assert.rejects(SecretFetcher.fetch(), { - name: 'LambdaError', - code: LambdaError.codes.JANIS_SECRET_MISSING - }); - - sinon.assert.calledOnceWithExactly(AwsSecretsManager.secret, 'AccountsIdsByService'); - sinon.assert.calledOnce(secretHandler.getValue); - }); - - it('Should return the local secret value (empty object) when the ENV is local', async () => { - - process.env.JANIS_ENV = 'local'; - - sinon.spy(AwsSecretsManager, 'secret'); - - await SecretFetcher.fetch(); - - assert.deepStrictEqual(SecretFetcher.secretValue, {}); - - sinon.assert.notCalled(AwsSecretsManager.secret); - }); - }); -}); diff --git a/tests/invoker.js b/tests/invoker.js index 5007aea..7d76bf7 100644 --- a/tests/invoker.js +++ b/tests/invoker.js @@ -9,7 +9,7 @@ const RouterFetcher = require('@janiscommerce/router-fetcher'); const { Invoker, LambdaError } = require('../lib/index'); const { LambdaWrapper } = require('../lib/helpers/aws-wrappers'); const LambdaInstance = require('../lib/helpers/lambda-instance'); -const SecretFetcher = require('../lib/helpers/secret-fetcher'); +const AccountsIdsProvider = require('../lib/helpers/accounts-ids-provider'); describe('Invoker', () => { @@ -69,7 +69,7 @@ describe('Invoker', () => { afterEach(() => { process.env = oldEnv; - delete SecretFetcher.secretValue; + delete AccountsIdsProvider.accountsIds; }); describe('Call', () => { @@ -757,17 +757,17 @@ describe('Invoker', () => { sinon.spy(LambdaWrapper.prototype, 'invoke'); - sinon.stub(SecretFetcher, 'fetch') + sinon.stub(AccountsIdsProvider, 'fetch') .resolves(); - SecretFetcher.secretValue = {}; + AccountsIdsProvider.accountsIds = {}; await assert.rejects(Invoker.serviceCall('some-service', 'some-function'), { name: 'LambdaError', code: LambdaError.codes.NO_SERVICE_ACCOUNT_ID }); - sinon.assert.calledOnce(SecretFetcher.fetch); + sinon.assert.calledOnce(AccountsIdsProvider.fetch); sinon.assert.notCalled(LambdaWrapper.prototype.invoke); }); @@ -778,10 +778,10 @@ describe('Invoker', () => { const ConfigError = new Error('Missing region in config'); ConfigError.name = 'ConfigError'; - sinon.stub(SecretFetcher, 'fetch') + sinon.stub(AccountsIdsProvider, 'fetch') .resolves(); - SecretFetcher.secretValue = fakeSecretValue; + AccountsIdsProvider.accountsIds = fakeSecretValue; sinon.stub(LambdaInstance, 'getInstanceWithRole') .resolves(new LambdaWrapper()); @@ -790,16 +790,16 @@ describe('Invoker', () => { await assert.rejects(Invoker.serviceCall('some-service', functionName), { name: 'ConfigError', message: 'Missing region in config' }); - sinon.assert.calledOnce(SecretFetcher.fetch); + sinon.assert.calledOnce(AccountsIdsProvider.fetch); sinon.assert.calledOnce(LambdaWrapper.prototype.invoke); }); it('Should fail if invoke rejects (rare or local cases)', async () => { - sinon.stub(SecretFetcher, 'fetch') + sinon.stub(AccountsIdsProvider, 'fetch') .resolves(); - SecretFetcher.secretValue = fakeSecretValue; + AccountsIdsProvider.accountsIds = fakeSecretValue; sinon.stub(LambdaInstance, 'getInstanceWithRole') .resolves(new LambdaWrapper()); @@ -808,16 +808,16 @@ describe('Invoker', () => { await assert.rejects(Invoker.serviceCall('some-service', functionName), { name: 'Error', message: 'AWS Failed' }); - sinon.assert.calledOnce(SecretFetcher.fetch); + sinon.assert.calledOnce(AccountsIdsProvider.fetch); sinon.assert.calledOnce(LambdaWrapper.prototype.invoke); }); it('Should return the lambda response formatted', async () => { - sinon.stub(SecretFetcher, 'fetch') + sinon.stub(AccountsIdsProvider, 'fetch') .resolves(); - SecretFetcher.secretValue = fakeSecretValue; + AccountsIdsProvider.accountsIds = fakeSecretValue; sinon.stub(LambdaInstance, 'getInstanceWithRole') .resolves(new LambdaWrapper()); @@ -831,7 +831,7 @@ describe('Invoker', () => { payload: {} }); - sinon.assert.calledOnce(SecretFetcher.fetch); + sinon.assert.calledOnce(AccountsIdsProvider.fetch); sinon.assert.calledOnceWithExactly(LambdaWrapper.prototype.invoke, { FunctionName: `${fakeServiceAccountId}:function:${lambdaExternalFunctionName}`, @@ -847,11 +847,11 @@ describe('Invoker', () => { sinon.stub(LambdaWrapper.prototype, 'invoke').resolves(invokeAsyncResponse); - sinon.spy(SecretFetcher, 'fetch'); + sinon.spy(AccountsIdsProvider, 'fetch'); const lambdaResponse = await Invoker.serviceCall('some-service', functionName); - sinon.assert.notCalled(SecretFetcher.fetch); + sinon.assert.notCalled(AccountsIdsProvider.fetch); assert.deepStrictEqual(lambdaResponse, { statusCode: invokeAsyncResponse.StatusCode, @@ -872,7 +872,7 @@ describe('Invoker', () => { sinon.stub(RouterFetcher.prototype, 'getSchema').resolves({}); sinon.spy(LambdaWrapper.prototype, 'invoke'); - sinon.spy(SecretFetcher, 'fetch'); + sinon.spy(AccountsIdsProvider, 'fetch'); await assert.rejects(Invoker.serviceCall('some-service', functionName), { name: 'LambdaError', @@ -880,16 +880,16 @@ describe('Invoker', () => { }); sinon.assert.calledOnceWithExactly(RouterFetcher.prototype.getSchema, 'some-service'); - sinon.assert.notCalled(SecretFetcher.fetch); + sinon.assert.notCalled(AccountsIdsProvider.fetch); sinon.assert.notCalled(LambdaWrapper.prototype.invoke); }); it('Should reject if the lambda response status code is 400 or higher', async () => { - sinon.stub(SecretFetcher, 'fetch') + sinon.stub(AccountsIdsProvider, 'fetch') .resolves(); - SecretFetcher.secretValue = fakeSecretValue; + AccountsIdsProvider.accountsIds = fakeSecretValue; sinon.stub(LambdaInstance, 'getInstanceWithRole') .resolves(new LambdaWrapper()); @@ -904,7 +904,7 @@ describe('Invoker', () => { code: LambdaError.codes.INVOCATION_FAILED }); - sinon.assert.calledOnce(SecretFetcher.fetch); + sinon.assert.calledOnce(AccountsIdsProvider.fetch); sinon.assert.calledOnceWithExactly(LambdaWrapper.prototype.invoke, { FunctionName: `${fakeServiceAccountId}:function:${lambdaExternalFunctionName}`, @@ -924,10 +924,10 @@ describe('Invoker', () => { Payload: '{"message": "Success"}' }; - sinon.stub(SecretFetcher, 'fetch') + sinon.stub(AccountsIdsProvider, 'fetch') .resolves(); - SecretFetcher.secretValue = fakeSecretValue; + AccountsIdsProvider.accountsIds = fakeSecretValue; sinon.stub(LambdaInstance, 'getInstanceWithRole') .resolves(new LambdaWrapper()); @@ -941,7 +941,7 @@ describe('Invoker', () => { payload: JSON.parse(lambdaResponse.Payload) }); - sinon.assert.calledOnce(SecretFetcher.fetch); + sinon.assert.calledOnce(AccountsIdsProvider.fetch); sinon.assert.calledOnceWithExactly(LambdaWrapper.prototype.invoke, { FunctionName: `${fakeServiceAccountId}:function:${lambdaExternalFunctionName}`, @@ -959,10 +959,10 @@ describe('Invoker', () => { Payload: 'OK' }; - sinon.stub(SecretFetcher, 'fetch') + sinon.stub(AccountsIdsProvider, 'fetch') .resolves(); - SecretFetcher.secretValue = fakeSecretValue; + AccountsIdsProvider.accountsIds = fakeSecretValue; sinon.stub(LambdaInstance, 'getInstanceWithRole') .resolves(new LambdaWrapper()); @@ -976,7 +976,7 @@ describe('Invoker', () => { payload: lambdaResponse.Payload }); - sinon.assert.calledOnce(SecretFetcher.fetch); + sinon.assert.calledOnce(AccountsIdsProvider.fetch); sinon.assert.calledOnceWithExactly(LambdaWrapper.prototype.invoke, { FunctionName: `${fakeServiceAccountId}:function:${lambdaExternalFunctionName}`, @@ -992,10 +992,10 @@ describe('Invoker', () => { StatusCode: 202 }; - sinon.stub(SecretFetcher, 'fetch') + sinon.stub(AccountsIdsProvider, 'fetch') .resolves(); - SecretFetcher.secretValue = fakeSecretValue; + AccountsIdsProvider.accountsIds = fakeSecretValue; sinon.stub(LambdaInstance, 'getInstanceWithRole') .resolves(new LambdaWrapper()); @@ -1009,7 +1009,7 @@ describe('Invoker', () => { payload: {} }); - sinon.assert.calledOnce(SecretFetcher.fetch); + sinon.assert.calledOnce(AccountsIdsProvider.fetch); sinon.assert.calledOnceWithExactly(LambdaWrapper.prototype.invoke, { FunctionName: `${fakeServiceAccountId}:function:${lambdaExternalFunctionName}`, @@ -1023,10 +1023,10 @@ describe('Invoker', () => { it('Should not reject if the lambda response status code is 400 or higher', async () => { - sinon.stub(SecretFetcher, 'fetch') + sinon.stub(AccountsIdsProvider, 'fetch') .resolves(); - SecretFetcher.secretValue = fakeSecretValue; + AccountsIdsProvider.accountsIds = fakeSecretValue; sinon.stub(LambdaInstance, 'getInstanceWithRole') .resolves(new LambdaWrapper()); @@ -1045,7 +1045,7 @@ describe('Invoker', () => { } }); - sinon.assert.calledOnce(SecretFetcher.fetch); + sinon.assert.calledOnce(AccountsIdsProvider.fetch); sinon.assert.calledOnceWithExactly(LambdaWrapper.prototype.invoke, { FunctionName: `${fakeServiceAccountId}:function:${lambdaExternalFunctionName}`, @@ -1167,17 +1167,17 @@ describe('Invoker', () => { sinon.spy(LambdaWrapper.prototype, 'invoke'); - sinon.stub(SecretFetcher, 'fetch') + sinon.stub(AccountsIdsProvider, 'fetch') .resolves(); - SecretFetcher.secretValue = {}; + AccountsIdsProvider.accountsIds = {}; await assert.rejects(Invoker.serviceClientCall('some-service', 'some-function', client), { name: 'LambdaError', code: LambdaError.codes.NO_SERVICE_ACCOUNT_ID }); - sinon.assert.calledOnce(SecretFetcher.fetch); + sinon.assert.calledOnce(AccountsIdsProvider.fetch); sinon.assert.notCalled(LambdaWrapper.prototype.invoke); }); @@ -1188,10 +1188,10 @@ describe('Invoker', () => { const ConfigError = new Error('Missing region in config'); ConfigError.name = 'ConfigError'; - sinon.stub(SecretFetcher, 'fetch') + sinon.stub(AccountsIdsProvider, 'fetch') .resolves(); - SecretFetcher.secretValue = fakeSecretValue; + AccountsIdsProvider.accountsIds = fakeSecretValue; sinon.stub(LambdaInstance, 'getInstanceWithRole') .resolves(new LambdaWrapper()); @@ -1203,16 +1203,16 @@ describe('Invoker', () => { message: 'Missing region in config' }); - sinon.assert.calledOnce(SecretFetcher.fetch); + sinon.assert.calledOnce(AccountsIdsProvider.fetch); sinon.assert.calledOnce(LambdaWrapper.prototype.invoke); }); it('Should fail if invoke rejects (rare or local cases)', async () => { - sinon.stub(SecretFetcher, 'fetch') + sinon.stub(AccountsIdsProvider, 'fetch') .resolves(); - SecretFetcher.secretValue = fakeSecretValue; + AccountsIdsProvider.accountsIds = fakeSecretValue; sinon.stub(LambdaInstance, 'getInstanceWithRole') .resolves(new LambdaWrapper()); @@ -1221,16 +1221,16 @@ describe('Invoker', () => { await assert.rejects(Invoker.serviceClientCall('some-service', functionName, client), { name: 'Error', message: 'AWS Failed' }); - sinon.assert.calledOnce(SecretFetcher.fetch); + sinon.assert.calledOnce(AccountsIdsProvider.fetch); sinon.assert.calledOnce(LambdaWrapper.prototype.invoke); }); it('Should return the lambda response formatted', async () => { - sinon.stub(SecretFetcher, 'fetch') + sinon.stub(AccountsIdsProvider, 'fetch') .resolves(); - SecretFetcher.secretValue = fakeSecretValue; + AccountsIdsProvider.accountsIds = fakeSecretValue; sinon.stub(LambdaInstance, 'getInstanceWithRole') .resolves(new LambdaWrapper()); @@ -1244,7 +1244,7 @@ describe('Invoker', () => { payload: {} }); - sinon.assert.calledOnce(SecretFetcher.fetch); + sinon.assert.calledOnce(AccountsIdsProvider.fetch); sinon.assert.calledOnceWithExactly(LambdaWrapper.prototype.invoke, { FunctionName: `${fakeServiceAccountId}:function:${lambdaExternalFunctionName}`, @@ -1261,11 +1261,11 @@ describe('Invoker', () => { sinon.stub(LambdaWrapper.prototype, 'invoke').resolves(invokeAsyncResponse); - sinon.spy(SecretFetcher, 'fetch'); + sinon.spy(AccountsIdsProvider, 'fetch'); const lambdaResponse = await Invoker.serviceClientCall('some-service', functionName, client); - sinon.assert.notCalled(SecretFetcher.fetch); + sinon.assert.notCalled(AccountsIdsProvider.fetch); assert.deepStrictEqual(lambdaResponse, { statusCode: invokeAsyncResponse.StatusCode, @@ -1286,7 +1286,7 @@ describe('Invoker', () => { sinon.stub(RouterFetcher.prototype, 'getSchema').resolves({ servers: [routerFetcherSchema.servers[0]] }); sinon.spy(LambdaWrapper.prototype, 'invoke'); - sinon.spy(SecretFetcher, 'fetch'); + sinon.spy(AccountsIdsProvider, 'fetch'); await assert.rejects(Invoker.serviceClientCall('some-service', functionName, client), { name: 'LambdaError', @@ -1295,16 +1295,16 @@ describe('Invoker', () => { sinon.assert.calledOnceWithExactly(RouterFetcher.prototype.getSchema, 'some-service'); - sinon.assert.notCalled(SecretFetcher.fetch); + sinon.assert.notCalled(AccountsIdsProvider.fetch); sinon.assert.notCalled(LambdaWrapper.prototype.invoke); }); it('Should reject if the lambda response status code is 400 or higher', async () => { - sinon.stub(SecretFetcher, 'fetch') + sinon.stub(AccountsIdsProvider, 'fetch') .resolves(); - SecretFetcher.secretValue = fakeSecretValue; + AccountsIdsProvider.accountsIds = fakeSecretValue; sinon.stub(LambdaInstance, 'getInstanceWithRole') .resolves(new LambdaWrapper()); @@ -1319,7 +1319,7 @@ describe('Invoker', () => { code: LambdaError.codes.INVOCATION_FAILED }); - sinon.assert.calledOnce(SecretFetcher.fetch); + sinon.assert.calledOnce(AccountsIdsProvider.fetch); sinon.assert.calledOnceWithExactly(LambdaWrapper.prototype.invoke, { FunctionName: `${fakeServiceAccountId}:function:${lambdaExternalFunctionName}`, @@ -1340,10 +1340,10 @@ describe('Invoker', () => { Payload: '{"message": "Success"}' }; - sinon.stub(SecretFetcher, 'fetch') + sinon.stub(AccountsIdsProvider, 'fetch') .resolves(); - SecretFetcher.secretValue = fakeSecretValue; + AccountsIdsProvider.accountsIds = fakeSecretValue; sinon.stub(LambdaInstance, 'getInstanceWithRole') .resolves(new LambdaWrapper()); @@ -1357,7 +1357,7 @@ describe('Invoker', () => { payload: JSON.parse(lambdaResponse.Payload) }); - sinon.assert.calledOnce(SecretFetcher.fetch); + sinon.assert.calledOnce(AccountsIdsProvider.fetch); sinon.assert.calledOnceWithExactly(LambdaWrapper.prototype.invoke, { FunctionName: `${fakeServiceAccountId}:function:${lambdaExternalFunctionName}`, @@ -1375,10 +1375,10 @@ describe('Invoker', () => { Payload: 'OK' }; - sinon.stub(SecretFetcher, 'fetch') + sinon.stub(AccountsIdsProvider, 'fetch') .resolves(); - SecretFetcher.secretValue = fakeSecretValue; + AccountsIdsProvider.accountsIds = fakeSecretValue; sinon.stub(LambdaInstance, 'getInstanceWithRole') .resolves(new LambdaWrapper()); @@ -1392,7 +1392,7 @@ describe('Invoker', () => { payload: lambdaResponse.Payload }); - sinon.assert.calledOnce(SecretFetcher.fetch); + sinon.assert.calledOnce(AccountsIdsProvider.fetch); sinon.assert.calledOnceWithExactly(LambdaWrapper.prototype.invoke, { FunctionName: `${fakeServiceAccountId}:function:${lambdaExternalFunctionName}`, @@ -1409,10 +1409,10 @@ describe('Invoker', () => { StatusCode: 202 }; - sinon.stub(SecretFetcher, 'fetch') + sinon.stub(AccountsIdsProvider, 'fetch') .resolves(); - SecretFetcher.secretValue = fakeSecretValue; + AccountsIdsProvider.accountsIds = fakeSecretValue; sinon.stub(LambdaInstance, 'getInstanceWithRole') .resolves(new LambdaWrapper()); @@ -1426,7 +1426,7 @@ describe('Invoker', () => { payload: {} }); - sinon.assert.calledOnce(SecretFetcher.fetch); + sinon.assert.calledOnce(AccountsIdsProvider.fetch); sinon.assert.calledOnceWithExactly(LambdaWrapper.prototype.invoke, { FunctionName: `${fakeServiceAccountId}:function:${lambdaExternalFunctionName}`, @@ -1441,10 +1441,10 @@ describe('Invoker', () => { it('Should not reject if the lambda response status code is 400 or higher', async () => { - sinon.stub(SecretFetcher, 'fetch') + sinon.stub(AccountsIdsProvider, 'fetch') .resolves(); - SecretFetcher.secretValue = fakeSecretValue; + AccountsIdsProvider.accountsIds = fakeSecretValue; sinon.stub(LambdaInstance, 'getInstanceWithRole') .resolves(new LambdaWrapper()); @@ -1465,7 +1465,7 @@ describe('Invoker', () => { } }); - sinon.assert.calledOnce(SecretFetcher.fetch); + sinon.assert.calledOnce(AccountsIdsProvider.fetch); sinon.assert.calledOnceWithExactly(LambdaWrapper.prototype.invoke, { FunctionName: `${fakeServiceAccountId}:function:${lambdaExternalFunctionName}`, @@ -1579,7 +1579,7 @@ describe('Invoker', () => { it('Should reject when no received namespace', async () => { - sinon.spy(SecretFetcher, 'fetch'); + sinon.spy(AccountsIdsProvider, 'fetch'); sinon.spy(LambdaWrapper.prototype, 'invoke'); @@ -1588,14 +1588,14 @@ describe('Invoker', () => { { name: 'LambdaError', code: LambdaError.codes.NO_ENDPOINT_PARAMS } ); - sinon.assert.notCalled(SecretFetcher.fetch); + sinon.assert.notCalled(AccountsIdsProvider.fetch); sinon.assert.notCalled(LambdaWrapper.prototype.invoke); }); it('Should reject when no method was received', async () => { - sinon.spy(SecretFetcher, 'fetch'); + sinon.spy(AccountsIdsProvider, 'fetch'); sinon.spy(LambdaWrapper.prototype, 'invoke'); @@ -1604,19 +1604,19 @@ describe('Invoker', () => { { name: 'LambdaError', code: LambdaError.codes.NO_ENDPOINT_PARAMS } ); - sinon.assert.notCalled(SecretFetcher.fetch); + sinon.assert.notCalled(AccountsIdsProvider.fetch); sinon.assert.notCalled(LambdaWrapper.prototype.invoke); }); it('Should invoke the api lambda of the service', async () => { - sinon.stub(SecretFetcher, 'fetch') + sinon.stub(AccountsIdsProvider, 'fetch') .resolves(); const accountId = 123456789; - SecretFetcher.secretValue = { catalog: accountId }; + AccountsIdsProvider.accountsIds = { catalog: accountId }; sinon.stub(LambdaInstance, 'getInstanceWithRole') .resolves(new LambdaWrapper()); @@ -1630,7 +1630,7 @@ describe('Invoker', () => { await Invoker.apiCall('catalog', 'Update-Product', 'product', 'update', event); - sinon.assert.calledOnce(SecretFetcher.fetch); + sinon.assert.calledOnce(AccountsIdsProvider.fetch); sinon.assert.calledOnceWithExactly(LambdaWrapper.prototype.invoke, { FunctionName: `${accountId}:function:API-Catalog-Update-Product-test`,