Skip to content

Commit 7b9a9d2

Browse files
ShadowCat567Vieltojarvigithub-advanced-security[bot]Amplifiyer
authored
Seed (#2546)
* seed setup and seed secrets * skeleton of sandbox seed command * seed policy generation * grants for storage * changed seed policy name to amplifySeedPolicy * test for secrets * removed managed seed policy * set secret and seed command * generate seed policy command * auth APIs - incomplete * created folders, getting data from generateConfig * removed static, changed name of AuthOutputsReader to ConfigReader * fixed seed commands, figured out callback for TOTP * conditional typing * mfa updates and seed command test * adjustments and tests added * update to user group type * changeset * update api * unit tests * adjust MFA type * remove edit to construct container * removed unnecessary changeset * fixed some tests * fix dependencies tests * adjust seed command test * fix dep inconsistencies * updated challenge response to use secretValue instead of input * PR feedback * more PR feedback * even more PR feedback * testing something * reverted * e2e tests * e2e-tests * revert some package-lock changes * e2e tests * removed user check from seed test * remove describeUserpoolCommand * update test * sts package-lock update * e2e tests * fix package versions * fixing permissions * import crypto * appeasing json * fixing json * cleaned up e2e file * crpyto polyfill in e2e test seed script * added context * fix dependency issues * added spinner and success message to seed command * updated success message * lint fixes from prettier update * updated some tests * adjustments * fixes after prettier sandbox merge * small api adjustment * seed version bump changed to major * reverted some package changes * PR feedback * make codeQL happy * Potential fix for code scanning alert no. 327: Insecure randomness Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * e2e tests did not like readFile, using import * fixed seed e2e tests * removed outputs import * Update packages/cli/src/commands/sandbox/sandbox-seed/sandbox_seed_command.ts Co-authored-by: Amplifiyer <[email protected]> * fixed tests --------- Co-authored-by: Vieltojarvi <[email protected]> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Amplifiyer <[email protected]>
1 parent a8a9430 commit 7b9a9d2

38 files changed

+2865
-45
lines changed

.changeset/little-kids-double.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@aws-amplify/seed': major
3+
'@aws-amplify/backend-cli': minor
4+
---
5+
6+
adding seed feature

package-lock.json

Lines changed: 24 additions & 40 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"@aws-sdk/client-amplify": "^3.750.0",
4747
"@aws-sdk/client-cloudformation": "^3.750.0",
4848
"@aws-sdk/client-s3": "^3.750.0",
49+
"@aws-sdk/client-sts": "^3.750.0",
4950
"@aws-sdk/credential-provider-ini": "^3.750.0",
5051
"@aws-sdk/credential-providers": "^3.750.0",
5152
"@aws-sdk/region-config-resolver": "^3.734.0",
@@ -61,7 +62,8 @@
6162
"zod": "^3.22.2"
6263
},
6364
"peerDependencies": {
64-
"@aws-sdk/types": "^3.734.0"
65+
"@aws-sdk/types": "^3.734.0",
66+
"aws-cdk-lib": "^2.180.0"
6567
},
6668
"devDependencies": {
6769
"@types/envinfo": "^7.8.3",
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { after, before, describe, it, mock } from 'node:test';
2+
import fsp from 'fs/promises';
3+
import * as path from 'path';
4+
import { SandboxSeedCommand } from './sandbox_seed_command.js';
5+
import yargs, { CommandModule } from 'yargs';
6+
import {
7+
TestCommandError,
8+
TestCommandRunner,
9+
} from '../../../test-utils/command_runner.js';
10+
import { createSandboxSecretCommand } from '../sandbox-secret/sandbox_secret_command_factory.js';
11+
import { EventHandler, SandboxCommand } from '../sandbox_command.js';
12+
import { SandboxSingletonFactory } from '@aws-amplify/sandbox';
13+
import { SandboxDeleteCommand } from '../sandbox-delete/sandbox_delete_command.js';
14+
import { ClientConfigGeneratorAdapter } from '../../../client-config/client_config_generator_adapter.js';
15+
import { format, printer } from '@aws-amplify/cli-core';
16+
import { CommandMiddleware } from '../../../command_middleware.js';
17+
import { SandboxSeedGeneratePolicyCommand } from './sandbox_seed_policy_command.js';
18+
import assert from 'node:assert';
19+
import { BackendIdentifier } from '@aws-amplify/plugin-types';
20+
import { SandboxBackendIdResolver } from '../sandbox_id_resolver.js';
21+
22+
const seedFileContents = 'console.log(`seed has been run`);';
23+
24+
const testBackendNameSpace = 'testSandboxId';
25+
const testSandboxName = 'testSandboxName';
26+
27+
const testBackendId: BackendIdentifier = {
28+
namespace: testBackendNameSpace,
29+
name: testSandboxName,
30+
type: 'sandbox',
31+
};
32+
33+
void describe('sandbox seed command', () => {
34+
let commandRunner: TestCommandRunner;
35+
36+
const clientConfigGenerationMock = mock.fn<EventHandler>();
37+
const clientConfigDeletionMock = mock.fn<EventHandler>();
38+
39+
const clientConfigGeneratorAdapterMock = {
40+
generateClientConfigToFile: clientConfigGenerationMock,
41+
} as unknown as ClientConfigGeneratorAdapter;
42+
43+
const commandMiddleware = new CommandMiddleware(printer);
44+
const mockHandleProfile = mock.method(
45+
commandMiddleware,
46+
'ensureAwsCredentialAndRegion',
47+
() => null,
48+
);
49+
50+
const mockProfileResolver = mock.fn();
51+
52+
let amplifySeedDir: string;
53+
let fullPath: string;
54+
55+
const sandboxIdResolver: SandboxBackendIdResolver = {
56+
resolve: () => Promise.resolve(testBackendId),
57+
} as SandboxBackendIdResolver;
58+
59+
before(async () => {
60+
const sandboxFactory = new SandboxSingletonFactory(
61+
() => Promise.resolve(testBackendId),
62+
mockProfileResolver,
63+
printer,
64+
format,
65+
);
66+
67+
const sandboxSeedCommand = new SandboxSeedCommand(sandboxIdResolver, [
68+
new SandboxSeedGeneratePolicyCommand(sandboxIdResolver),
69+
]);
70+
71+
const sandboxCommand = new SandboxCommand(
72+
sandboxFactory,
73+
[
74+
new SandboxDeleteCommand(sandboxFactory),
75+
createSandboxSecretCommand(),
76+
sandboxSeedCommand,
77+
],
78+
clientConfigGeneratorAdapterMock,
79+
commandMiddleware,
80+
() => ({
81+
successfulDeployment: [clientConfigGenerationMock],
82+
successfulDeletion: [clientConfigDeletionMock],
83+
failedDeployment: [],
84+
}),
85+
);
86+
const parser = yargs().command(sandboxCommand as unknown as CommandModule);
87+
commandRunner = new TestCommandRunner(parser);
88+
mockHandleProfile.mock.resetCalls();
89+
});
90+
91+
void describe('seed script exists', () => {
92+
before(async () => {
93+
await fsp.mkdir(path.join(process.cwd(), 'amplify', 'seed'), {
94+
recursive: true,
95+
});
96+
amplifySeedDir = path.join(process.cwd(), 'amplify');
97+
fullPath = path.join(process.cwd(), 'amplify', 'seed', 'seed.ts');
98+
await fsp.writeFile(fullPath, seedFileContents, 'utf8');
99+
});
100+
101+
after(async () => {
102+
await fsp.rm(amplifySeedDir, { recursive: true, force: true });
103+
if (process.env.AMPLIFY_BACKEND_IDENTIFIER) {
104+
delete process.env.AMPLIFY_BACKEND_IDENTIFIER;
105+
}
106+
});
107+
108+
void it('runs seed if seed script is found', async () => {
109+
const output = await commandRunner.runCommand('sandbox seed');
110+
111+
const successMessage = output.trimEnd().split('\n')[2].trimStart();
112+
113+
assert.ok(output !== undefined);
114+
assert.deepStrictEqual(
115+
successMessage,
116+
'✔ seed has successfully completed',
117+
);
118+
assert.strictEqual(mockHandleProfile.mock.callCount(), 1);
119+
});
120+
});
121+
122+
void describe('seed script does not exist', () => {
123+
before(async () => {
124+
await fsp.mkdir(path.join(process.cwd(), 'amplify', 'seed'), {
125+
recursive: true,
126+
});
127+
amplifySeedDir = path.join(process.cwd(), 'amplify');
128+
});
129+
130+
after(async () => {
131+
await fsp.rm(amplifySeedDir, { recursive: true, force: true });
132+
if (process.env.AMPLIFY_BACKEND_IDENTIFIER) {
133+
delete process.env.AMPLIFY_BACKEND_IDENTIFIER;
134+
}
135+
});
136+
137+
void it('throws error if seed script does not exist', async () => {
138+
await assert.rejects(
139+
() => commandRunner.runCommand('sandbox seed'),
140+
(err: TestCommandError) => {
141+
assert.match(err.output, /SeedScriptNotFoundError/);
142+
assert.match(err.output, /There is no file that corresponds to/);
143+
assert.match(
144+
err.output,
145+
/Please make a file that corresponds to (.*) and put your seed logic in it/,
146+
);
147+
return true;
148+
},
149+
);
150+
});
151+
});
152+
});
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { Argv, CommandModule } from 'yargs';
2+
import path from 'path';
3+
import { existsSync } from 'fs';
4+
import { execa } from 'execa';
5+
import { SandboxBackendIdResolver } from '../sandbox_id_resolver.js';
6+
import { AmplifyUserError } from '@aws-amplify/platform-core';
7+
import { SandboxCommandGlobalOptions } from '../option_types.js';
8+
import { format, printer } from '@aws-amplify/cli-core';
9+
10+
/**
11+
* Command that runs seed in sandbox environment
12+
*/
13+
export class SandboxSeedCommand implements CommandModule<object> {
14+
/**
15+
* @inheritDoc
16+
*/
17+
readonly command: string;
18+
19+
/**
20+
* @inheritDoc
21+
*/
22+
readonly describe: string;
23+
24+
/**
25+
* Seeds sandbox environment.
26+
*/
27+
constructor(
28+
private readonly backendIDResolver: SandboxBackendIdResolver,
29+
private readonly seedSubCommands: CommandModule[],
30+
) {
31+
this.command = 'seed';
32+
this.describe = 'Seeds sandbox environment';
33+
}
34+
35+
/**
36+
* @inheritDoc
37+
*/
38+
handler = async (): Promise<void> => {
39+
printer.startSpinner('Running seed...');
40+
const backendID = await this.backendIDResolver.resolve();
41+
const seedPath = path.join('amplify', 'seed', 'seed.ts');
42+
try {
43+
await execa('tsx', [seedPath], {
44+
cwd: process.cwd(),
45+
stdio: 'inherit',
46+
env: {
47+
AMPLIFY_BACKEND_IDENTIFIER: JSON.stringify(backendID),
48+
},
49+
});
50+
} finally {
51+
printer.stopSpinner();
52+
}
53+
printer.printNewLine();
54+
printer.print(`${format.success('✔')} seed has successfully completed`);
55+
};
56+
57+
/**
58+
* @inheritDoc
59+
*/
60+
builder = (yargs: Argv): Argv<SandboxCommandGlobalOptions> => {
61+
return yargs.command(this.seedSubCommands).check(() => {
62+
const seedPath = path.join(process.cwd(), 'amplify', 'seed', 'seed.ts');
63+
if (!existsSync(seedPath)) {
64+
throw new AmplifyUserError('SeedScriptNotFoundError', {
65+
message: `There is no file that corresponds to ${seedPath}`,
66+
resolution: `Please make a file that corresponds to ${seedPath} and put your seed logic in it`,
67+
});
68+
}
69+
return true;
70+
});
71+
};
72+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Argv, CommandModule } from 'yargs';
2+
import { printer } from '@aws-amplify/cli-core';
3+
import { SandboxBackendIdResolver } from '../sandbox_id_resolver.js';
4+
import { generateSeedPolicyTemplate } from '../../../seed-policy-generation/generate_seed_policy_template.js';
5+
6+
/**
7+
* Command that generates policy template with permissions to be able to run seed in sandbox environment
8+
*/
9+
export class SandboxSeedGeneratePolicyCommand implements CommandModule<object> {
10+
/**
11+
* @inheritDoc
12+
*/
13+
readonly command: string;
14+
15+
/**
16+
* @inheritDoc
17+
*/
18+
readonly describe: string;
19+
20+
/**
21+
* Generates policy to run seed, is a subcommand of seed
22+
*/
23+
constructor(private readonly backendIdResolver: SandboxBackendIdResolver) {
24+
this.command = 'generate-policy';
25+
this.describe = 'Generates policy for seeding';
26+
}
27+
28+
/**
29+
* @inheritDoc
30+
*/
31+
handler = async (): Promise<void> => {
32+
const backendId = await this.backendIdResolver.resolve();
33+
const policyDocument = await generateSeedPolicyTemplate(backendId);
34+
printer.print(JSON.stringify(policyDocument.toJSON(), null, 2));
35+
};
36+
37+
/**
38+
* @inheritDoc
39+
*/
40+
builder = (yargs: Argv) => {
41+
return yargs;
42+
};
43+
}

packages/cli/src/commands/sandbox/sandbox_command_factory.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
} from './sandbox_command.js';
77
import { SandboxSingletonFactory } from '@aws-amplify/sandbox';
88
import { SandboxDeleteCommand } from './sandbox-delete/sandbox_delete_command.js';
9+
import { SandboxSeedCommand } from './sandbox-seed/sandbox_seed_command.js';
910
import { SandboxBackendIdResolver } from './sandbox_id_resolver.js';
1011
import { ClientConfigGeneratorAdapter } from '../../client-config/client_config_generator_adapter.js';
1112
import { LocalNamespaceResolver } from '../../backend-identifier/local_namespace_resolver.js';
@@ -25,6 +26,7 @@ import { S3Client } from '@aws-sdk/client-s3';
2526
import { AmplifyClient } from '@aws-sdk/client-amplify';
2627
import { CloudFormationClient } from '@aws-sdk/client-cloudformation';
2728
import { NoticesRenderer } from '../../notices/notices_renderer.js';
29+
import { SandboxSeedGeneratePolicyCommand } from './sandbox-seed/sandbox_seed_policy_command.js';
2830
import { SDKProfileResolverProvider } from '../../sdk_profile_resolver_provider.js';
2931

3032
/**
@@ -78,7 +80,13 @@ export const createSandboxCommand = (
7880
const commandMiddleWare = new CommandMiddleware(printer);
7981
return new SandboxCommand(
8082
sandboxFactory,
81-
[new SandboxDeleteCommand(sandboxFactory), createSandboxSecretCommand()],
83+
[
84+
new SandboxDeleteCommand(sandboxFactory),
85+
createSandboxSecretCommand(),
86+
new SandboxSeedCommand(sandboxBackendIdPartsResolver, [
87+
new SandboxSeedGeneratePolicyCommand(sandboxBackendIdPartsResolver),
88+
]),
89+
],
8290
clientConfigGeneratorAdapter,
8391
commandMiddleWare,
8492
eventHandlerFactory.getSandboxEventHandlers,

0 commit comments

Comments
 (0)