From 3e8d8d8e1b9a88376a6460094dea0c08ce19742e Mon Sep 17 00:00:00 2001 From: Rico Hermans Date: Fri, 17 Mar 2023 13:48:48 +0100 Subject: [PATCH] fix(cli): user agent is reported as `undefined/undefined` (#24663) Our CLI's user agent is reported as `undefined/undefined`. This is because we are reading the package name and version from the CLI's `package.json` by using a relative path to the source file (using `__dirname`). However, since a good long while, our production CLI is being bundled using `esbuild` into a single JavaScript file. This means that at runtime, `__dirname` points to a completely different directory than the one it's been coded against, and so reading the `package.json` fails. Account for this by using a function that searches for `package.json`; still do it defensively so that if some other condition we didn't predict causes the search to fail, our CLI doesn't fail. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-cdk/lib/api/aws-auth/sdk-provider.ts | 19 +++++++++++++++--- packages/aws-cdk/lib/util/directories.ts | 20 +++++++++++++++---- .../aws-cdk/test/api/sdk-provider.test.ts | 6 +++++- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts index 567900f6326fc..739bfc1473503 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts @@ -10,6 +10,7 @@ import { cached } from './cached'; import { CredentialPlugins } from './credential-plugins'; import { Mode } from './credentials'; import { ISDK, SDK, isUnrecoverableAwsError } from './sdk'; +import { rootDir } from '../../util/directories'; import { traceMethods } from '../../util/tracing'; @@ -417,9 +418,7 @@ function parseHttpOptions(options: SdkHttpOptions) { let userAgent = options.userAgent; if (userAgent == null) { - // Find the package.json from the main toolkit - const pkg = JSON.parse(readIfPossible(path.join(__dirname, '..', '..', '..', 'package.json')) ?? '{}'); - userAgent = `${pkg.name}/${pkg.version}`; + userAgent = defaultCliUserAgent(); } config.customUserAgent = userAgent; @@ -444,6 +443,20 @@ function parseHttpOptions(options: SdkHttpOptions) { return config; } +/** + * Find the package.json from the main toolkit. + * + * If we can't read it for some reason, try to do something reasonable anyway. + * Fall back to argv[1], or a standard string if that is undefined for some reason. + */ +export function defaultCliUserAgent() { + const root = rootDir(false); + const pkg = JSON.parse((root ? readIfPossible(path.join(root, 'package.json')) : undefined) ?? '{}'); + const name = pkg.name ?? path.basename(process.argv[1] ?? 'cdk-cli'); + const version = pkg.version ?? ''; + return `${name}/${version}`; +} + /** * Find and return a CA certificate bundle path to be passed into the SDK. */ diff --git a/packages/aws-cdk/lib/util/directories.ts b/packages/aws-cdk/lib/util/directories.ts index a2327f5d292bc..20be45f47926f 100644 --- a/packages/aws-cdk/lib/util/directories.ts +++ b/packages/aws-cdk/lib/util/directories.ts @@ -28,15 +28,27 @@ export function cdkCacheDir() { return path.join(cdkHomeDir(), 'cache'); } -export function rootDir() { - - function _rootDir(dirname: string): string { +/** + * From the current file, find the directory that contains the CLI's package.json + * + * Can't use `__dirname` in production code, as the CLI will get bundled as it's + * released and `__dirname` will refer to a different location in the `.ts` form + * as it will in the final executing form. + */ +export function rootDir(): string; +export function rootDir(fail: true): string; +export function rootDir(fail: false): string | undefined; +export function rootDir(fail?: boolean) { + function _rootDir(dirname: string): string | undefined { const manifestPath = path.join(dirname, 'package.json'); if (fs.existsSync(manifestPath)) { return dirname; } if (path.dirname(dirname) === dirname) { - throw new Error('Unable to find package manifest'); + if (fail ?? true) { + throw new Error('Unable to find package manifest'); + } + return undefined; } return _rootDir(path.dirname(dirname)); } diff --git a/packages/aws-cdk/test/api/sdk-provider.test.ts b/packages/aws-cdk/test/api/sdk-provider.test.ts index d66b631686ac2..bd2c19bc6e321 100644 --- a/packages/aws-cdk/test/api/sdk-provider.test.ts +++ b/packages/aws-cdk/test/api/sdk-provider.test.ts @@ -5,7 +5,7 @@ import type { ConfigurationOptions } from 'aws-sdk/lib/config-base'; import * as promptly from 'promptly'; import * as uuid from 'uuid'; import { FakeSts, RegisterRoleOptions, RegisterUserOptions } from './fake-sts'; -import { ISDK, Mode, SDK, SdkProvider } from '../../lib/api/aws-auth'; +import { ISDK, Mode, SDK, SdkProvider, defaultCliUserAgent } from '../../lib/api/aws-auth'; import { PluginHost } from '../../lib/api/plugin'; import * as logging from '../../lib/logging'; import * as bockfs from '../bockfs'; @@ -623,6 +623,10 @@ test('even when using a profile to assume another profile, STS calls goes throug expect(called).toEqual(true); }); +test('default useragent is reasonable', () => { + expect(defaultCliUserAgent()).toContain('aws-cdk/'); +}); + /** * Use object hackery to get the credentials out of the SDK object */