From 7145eae7bd592217aa1d05d38f780ab941bc7e6b Mon Sep 17 00:00:00 2001 From: Amaad Martin Date: Wed, 5 Aug 2026 22:13:45 -0700 Subject: [PATCH 1/2] Fix: map the ADK log level to a legal gcloud verbosity in deploy cloud_run gcloud's --verbosity accepts critical|debug|error|info|none|warning. The ADK CLI accepts debug|info|warn|error. `adk deploy cloud_run --log_level warn` forwarded 'warn' verbatim, so gcloud rejected the argument and the whole deploy failed before it reached GCP. Translate at the single point of use. The generated Dockerfile keeps the raw ADK level, because the ADK server inside the container speaks the ADK vocabulary. --- dev/src/cli/deploy/cli_deploy_cloud_run.ts | 17 +++++- dev/test/cli/cli_deploy_cloud_run_test.ts | 61 ++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/dev/src/cli/deploy/cli_deploy_cloud_run.ts b/dev/src/cli/deploy/cli_deploy_cloud_run.ts index eeec5ddd6..50b5ada4a 100644 --- a/dev/src/cli/deploy/cli_deploy_cloud_run.ts +++ b/dev/src/cli/deploy/cli_deploy_cloud_run.ts @@ -57,6 +57,21 @@ function validateGcloudExtraArgs( } } +// gcloud's --verbosity vocabulary is critical|debug|error|info|none|warning, +// which is not the ADK CLI's debug|info|warn|error. gcloud has no 'warn', so +// forwarding the ADK level verbatim makes gcloud reject the argument and fail +// the whole deploy. +const GCLOUD_VERBOSITY_BY_LOG_LEVEL: Record = { + 'debug': 'debug', + 'info': 'info', + 'warn': 'warning', + 'error': 'error', +}; + +function toGcloudVerbosity(logLevel: string): string { + return GCLOUD_VERBOSITY_BY_LOG_LEVEL[logLevel.toLowerCase()] ?? 'info'; +} + function prepareGCloudArguments(options: DeployToCloudRunOptions): string[] { const regionOptions: string[] = options.region ? ['--region', options.region] @@ -90,7 +105,7 @@ function prepareGCloudArguments(options: DeployToCloudRunOptions): string[] { '--port', options.port.toString(), '--verbosity', - options.logLevel.toLowerCase(), + toGcloudVerbosity(options.logLevel), ]; if (options.a2aAuthToken) { diff --git a/dev/test/cli/cli_deploy_cloud_run_test.ts b/dev/test/cli/cli_deploy_cloud_run_test.ts index e24ff4bc3..d41f4675e 100644 --- a/dev/test/cli/cli_deploy_cloud_run_test.ts +++ b/dev/test/cli/cli_deploy_cloud_run_test.ts @@ -18,6 +18,7 @@ import { isFile, isFolderExists, loadFileData, + saveToFile, tryToFindFileRecursively, } from '../../src/utils/file_utils.js'; @@ -478,6 +479,66 @@ describe('deployToCloudRun', () => { ); }); + // gcloud's full --verbosity vocabulary, as reported by + // `gcloud version --verbosity=bogus`. + const GCLOUD_VERBOSITIES = [ + 'critical', + 'debug', + 'error', + 'info', + 'none', + 'warning', + ]; + + function verbosityArg(): string { + const gcloudArgs = spawnMock.mock.calls[0][1]; + return gcloudArgs[gcloudArgs.indexOf('--verbosity') + 1]; + } + + it('should translate the warn log level to the gcloud warning verbosity', async () => { + await deployToCloudRun({...defaultOptions, logLevel: 'warn'}); + + expect(verbosityArg()).toBe('warning'); + expect(spawnMock.mock.calls[0][1]).not.toContain('warn'); + }); + + it.each(['debug', 'info', 'error'])( + 'should pass the %s log level through to gcloud unchanged', + async (logLevel) => { + await deployToCloudRun({...defaultOptions, logLevel}); + + expect(verbosityArg()).toBe(logLevel); + }, + ); + + it('should accept an upper-case log level', async () => { + await deployToCloudRun({...defaultOptions, logLevel: 'WARN'}); + + expect(verbosityArg()).toBe('warning'); + }); + + it('should fall back to the info verbosity for a level gcloud does not accept', async () => { + await deployToCloudRun({...defaultOptions, logLevel: 'trace'}); + + expect(verbosityArg()).toBe('info'); + expect(GCLOUD_VERBOSITIES).toContain(verbosityArg()); + }); + + it('should keep the raw ADK log level in the generated Dockerfile', async () => { + // The containerized ADK server speaks the ADK vocabulary, so it must keep + // receiving 'warn' and never the gcloud spelling. + await deployToCloudRun({...defaultOptions, logLevel: 'warn'}); + + // createPackageJson also writes through saveToFile, so select by path. + const dockerFileCall = vi + .mocked(saveToFile) + .mock.calls.find(([filePath]) => filePath.endsWith('Dockerfile')); + if (!dockerFileCall) { + expect.fail('deployToCloudRun did not write a Dockerfile'); + } + expect(dockerFileCall[1]).toContain("--log_level='warn'"); + }); + it('should handle spawn failures', async () => { const consoleErrorSpy = vi.spyOn(console, 'error'); spawnMock.mockReturnValue({ From 0aff1d18dbeff9c62465c05b07951a773ea810d1 Mon Sep 17 00:00:00 2001 From: Amaad Martin Date: Wed, 5 Aug 2026 22:33:12 -0700 Subject: [PATCH 2/2] Simplify: inline the verbosity lookup and drop a tautological assertion The toGcloudVerbosity wrapper had one caller and wrapped a constant declared directly above it. Inlining the lookup at the call site matches the shape cli.ts:47 already uses for the same problem. The fallback test asserted the result was a member of a hardcoded list that contained the literal the preceding line already pinned, so the assertion could not fail. The toBe('info') assertion above it pins the fallback on its own. --- dev/src/cli/deploy/cli_deploy_cloud_run.ts | 6 +----- dev/test/cli/cli_deploy_cloud_run_test.ts | 12 ------------ 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/dev/src/cli/deploy/cli_deploy_cloud_run.ts b/dev/src/cli/deploy/cli_deploy_cloud_run.ts index 50b5ada4a..27cf2b404 100644 --- a/dev/src/cli/deploy/cli_deploy_cloud_run.ts +++ b/dev/src/cli/deploy/cli_deploy_cloud_run.ts @@ -68,10 +68,6 @@ const GCLOUD_VERBOSITY_BY_LOG_LEVEL: Record = { 'error': 'error', }; -function toGcloudVerbosity(logLevel: string): string { - return GCLOUD_VERBOSITY_BY_LOG_LEVEL[logLevel.toLowerCase()] ?? 'info'; -} - function prepareGCloudArguments(options: DeployToCloudRunOptions): string[] { const regionOptions: string[] = options.region ? ['--region', options.region] @@ -105,7 +101,7 @@ function prepareGCloudArguments(options: DeployToCloudRunOptions): string[] { '--port', options.port.toString(), '--verbosity', - toGcloudVerbosity(options.logLevel), + GCLOUD_VERBOSITY_BY_LOG_LEVEL[options.logLevel.toLowerCase()] ?? 'info', ]; if (options.a2aAuthToken) { diff --git a/dev/test/cli/cli_deploy_cloud_run_test.ts b/dev/test/cli/cli_deploy_cloud_run_test.ts index d41f4675e..81c9fe673 100644 --- a/dev/test/cli/cli_deploy_cloud_run_test.ts +++ b/dev/test/cli/cli_deploy_cloud_run_test.ts @@ -479,17 +479,6 @@ describe('deployToCloudRun', () => { ); }); - // gcloud's full --verbosity vocabulary, as reported by - // `gcloud version --verbosity=bogus`. - const GCLOUD_VERBOSITIES = [ - 'critical', - 'debug', - 'error', - 'info', - 'none', - 'warning', - ]; - function verbosityArg(): string { const gcloudArgs = spawnMock.mock.calls[0][1]; return gcloudArgs[gcloudArgs.indexOf('--verbosity') + 1]; @@ -521,7 +510,6 @@ describe('deployToCloudRun', () => { await deployToCloudRun({...defaultOptions, logLevel: 'trace'}); expect(verbosityArg()).toBe('info'); - expect(GCLOUD_VERBOSITIES).toContain(verbosityArg()); }); it('should keep the raw ADK log level in the generated Dockerfile', async () => {