Skip to content

Commit ff7de2b

Browse files
committed
supply a valid $.Source in local execution's runtime context
@datadog/apps-backend's buildRuntimeFromJsFunctionWithActions is injected into every backend function's bundle when that package is installed, regardless of which specific function is called, and requires $.Source.{initiator,runAsUser} to each be a User object with non-empty id/orgId strings. Local execution's $ context never included Source, so any app with @datadog/apps-backend installed (the create-apps scaffold default) failed on its very first request -- not just functions that call $.Actions, which was the only gap previously documented. Confirmed by tracing the real, published package's bundled validation logic and dd-source's production path: Source is populated upstream of wf-actions-worker (the app-builder API layer, from the authenticated caller's identity) before a bundled function ever runs -- it's plain caller-supplied JSON by the time it reaches the script template, not a server-signed value the runtime cross-checks. The validator only checks shape (non-empty id/orgId strings), not a live session, so a synthetic identity fully satisfies it. Uses one placeholder identity for both initiator and runAsUser, since on-behalf-of impersonation isn't meaningful when a single developer is running their own code locally. No existing whoami-style endpoint exists in this package's auth layer to fetch a real identity instead; real identity resolution (if ever wanted) is a separate, non-blocking enhancement, not a gap in this fix. Verified against the real npm package (not a mock) via a new integration test reusing the existing apps_backend_project fixture, and against a real npm-linked scaffolded app calling getExecutionUser()/ getInitiatingUser() through /__dd/executeAction.
1 parent 2a864e8 commit ff7de2b

2 files changed

Lines changed: 65 additions & 2 deletions

File tree

packages/plugins/apps/src/vite/local-exec-child.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,33 @@ function makeActionsProxy(pathParts = []) {
6868
});
6969
}
7070

71+
// @datadog/apps-backend's buildRuntimeFromJsFunctionWithActions (injected into
72+
// every bundle when that package is installed, regardless of which specific
73+
// function is called -- see virtual-entry.ts's SET_BACKEND_CONTEXT_SNIPPET)
74+
// requires $.Source.{initiator,runAsUser} to each be a User object with
75+
// non-empty id/orgId strings. In production this comes from the real
76+
// workflow-execution's trigger/run-as identity (domains/workflow's Go proto
77+
// data); no equivalent identity exists for a local fork, and no impersonation
78+
// (initiator vs. runAsUser) is meaningful when one developer is running their
79+
// own code locally, so both roles use the same clearly-synthetic identity.
80+
const LOCAL_DEV_USER = {
81+
id: 'local-dev-user',
82+
orgId: 'local-dev-org',
83+
email: null,
84+
name: 'Local Development',
85+
};
86+
7187
process.on('message', async function onExecute(msg) {
7288
if (!msg || msg.type !== 'execute') {
7389
return;
7490
}
7591

7692
try {
77-
const $ = { backendFunctionArgs: msg.backendFunctionArgs, Actions: makeActionsProxy() };
93+
const $ = {
94+
backendFunctionArgs: msg.backendFunctionArgs,
95+
Actions: makeActionsProxy(),
96+
Source: { initiator: LOCAL_DEV_USER, runAsUser: LOCAL_DEV_USER },
97+
};
7898
globalThis.$ = $;
7999

80100
// The real bundled code (from vite.build(), format:'es', no externals

packages/plugins/apps/src/vite/local-execution.integration.test.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
import { outputFileSync } from '@dd/core/helpers/fs';
18-
import { getTempWorkingDir } from '@dd/tests/_jest/helpers/env';
18+
import { getTempWorkingDir, prepareWorkingDir } from '@dd/tests/_jest/helpers/env';
1919
import { getMockLogger } from '@dd/tests/_jest/helpers/mocks';
2020
import { build } from 'vite';
2121

@@ -247,4 +247,47 @@ describe('executeScriptLocally (real bundle, no mocks)', () => {
247247
await expect(execution).rejects.toThrow();
248248
expect(child?.killed).toBe(true);
249249
}, 20_000);
250+
251+
test('supplies a valid $.Source so @datadog/apps-backend does not throw, when the package is installed', async () => {
252+
// Unlike the other tests in this file (a bare temp dir with only the
253+
// one .backend.ts file written into it), this uses the real fixture
254+
// project tree, which has a real @datadog/apps-backend installed --
255+
// matching a real scaffolded app, and the exact condition that
256+
// exposed this bug: isDatadogAppsBackendInstalled(workingDir) resolves
257+
// true here, so generateDevVirtualEntryContent injects
258+
// SET_BACKEND_CONTEXT_SNIPPET, which throws unless $.Source is valid.
259+
const workingDir = await prepareWorkingDir(`local-exec-poc-apps-backend-${Date.now()}`);
260+
const sourceCode = `
261+
import { getExecutionUser, getInitiatingUser } from '@datadog/apps-backend/user';
262+
export async function usesSdk() {
263+
const [executionUser, initiatingUser] = await Promise.all([
264+
getExecutionUser(),
265+
getInitiatingUser(),
266+
]);
267+
return { executionUser, initiatingUser };
268+
}
269+
`;
270+
const code = await bundleRealBackendFunction(workingDir, 'usesSdk', sourceCode);
271+
272+
// Confirms the bundle actually took the @datadog/apps-backend branch
273+
// (the bug this test guards would otherwise be silently untested if
274+
// the fixture stopped resolving as installed for some other reason).
275+
// Asserts on the snippet's literal comment, not the imported
276+
// identifiers -- Rollup renames those during bundling.
277+
expect(code).toContain('Supply the backend runtime context');
278+
279+
const func: BackendFunction = {
280+
relativePath: 'src/usesSdk',
281+
name: 'usesSdk',
282+
absolutePath: `${workingDir}/src/usesSdk.backend.ts`,
283+
allowedConnectionIds: [],
284+
};
285+
286+
const outputs = await executeScriptLocally(code, func, [], log);
287+
288+
expect(outputs.data).toMatchObject({
289+
executionUser: { id: 'local-dev-user', orgId: 'local-dev-org' },
290+
initiatingUser: { id: 'local-dev-user', orgId: 'local-dev-org' },
291+
});
292+
}, 20_000);
250293
});

0 commit comments

Comments
 (0)