Repository: serverless/dashboard (language: python)

Issue:
Convert extension to be Serverless Console specific
As discussed internally we're converting the extension so it is Serverless Console specific.

In this PR I reshape user settings which now will be taken with `SLS_EXTENSION` (not `SLS_OTEL_USER_SETTINGS`) environment variable.

Additionally:
- (in response to above rename) improve the naming convention of internal (test purpose) environment variables
- Move test documentation to the test folder and document all test environment variables.




Discussion / hints:

Fix patch:
diff --git a/node/package.json b/node/package.json
index 758fc3acb..ccb6b5e56 100644
--- a/node/package.json
+++ b/node/package.json
@@ -31,7 +31,6 @@
     "eslint": "^8.18.0",
     "eslint-plugin-import": "^2.26.0",
     "essentials": "^1.2.0",
-    "evil-dns": "^0.2.0",
     "git-list-updated": "^1.2.1",
     "github-release-from-cc-changelog": "^2.3.0",
     "husky": "^4.3.8",
@@ -45,7 +44,6 @@
     "node-fetch": "^2.6.7",
     "node-machine-id": "^1.1.12",
     "prettier": "^2.7.1",
-    "process-utils": "^4.0.0",
     "standard-version": "^9.5.0",
     "timers-ext": "^0.1.7",
     "type": "^2.6.0",
diff --git a/node/packages/aws-lambda-otel-extension/external/otel-extension-external/index.js b/node/packages/aws-lambda-otel-extension/external/otel-extension-external/index.js
old mode 100644
new mode 100755
index 5a51536c0..bf2f5c195
--- a/node/packages/aws-lambda-otel-extension/external/otel-extension-external/index.js
+++ b/node/packages/aws-lambda-otel-extension/external/otel-extension-external/index.js
@@ -7,7 +7,7 @@ let isInitializing = true;
 let invocationStartTime;
 let shutdownStartTime;
 
