From 27bdacb34e746867a66052f7dacbe902a3dab9a6 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 23 Aug 2023 10:23:16 +0400 Subject: [PATCH] feat(repeater): send runtime information upon repeater registration (#442) closes #441 --- .github/workflows/deploy.yml | 13 ++- Dockerfile | 3 + package-lock.json | 25 +++++ package.json | 4 + src/Config/container.ts | 9 +- src/Handlers/Events/RepeaterRegistering.ts | 12 ++- src/Repeater/DefaultRepeaterLauncher.ts | 12 ++- src/Repeater/DefaultRuntimeDetector.ts | 110 +++++++++++++++++++++ src/Repeater/RepeaterServer.ts | 5 + src/Repeater/RuntimeDetector.ts | 13 +++ src/Repeater/ServerRepeaterLauncher.ts | 9 +- src/Repeater/index.ts | 2 + 12 files changed, 212 insertions(+), 5 deletions(-) create mode 100644 src/Repeater/DefaultRuntimeDetector.ts create mode 100644 src/Repeater/RuntimeDetector.ts diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index bd99ee68..6b6346c2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -34,6 +34,9 @@ jobs: - name: Install deps uses: ./.github/workflows/composite/npm + - name: Set default distribution + run: npm pkg set brightCli.distribution=package + - name: Build package run: npm run build @@ -108,6 +111,9 @@ jobs: with: version: ${{ matrix.node }} + - name: Set distribution + run: npm pkg set brightCli.distribution=${{ matrix.target }}-executable + - name: Build executable file run: npm run build:pkg -- -t node${{ matrix.node }}-${{ matrix.target }}-x64 @@ -163,6 +169,9 @@ jobs: - name: Setup node uses: ./.github/workflows/composite/npm + - name: Set NPM distribution + run: npm pkg set brightCli.distribution=npm + - run: npm publish --tag $TAG env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} @@ -170,7 +179,9 @@ jobs: # The scope does not align with the package name # FIXME: once GitHub organization name has been changed, we should prevent this behavior - name: Prepare GPR package - run: npm pkg set name='@neuralegion/bright-cli' + run: | + npm pkg set name='@neuralegion/bright-cli' \ + && npm pkg set brightCli.distribution=gpr - name: Setup node uses: ./.github/workflows/composite/npm diff --git a/Dockerfile b/Dockerfile index d8bb7dd6..4eba3e42 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,9 @@ LABEL org.opencontainers.image.version="$VERSION" # good colors for most applications ENV TERM xterm +# inform cli that it's running inside docker container +ENV BRIGHT_CLI_DOCKER 1 + # avoid million NPM install messages ENV npm_config_loglevel warn # allow installing when the main user is root diff --git a/package-lock.json b/package-lock.json index 3001fe0d..666097ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@neuralegion/os-service": "^1.1.1", "ajv": "^6.12.6", "amqplib": "~0.10.2", + "arch": "^2.2.0", "better-ajv-errors": "^1.2.0", "chalk": "^4.1.2", "content-type": "^1.0.4", @@ -3050,6 +3051,25 @@ "node": ">= 8" } }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -17530,6 +17550,11 @@ "picomatch": "^2.0.4" } }, + "arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==" + }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", diff --git a/package.json b/package.json index fd262ade..a52ae8f6 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@neuralegion/os-service": "^1.1.1", "ajv": "^6.12.6", "amqplib": "~0.10.2", + "arch": "^2.2.0", "better-ajv-errors": "^1.2.0", "chalk": "^4.1.2", "content-type": "^1.0.4", @@ -155,5 +156,8 @@ "build:pkg": "pkg .", "prepare": "is-ci || husky install", "start": "node -r ts-node/register/transpile-only -r tsconfig-paths/register src/index.ts" + }, + "brightCli": { + "distribution": "unknown" } } diff --git a/src/Config/container.ts b/src/Config/container.ts index 6f288db8..0464bb82 100644 --- a/src/Config/container.ts +++ b/src/Config/container.ts @@ -54,7 +54,9 @@ import { RepeaterServer, DefaultRepeaterServer, RepeaterCommandHub, - DefaultRepeaterCommandHub + DefaultRepeaterCommandHub, + RuntimeDetector, + DefaultRuntimeDetector } from '../Repeater'; import { container, Lifecycle } from 'tsyringe'; @@ -105,6 +107,11 @@ container }, { lifecycle: Lifecycle.Singleton } ) + .register( + RuntimeDetector, + { useClass: DefaultRuntimeDetector }, + { lifecycle: Lifecycle.Singleton } + ) .register( RepeaterServer, { diff --git a/src/Handlers/Events/RepeaterRegistering.ts b/src/Handlers/Events/RepeaterRegistering.ts index 5a29db9b..44c2bb2d 100644 --- a/src/Handlers/Events/RepeaterRegistering.ts +++ b/src/Handlers/Events/RepeaterRegistering.ts @@ -1,9 +1,19 @@ import { Event } from '../../Bus'; +interface RepeaterRuntime { + readonly transport?: 'rabbitmq' | 'ws'; + readonly arch?: string; + readonly os?: string; + readonly docker?: boolean; + readonly distribution?: string; + readonly nodeVersion?: string; +} + export class RepeaterRegistering implements Event { constructor( public readonly repeaterId: string, public readonly version: string, - public readonly localScriptsUsed: boolean + public readonly localScriptsUsed: boolean, + public readonly runtime?: RepeaterRuntime ) {} } diff --git a/src/Repeater/DefaultRepeaterLauncher.ts b/src/Repeater/DefaultRepeaterLauncher.ts index cd13661d..0117bdc7 100644 --- a/src/Repeater/DefaultRepeaterLauncher.ts +++ b/src/Repeater/DefaultRepeaterLauncher.ts @@ -15,6 +15,7 @@ import { } from '../Handlers'; import { CliInfo } from '../Config'; import { RepeaterCommandHub } from './RepeaterCommandHub'; +import { RuntimeDetector } from './RuntimeDetector'; import { gt } from 'semver'; import chalk from 'chalk'; import { delay, inject, injectable } from 'tsyringe'; @@ -28,6 +29,7 @@ export class DefaultRepeaterLauncher implements RepeaterLauncher { private repeaterStarted: boolean = false; constructor( + @inject(RuntimeDetector) private readonly runtimeDetector: RuntimeDetector, @inject(Bus) private readonly bus: Bus, @inject(RepeaterCommandHub) private readonly commandHub: RepeaterCommandHub, @inject(VirtualScripts) private readonly virtualScripts: VirtualScripts, @@ -117,7 +119,15 @@ export class DefaultRepeaterLauncher implements RepeaterLauncher { payload: new RepeaterRegistering( repeaterId, this.info.version, - !!this.virtualScripts.size + !!this.virtualScripts.size, + { + transport: 'rabbitmq', + os: this.runtimeDetector.os(), + arch: this.runtimeDetector.arch(), + docker: this.runtimeDetector.isInsideDocker(), + distribution: this.runtimeDetector.distribution(), + nodeVersion: this.runtimeDetector.nodeVersion() + } ) }); diff --git a/src/Repeater/DefaultRuntimeDetector.ts b/src/Repeater/DefaultRuntimeDetector.ts new file mode 100644 index 00000000..8f11615f --- /dev/null +++ b/src/Repeater/DefaultRuntimeDetector.ts @@ -0,0 +1,110 @@ +import packageInfo from '../../package.json'; +import { RuntimeDetector } from './RuntimeDetector'; +import arch from 'arch'; +import { execSync } from 'child_process'; +import os from 'os'; + +export class DefaultRuntimeDetector implements RuntimeDetector { + public distribution(): string | undefined { + return packageInfo.brightCli.distribution; + } + + public isInsideDocker(): boolean { + return !!process.env['BRIGHT_CLI_DOCKER']; + } + + public nodeVersion(): string { + return process.version; + } + + public arch(): string { + try { + return arch(); + } catch { + // pass + } + + // As a fallback use arch info for which the Node.js binary was compiled + return os.arch(); + } + + public os(): string { + const platform = os.platform(); + + if (platform === 'darwin') { + return this.detectMacosVersion(); + } else if (platform === 'linux') { + return this.detectLinuxVersion(); + } else if (platform === 'win32') { + return this.detectWindowsVersion(); + } + + // As a fallback use OS info for which the Node.js binary was compiled + return `${os.platform()} (${os.release()})`; + } + + private detectMacosVersion() { + try { + const name = execSync('sw_vers -productName', { + encoding: 'utf8' + }).trim(); + const version = execSync('sw_vers -productVersion', { + encoding: 'utf8' + }).trim(); + const build = execSync('sw_vers -buildVersion', { + encoding: 'utf8' + }).trim(); + + if (name.length && version.length && build.length) { + return `${name} ${version} (${build})`; + } + } catch { + // pass + } + + return `${os.platform()} (${os.release()})`; + } + + private detectLinuxVersion() { + try { + const osRelease = execSync('cat /etc/os-release', { + encoding: 'utf8' + }).trim(); + const extractValue = (key: string) => + new RegExp( + `(?:^|[\r\n]+)${key}(?:\\s*=\\s*?|:\\s+?)(\\s*'(?:\\\\'|[^'])*'|\\s*"(?:\\\\"|[^"])*"|\\s*\`(?:\\\\\`|[^\`])*\`|[^#\r\n]+)?`, + 'i' + ) + .exec(osRelease)?.[1] + .replace(/^(['"`])([\s\S]*)\1$/i, '$2'); + + const name = extractValue('NAME') || extractValue('ID'); + const version = extractValue('VERSION') || extractValue('VERSION_ID'); + const prettyName = extractValue('PRETTY_NAME'); + + if (name.length && version.length) { + return `${name} ${version}`; + } else if (prettyName.length) { + return prettyName; + } + } catch { + // pass + } + + return `${os.platform()} (${os.release()})`; + } + + private detectWindowsVersion() { + try { + const version = execSync('ver', { encoding: 'utf8' }).trim(); + + if (version.length) { + return version; + } + } catch { + // pass + } + + return `${os.platform()} (${os.release()})`; + } +} diff --git a/src/Repeater/RepeaterServer.ts b/src/Repeater/RepeaterServer.ts index edf922e8..591c4c7f 100644 --- a/src/Repeater/RepeaterServer.ts +++ b/src/Repeater/RepeaterServer.ts @@ -71,6 +71,11 @@ export interface DeployCommandOptions { export interface DeploymentRuntime { version: string; scriptsLoaded: boolean; + os?: string; + arch?: string; + docker?: boolean; + distribution?: string; + nodeVersion?: string; } export interface RepeaterServer { diff --git a/src/Repeater/RuntimeDetector.ts b/src/Repeater/RuntimeDetector.ts new file mode 100644 index 00000000..d9e98303 --- /dev/null +++ b/src/Repeater/RuntimeDetector.ts @@ -0,0 +1,13 @@ +export interface RuntimeDetector { + os(): string; + + arch(): string; + + isInsideDocker(): boolean; + + nodeVersion(): string; + + distribution(): string | undefined; +} + +export const RuntimeDetector: unique symbol = Symbol('RuntimeDetector'); diff --git a/src/Repeater/ServerRepeaterLauncher.ts b/src/Repeater/ServerRepeaterLauncher.ts index 63c3d6c1..3b449f25 100644 --- a/src/Repeater/ServerRepeaterLauncher.ts +++ b/src/Repeater/ServerRepeaterLauncher.ts @@ -6,6 +6,7 @@ import { RepeaterServerReconnectionFailedEvent, RepeaterServerRequestEvent } from './RepeaterServer'; +import { RuntimeDetector } from './RuntimeDetector'; import { ScriptLoader, VirtualScripts } from '../Scripts'; import { StartupManager } from '../StartupScripts'; import { Certificates, Request } from '../RequestExecutor'; @@ -20,6 +21,7 @@ export class ServerRepeaterLauncher implements RepeaterLauncher { private repeaterStarted: boolean = false; constructor( + @inject(RuntimeDetector) private readonly runtimeDetector: RuntimeDetector, @inject(VirtualScripts) private readonly virtualScripts: VirtualScripts, @inject(RepeaterServer) private readonly repeaterServer: RepeaterServer, @inject(StartupManager) @@ -108,7 +110,12 @@ export class ServerRepeaterLauncher implements RepeaterLauncher { private getRuntime(): DeploymentRuntime { return { version: this.info.version, - scriptsLoaded: !!this.virtualScripts.size + scriptsLoaded: !!this.virtualScripts.size, + os: this.runtimeDetector.os(), + arch: this.runtimeDetector.arch(), + docker: this.runtimeDetector.isInsideDocker(), + distribution: this.runtimeDetector.distribution(), + nodeVersion: this.runtimeDetector.nodeVersion() }; } diff --git a/src/Repeater/index.ts b/src/Repeater/index.ts index d57d1d20..84f68db0 100644 --- a/src/Repeater/index.ts +++ b/src/Repeater/index.ts @@ -6,3 +6,5 @@ export * from './DefaultRepeaterServer'; export * from './RepeaterLauncher'; export * from './RepeaterServer'; export * from './ServerRepeaterLauncher'; +export * from './RuntimeDetector'; +export * from './DefaultRuntimeDetector';