-module.exports = (async () => {
+(async () => {
   const fs = require('fs');
   const http = require('http');
   const path = require('path');
@@ -15,6 +15,7 @@ module.exports = (async () => {
   const { EventEmitter } = require('events');
 
   const baseUrl = `http://${process.env.AWS_LAMBDA_RUNTIME_API}/2020-01-01/extension`;
+  const sandboxHostname = process.env.SLS_TEST_EXTENSION_HOSTNAME || 'sandbox';
 
   const runtimeEventEmitter = new EventEmitter();
   const servers = new Set();
@@ -164,7 +165,7 @@ module.exports = (async () => {
             }
           });
         })
-        .listen(4243, 'sandbox')
+        .listen(4243, sandboxHostname)
     );
 
     // Subscribe to logs.
@@ -176,7 +177,7 @@ module.exports = (async () => {
       const eventTypes = ['platform'];
       if (!isNoopMode && !userSettings.logs.disabled) eventTypes.push('function');
       const putData = JSON.stringify({
-        destination: { protocol: 'HTTP', URI: 'http://sandbox:4243' },
+        destination: { protocol: 'HTTP', URI: `http://${sandboxHostname}:4243` },
         types: eventTypes,
         buffering: { timeoutMs: 25, maxBytes: 262144, maxItems: 1000 },
         schemaVersion: '2021-03-18',
@@ -384,7 +385,7 @@ module.exports = (async () => {
     'Extension overhead duration: external shutdown:',
     `${Math.round(Number(process.hrtime.bigint() - shutdownStartTime) / 1000000)}ms`
   );
-  if (!process.env.SLS_TEST_EXTENSION_EXTERNAL_NO_EXIT) process.exit();
+  process.exit();
 })().catch((error) => {
   // Ensure to crash extension process on unhandled rejection
   process.nextTick(() => {


Test patch:
diff --git a/node/packages/aws-lambda-otel-extension/test/README.md b/node/packages/aws-lambda-otel-extension/test/README.md
index 63fe69b38..8853ec55b 100644
--- a/node/packages/aws-lambda-otel-extension/test/README.md
+++ b/node/packages/aws-lambda-otel-extension/test/README.md
@@ -53,8 +53,9 @@ How extensions behave, for various testing purposes, can be tweaked with followi
   - Log payloads which are send between extensions and to the external ingestion server
   - Report durations overhead that extension introduces
 - `SLS_TEST_EXTENSION_INTERNAL_LOG` - In context of the internal extension, log payloads instead of sending them to the external extension
-- `SLS_TEST_EXTENSION_EXTERNAL_NO_EXIT` - In context of the external extension, do not exit the process after shutdown phase
+- `SLS_TEST_EXTENSION_HOSTNAME` - Custom environment hostname. Used to override `sandbox` when setting up log subscription server
 - `SLS_TEST_EXTENSION_REPORT_TYPE` - Set to `json` to pass reports in direct JSON format instead of Protobuf
 - `SLS_TEST_EXTENSION_REPORT_DESTINATION` - Telemetry reports normally are sent to the Console ingestion servers, with this variable this can be overridden:
   - Set to `s3://<bucket>//<root-key>` to send reports to S3 bucket
   - Set to `log` to just log reports into process stdout
+- `SLS_TEST_EXTENSION_COMMAND` - _(Processed only in context of the external extension unit test)_ A command to run external extension process (e.g. `node path/to/external-extension/module.js`)
diff --git a/node/packages/aws-lambda-otel-extension/test/lib/process-function.js b/node/packages/aws-lambda-otel-extension/test/lib/process-function.js
index 2ce99bf2b..d3037a886 100644
--- a/node/packages/aws-lambda-otel-extension/test/lib/process-function.js
+++ b/node/packages/aws-lambda-otel-extension/test/lib/process-function.js
@@ -131,7 +131,11 @@ const retrieveReports = async (testConfig) => {
       logGroupName: `/aws/lambda/${testConfig.configuration.FunctionName}`,
       nextToken,
     });
-    if (result.nextToken) return [...result.events, ...(await retrieveEvents(result.nextToken))];
+    // AWS happens to respond with `nextToken` chains when there are no further events
+    // Therefore we use it only if we received some events with the request
+    if (result.nextToken && result.events.length) {
+      return [...result.events, ...(await retrieveEvents(result.nextToken))];
+    }
     return result.events;
   };
   const retrieveAllEvents = async () => {
@@ -269,7 +273,6 @@ const retrieveReports = async (testConfig) => {
           currentProcessData.initDuration = Number(reportData.initDuration);
         }
         invocationsData.push(currentInvocationData);
-        currentInvocationData = null;
       }
     }
   } while (invocationsData.length < testConfig.invokeCount);
diff --git a/node/packages/aws-lambda-otel-extension/test/unit/external/index.test.js b/node/packages/aws-lambda-otel-extension/test/unit/external/index.test.js
index a0a090f69..225044480 100644
--- a/node/packages/aws-lambda-otel-extension/test/unit/external/index.test.js
+++ b/node/packages/aws-lambda-otel-extension/test/unit/external/index.test.js
@@ -1,11 +1,12 @@
 'use strict';
 
 const { expect } = require('chai');
+
+const path = require('path');
 const { EventEmitter } = require('events');
-const evilDns = require('evil-dns');
+const spawn = require('child-process-ext/spawn');
+const argsParse = require('yargs-parser');
 const log = require('log').get('test');
-const requireUncached = require('ncjsm/require-uncached');
-const overwriteStdoutWrite = require('process-utils/override-stdout-write');
 const getExtensionServerMock = require('../../utils/get-extension-server-mock');
 const normalizeOtelAttributes = require('../../utils/normalize-otel-attributes');
 const { default: fetch } = require('node-fetch');
@@ -13,15 +14,14 @@ const ensureNpmDependencies = require('../../../scripts/lib/ensure-npm-dependenc
 
 const OTEL_SERVER_PORT = 2772;
 const port = 9001;
+const extensionFilename = path.resolve(
+  __dirname,
+  '../../../external/otel-extension-external/index.js'
+);
 
 describe('external', () => {
   before(async () => {
     ensureNpmDependencies('external/otel-extension-external');
-    evilDns.add('sandbox', '127.0.0.1');
-    process.env.AWS_LAMBDA_RUNTIME_API = `127.0.0.1:${port}`;
-    process.env.SLS_TEST_EXTENSION_REPORT_TYPE = 'json';
-    process.env.SLS_TEST_EXTENSION_EXTERNAL_NO_EXIT = '1';
-    process.env.SLS_TEST_EXTENSION_REPORT_DESTINATION = 'log';
   });
 
   it('should handle plain success invocation', async () => {
@@ -30,11 +30,20 @@ describe('external', () => {
     const { server, listenerEmitter } = getExtensionServerMock(emitter, { requestId });
 
     server.listen(port);
-    let stdoutData = '';
-    const extensionProcess = overwriteStdoutWrite(
-      (data) => (stdoutData += data),
-      async () => requireUncached(() => require('../../../external/otel-extension-external'))
-    );
+    const [extensionCommand, ...extensionArgs] = (() => {
+      const customCommand = process.env.SLS_TEST_EXTENSION_COMMAND;
+      if (!customCommand) return ['node', extensionFilename];
+      return argsParse(customCommand)._;
+    })();
+    const extensionProcess = spawn(extensionCommand, extensionArgs, {
+      env: {
+        ...process.env,
+        AWS_LAMBDA_RUNTIME_API: `127.0.0.1:${port}`,
+        SLS_TEST_EXTENSION_REPORT_TYPE: 'json',
+        SLS_TEST_EXTENSION_REPORT_DESTINATION: 'log',
+        SLS_TEST_EXTENSION_HOSTNAME: 'localhost',
+      },
+    });
     await Promise.all([
       new Promise((resolve) => listenerEmitter.once('listener', resolve)),
       new Promise((resolve) => listenerEmitter.once('logsSubscription', resolve)),
@@ -220,7 +229,7 @@ describe('external', () => {
       },
     ]);
 
-    await extensionProcess;
+    const stdoutData = (await extensionProcess).stdoutBuffer.toString();
     server.close();
 
     log.debug('report string %s', stdoutData);