@action-llama/action-llama 0.8.2 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/cli/commands/cloud-deploy.d.ts +15 -0
  2. package/dist/cli/commands/cloud-deploy.d.ts.map +1 -0
  3. package/dist/cli/commands/cloud-deploy.js +136 -0
  4. package/dist/cli/commands/cloud-deploy.js.map +1 -0
  5. package/dist/cli/commands/cloud-setup.d.ts.map +1 -1
  6. package/dist/cli/commands/cloud-setup.js +298 -3
  7. package/dist/cli/commands/cloud-setup.js.map +1 -1
  8. package/dist/cli/commands/cloud-teardown.js +12 -2
  9. package/dist/cli/commands/cloud-teardown.js.map +1 -1
  10. package/dist/cli/commands/logs.d.ts.map +1 -1
  11. package/dist/cli/commands/logs.js +31 -0
  12. package/dist/cli/commands/logs.js.map +1 -1
  13. package/dist/cli/commands/status.d.ts.map +1 -1
  14. package/dist/cli/commands/status.js +25 -0
  15. package/dist/cli/commands/status.js.map +1 -1
  16. package/dist/cli/main.js +8 -0
  17. package/dist/cli/main.js.map +1 -1
  18. package/dist/cloud/deploy-apprunner.d.ts +37 -0
  19. package/dist/cloud/deploy-apprunner.d.ts.map +1 -0
  20. package/dist/cloud/deploy-apprunner.js +201 -0
  21. package/dist/cloud/deploy-apprunner.js.map +1 -0
  22. package/dist/cloud/deploy-cloudrun.d.ts +36 -0
  23. package/dist/cloud/deploy-cloudrun.d.ts.map +1 -0
  24. package/dist/cloud/deploy-cloudrun.js +154 -0
  25. package/dist/cloud/deploy-cloudrun.js.map +1 -0
  26. package/dist/cloud/image-builder.d.ts +35 -0
  27. package/dist/cloud/image-builder.d.ts.map +1 -0
  28. package/dist/cloud/image-builder.js +111 -0
  29. package/dist/cloud/image-builder.js.map +1 -0
  30. package/dist/cloud/scheduler-image.d.ts +27 -0
  31. package/dist/cloud/scheduler-image.d.ts.map +1 -0
  32. package/dist/cloud/scheduler-image.js +127 -0
  33. package/dist/cloud/scheduler-image.js.map +1 -0
  34. package/dist/docker/aws-shared.d.ts.map +1 -1
  35. package/dist/docker/aws-shared.js +13 -8
  36. package/dist/docker/aws-shared.js.map +1 -1
  37. package/dist/docker/ecs-runtime.d.ts +1 -1
  38. package/dist/docker/ecs-runtime.d.ts.map +1 -1
  39. package/dist/docker/ecs-runtime.js +2 -2
  40. package/dist/docker/ecs-runtime.js.map +1 -1
  41. package/dist/docker/local-runtime.d.ts.map +1 -1
  42. package/dist/docker/local-runtime.js +4 -2
  43. package/dist/docker/local-runtime.js.map +1 -1
  44. package/dist/scheduler/index.d.ts.map +1 -1
  45. package/dist/scheduler/index.js +22 -107
  46. package/dist/scheduler/index.js.map +1 -1
  47. package/dist/shared/aws-constants.d.ts +12 -0
  48. package/dist/shared/aws-constants.d.ts.map +1 -1
  49. package/dist/shared/aws-constants.js +12 -0
  50. package/dist/shared/aws-constants.js.map +1 -1
  51. package/dist/shared/config.d.ts +4 -0
  52. package/dist/shared/config.d.ts.map +1 -1
  53. package/dist/shared/config.js.map +1 -1
  54. package/package.json +2 -1
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `al cloud deploy` — Build and deploy the scheduler + agents to the cloud.
3
+ *
4
+ * Steps:
5
+ * 1. Validate config (must have [cloud] section)
6
+ * 2. Run doctor -c to ensure creds are pushed + IAM is set up
7
+ * 3. Create cloud runtime + build all agent images
8
+ * 4. Build scheduler image
9
+ * 5. Deploy scheduler service (App Runner or Cloud Run)
10
+ * 6. Print deployed URL
11
+ */
12
+ export declare function execute(opts: {
13
+ project: string;
14
+ }): Promise<void>;
15
+ //# sourceMappingURL=cloud-deploy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud-deploy.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/cloud-deploy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAWH,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA8FtE"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * `al cloud deploy` — Build and deploy the scheduler + agents to the cloud.
3
+ *
4
+ * Steps:
5
+ * 1. Validate config (must have [cloud] section)
6
+ * 2. Run doctor -c to ensure creds are pushed + IAM is set up
7
+ * 3. Create cloud runtime + build all agent images
8
+ * 4. Build scheduler image
9
+ * 5. Deploy scheduler service (App Runner or Cloud Run)
10
+ * 6. Print deployed URL
11
+ */
12
+ import { resolve } from "path";
13
+ import { loadGlobalConfig, discoverAgents, loadAgentConfig, validateAgentConfig } from "../../shared/config.js";
14
+ import { execute as runDoctor } from "./doctor.js";
15
+ import { buildAllImages } from "../../cloud/image-builder.js";
16
+ import { buildSchedulerImage } from "../../cloud/scheduler-image.js";
17
+ import { createLogger } from "../../shared/logger.js";
18
+ export async function execute(opts) {
19
+ const projectPath = resolve(opts.project);
20
+ const globalConfig = loadGlobalConfig(projectPath);
21
+ const cloud = globalConfig.cloud;
22
+ if (!cloud) {
23
+ throw new Error("No [cloud] section found in config.toml. Run 'al cloud setup' first.");
24
+ }
25
+ const logger = createLogger(projectPath, "deploy");
26
+ console.log(`\n=== Cloud Deploy (${cloud.provider}) ===\n`);
27
+ // 1. Run doctor -c to push credentials and reconcile IAM
28
+ console.log("Step 1: Validating credentials and IAM...");
29
+ await runDoctor({ project: opts.project, cloud: true, checkOnly: true });
30
+ console.log("");
31
+ // 2. Set up cloud credential backend
32
+ const { setDefaultBackend } = await import("../../shared/credentials.js");
33
+ const { createBackendFromCloudConfig } = await import("../../shared/remote.js");
34
+ const backend = await createBackendFromCloudConfig(cloud);
35
+ setDefaultBackend(backend);
36
+ // 3. Discover agents and create runtime
37
+ const agentNames = discoverAgents(projectPath);
38
+ if (agentNames.length === 0) {
39
+ throw new Error("No agents found. Create agents first.");
40
+ }
41
+ const agentConfigs = agentNames.map((name) => loadAgentConfig(projectPath, name));
42
+ for (const config of agentConfigs) {
43
+ validateAgentConfig(config);
44
+ }
45
+ const activeAgentConfigs = agentConfigs.filter((a) => (a.scale ?? 1) > 0);
46
+ const runtime = await createCloudRuntime(cloud);
47
+ // 4. Build agent images
48
+ console.log("Step 2: Building agent images...");
49
+ const lastProgress = new Map();
50
+ await buildAllImages({
51
+ projectPath,
52
+ globalConfig,
53
+ activeAgentConfigs,
54
+ runtime,
55
+ runtimeType: cloud.provider,
56
+ logger,
57
+ skills: { locking: true },
58
+ onProgress: (label, msg) => {
59
+ // Avoid repeating the same message for the same label
60
+ if (lastProgress.get(label) !== msg) {
61
+ lastProgress.set(label, msg);
62
+ console.log(` [${label}] ${msg}`);
63
+ }
64
+ },
65
+ });
66
+ console.log("Agent images built and pushed.\n");
67
+ // 5. Build scheduler image
68
+ console.log("Step 3: Building scheduler image...");
69
+ const schedulerImageUri = await buildSchedulerImage({
70
+ projectPath,
71
+ globalConfig,
72
+ runtime,
73
+ logger,
74
+ onProgress: (msg) => console.log(` ${msg}`),
75
+ });
76
+ console.log(`Scheduler image: ${schedulerImageUri}\n`);
77
+ // 6. Deploy scheduler service
78
+ console.log("Step 4: Deploying scheduler service...");
79
+ const serviceInfo = await deploySchedulerService(cloud, schedulerImageUri);
80
+ console.log(`\nScheduler deployed successfully!`);
81
+ console.log(` URL: ${serviceInfo.serviceUrl}`);
82
+ console.log(` Status: ${serviceInfo.status}`);
83
+ // Print webhook URLs
84
+ const webhookSources = globalConfig.webhooks ?? {};
85
+ const providerTypes = new Set(agentConfigs.flatMap((a) => a.webhooks?.map((t) => webhookSources[t.source]?.type).filter(Boolean) || []));
86
+ if (providerTypes.size > 0) {
87
+ console.log("\nWebhook endpoints:");
88
+ for (const pt of providerTypes) {
89
+ console.log(` ${pt}: ${serviceInfo.serviceUrl}/webhooks/${pt}`);
90
+ }
91
+ }
92
+ console.log("");
93
+ }
94
+ async function createCloudRuntime(cloud) {
95
+ if (cloud.provider === "cloud-run") {
96
+ const { CloudRunJobRuntime } = await import("../../docker/cloud-run-runtime.js");
97
+ const { gcpProject, region, artifactRegistry, serviceAccount, secretPrefix } = cloud;
98
+ if (!gcpProject || !region || !artifactRegistry || !serviceAccount) {
99
+ throw new Error("Cloud Run deployment requires cloud.gcpProject, cloud.region, " +
100
+ "cloud.artifactRegistry, and cloud.serviceAccount in config.toml");
101
+ }
102
+ return new CloudRunJobRuntime({ gcpProject, region, artifactRegistry, serviceAccount, secretPrefix });
103
+ }
104
+ if (cloud.provider === "ecs") {
105
+ const { ECSFargateRuntime } = await import("../../docker/ecs-runtime.js");
106
+ const cc = cloud;
107
+ if (!cc.awsRegion || !cc.ecsCluster || !cc.ecrRepository || !cc.executionRoleArn || !cc.taskRoleArn || !cc.subnets?.length) {
108
+ throw new Error("ECS deployment requires cloud.awsRegion, cloud.ecsCluster, cloud.ecrRepository, " +
109
+ "cloud.executionRoleArn, cloud.taskRoleArn, and cloud.subnets in config.toml");
110
+ }
111
+ return new ECSFargateRuntime({
112
+ awsRegion: cc.awsRegion,
113
+ ecsCluster: cc.ecsCluster,
114
+ ecrRepository: cc.ecrRepository,
115
+ executionRoleArn: cc.executionRoleArn,
116
+ taskRoleArn: cc.taskRoleArn,
117
+ subnets: cc.subnets,
118
+ securityGroups: cc.securityGroups,
119
+ secretPrefix: cc.awsSecretPrefix,
120
+ buildBucket: cc.buildBucket,
121
+ });
122
+ }
123
+ throw new Error(`Unknown cloud provider: "${cloud.provider}"`);
124
+ }
125
+ async function deploySchedulerService(cloud, imageUri) {
126
+ if (cloud.provider === "ecs") {
127
+ const { deployAppRunner } = await import("../../cloud/deploy-apprunner.js");
128
+ return await deployAppRunner({ imageUri, cloudConfig: cloud });
129
+ }
130
+ if (cloud.provider === "cloud-run") {
131
+ const { deployCloudRun } = await import("../../cloud/deploy-cloudrun.js");
132
+ return await deployCloudRun({ imageUri, cloudConfig: cloud });
133
+ }
134
+ throw new Error(`Unknown cloud provider: "${cloud.provider}"`);
135
+ }
136
+ //# sourceMappingURL=cloud-deploy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud-deploy.js","sourceRoot":"","sources":["../../../src/cli/commands/cloud-deploy.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"cloud-setup.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/cloud-setup.ts"],"names":[],"mappings":"AA+CA,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA0GtE"}
1
+ {"version":3,"file":"cloud-setup.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/cloud-setup.ts"],"names":[],"mappings":"AAgDA,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA0GtE"}
@@ -8,7 +8,7 @@ import { teardownCloud } from "./cloud-teardown.js";
8
8
  import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
9
9
  import { ECRClient, DescribeRepositoriesCommand, CreateRepositoryCommand, SetRepositoryPolicyCommand, } from "@aws-sdk/client-ecr";
10
10
  import { ECSClient, ListClustersCommand, DescribeClustersCommand, CreateClusterCommand, } from "@aws-sdk/client-ecs";
11
- import { IAMClient, ListRolesCommand, CreateRoleCommand, GetRoleCommand, AttachRolePolicyCommand, PutRolePolicyCommand, PutUserPolicyCommand, } from "@aws-sdk/client-iam";
11
+ import { IAMClient, ListRolesCommand, CreateRoleCommand, GetRoleCommand, AttachRolePolicyCommand, PutRolePolicyCommand, PutUserPolicyCommand, CreateServiceLinkedRoleCommand, } from "@aws-sdk/client-iam";
12
12
  import { EC2Client, DescribeVpcsCommand, DescribeSubnetsCommand, DescribeSecurityGroupsCommand, } from "@aws-sdk/client-ec2";
13
13
  import { CloudWatchLogsClient, CreateLogGroupCommand, } from "@aws-sdk/client-cloudwatch-logs";
14
14
  import { AWS_CONSTANTS } from "../../shared/aws-constants.js";
@@ -146,6 +146,8 @@ async function setupEcsCloud(cloud) {
146
146
  let accountId;
147
147
  const identity = await stsClient.send(new GetCallerIdentityCommand({}));
148
148
  accountId = identity.Account;
149
+ // Ensure service-linked roles exist (one-time per AWS account)
150
+ await ensureServiceLinkedRoles(iamClient);
149
151
  cloud.ecrRepository = await pickOrCreateEcrRepo(ecrClient, region, accountId);
150
152
  await ensureLambdaEcrPolicy(ecrClient, cloud.ecrRepository);
151
153
  cloud.ecsCluster = await pickOrCreateEcsCluster(ecsClient);
@@ -197,6 +199,9 @@ async function setupEcsCloud(cloud) {
197
199
  }
198
200
  // Create CodeBuild service role for remote image builds
199
201
  await ensureCodeBuildRole(iamClient, accountId, region, cloud.ecrRepository);
202
+ // Create App Runner roles for cloud deploy
203
+ cloud.appRunnerAccessRoleArn = await ensureAppRunnerAccessRole(iamClient);
204
+ cloud.appRunnerInstanceRoleArn = await ensureAppRunnerInstanceRole(iamClient, accountId, region, cloud.ecrRepository);
200
205
  const result = await pickVpcAndSubnets(ec2Client);
201
206
  cloud.subnets = result.subnets;
202
207
  const sgs = await pickSecurityGroups(ec2Client, result.vpcId);
@@ -230,13 +235,37 @@ async function setupEcsCloud(cloud) {
230
235
  Resource: [
231
236
  `arn:aws:logs:${region}:${accountId}:log-group:${AWS_CONSTANTS.LOG_GROUP}*`,
232
237
  `arn:aws:logs:${region}:${accountId}:log-group:${AWS_CONSTANTS.LAMBDA_LOG_GROUP}/al-*`,
238
+ `arn:aws:logs:${region}:${accountId}:log-group:${AWS_CONSTANTS.APPRUNNER_LOG_GROUP}*`,
233
239
  ],
234
240
  },
241
+ {
242
+ Effect: "Allow",
243
+ Action: [
244
+ "apprunner:CreateService",
245
+ "apprunner:UpdateService",
246
+ "apprunner:DescribeService",
247
+ "apprunner:DeleteService",
248
+ ],
249
+ Resource: `arn:aws:apprunner:${region}:${accountId}:service/al-scheduler/*`,
250
+ },
251
+ {
252
+ Effect: "Allow",
253
+ Action: "apprunner:ListServices",
254
+ Resource: "*",
255
+ },
235
256
  {
236
257
  Effect: "Allow",
237
258
  Action: "iam:PutUserPolicy",
238
259
  Resource: `arn:aws:iam::${accountId}:user/${userName}`,
239
260
  },
261
+ {
262
+ Effect: "Allow",
263
+ Action: "iam:CreateServiceLinkedRole",
264
+ Resource: [
265
+ `arn:aws:iam::${accountId}:role/aws-service-role/ecs.amazonaws.com/*`,
266
+ `arn:aws:iam::${accountId}:role/aws-service-role/apprunner.amazonaws.com/*`,
267
+ ],
268
+ },
240
269
  ],
241
270
  });
242
271
  try {
@@ -255,6 +284,30 @@ async function setupEcsCloud(cloud) {
255
284
  }
256
285
  return true;
257
286
  }
287
+ // --- Service-linked roles ---
288
+ async function ensureServiceLinkedRoles(iamClient) {
289
+ const services = [
290
+ { name: "ECS", serviceName: "ecs.amazonaws.com" },
291
+ { name: "App Runner", serviceName: "apprunner.amazonaws.com" },
292
+ ];
293
+ for (const svc of services) {
294
+ try {
295
+ await iamClient.send(new CreateServiceLinkedRoleCommand({
296
+ AWSServiceName: svc.serviceName,
297
+ }));
298
+ console.log(` Created service-linked role for ${svc.name}`);
299
+ }
300
+ catch (err) {
301
+ if (err.name === "InvalidInputException" && err.message?.includes("already exists")) {
302
+ console.log(` Service-linked role for ${svc.name} already exists`);
303
+ }
304
+ else {
305
+ console.log(` Warning: could not create service-linked role for ${svc.name}: ${err.message}`);
306
+ console.log(` You may need to run: aws iam create-service-linked-role --aws-service-name ${svc.serviceName}`);
307
+ }
308
+ }
309
+ }
310
+ }
258
311
  // --- Resource pickers ---
259
312
  async function pickOrCreateEcrRepo(ecrClient, region, accountId) {
260
313
  console.log("Looking for ECR repositories...");
@@ -399,12 +452,19 @@ async function pickOrCreateEcsRole(iamClient, label, defaultName, managedPolicie
399
452
  }
400
453
  });
401
454
  if (ecsRoles.length > 0) {
455
+ // Sort so the expected default role appears first
456
+ const sorted = [...ecsRoles].sort((a, b) => {
457
+ const aMatch = a.RoleName === defaultName ? 0 : 1;
458
+ const bMatch = b.RoleName === defaultName ? 0 : 1;
459
+ return aMatch - bMatch || a.RoleName.localeCompare(b.RoleName);
460
+ });
461
+ const defaultArn = sorted.find((r) => r.RoleName === defaultName)?.Arn;
402
462
  const choices = [
403
- ...ecsRoles.map((r) => ({ name: r.RoleName, value: r.Arn })),
463
+ ...sorted.map((r) => ({ name: r.RoleName, value: r.Arn })),
404
464
  { name: `Create new: ${defaultName}`, value: CREATE_NEW },
405
465
  { name: "Enter ARN manually", value: MANUAL_INPUT },
406
466
  ];
407
- const choice = await select({ message: `${label}:`, choices });
467
+ const choice = await select({ message: `${label}:`, choices, default: defaultArn });
408
468
  if (choice === MANUAL_INPUT)
409
469
  return input({ message: `${label} ARN:` });
410
470
  if (choice !== CREATE_NEW)
@@ -609,6 +669,241 @@ async function pickVpcAndSubnets(ec2Client) {
609
669
  const raw = await input({ message: "Subnet IDs (comma-separated):" });
610
670
  return { subnets: raw.split(",").map(s => s.trim()).filter(Boolean), vpcId };
611
671
  }
672
+ async function ensureAppRunnerAccessRole(iamClient) {
673
+ const roleName = AWS_CONSTANTS.APPRUNNER_ACCESS_ROLE;
674
+ console.log(`\nEnsuring App Runner access role (${roleName})...`);
675
+ const trustPolicy = JSON.stringify({
676
+ Version: "2012-10-17",
677
+ Statement: [{
678
+ Effect: "Allow",
679
+ Principal: { Service: "build.apprunner.amazonaws.com" },
680
+ Action: "sts:AssumeRole",
681
+ }],
682
+ });
683
+ try {
684
+ const data = await iamClient.send(new CreateRoleCommand({
685
+ RoleName: roleName,
686
+ AssumeRolePolicyDocument: trustPolicy,
687
+ }));
688
+ console.log(` Created role: ${roleName}`);
689
+ }
690
+ catch (err) {
691
+ if (err.name === "EntityAlreadyExistsException") {
692
+ console.log(` Role already exists`);
693
+ }
694
+ else {
695
+ console.log(` Warning: could not create ${roleName}: ${err.message}`);
696
+ return input({ message: "App Runner access role ARN:" });
697
+ }
698
+ }
699
+ try {
700
+ await iamClient.send(new AttachRolePolicyCommand({
701
+ RoleName: roleName,
702
+ PolicyArn: "arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess",
703
+ }));
704
+ console.log(` Attached AWSAppRunnerServicePolicyForECRAccess`);
705
+ }
706
+ catch (err) {
707
+ console.log(` Warning: could not attach ECR access policy: ${err.message}`);
708
+ }
709
+ const data = await iamClient.send(new GetRoleCommand({ RoleName: roleName }));
710
+ return data.Role.Arn;
711
+ }
712
+ async function ensureAppRunnerInstanceRole(iamClient, accountId, region, ecrRepository) {
713
+ const roleName = AWS_CONSTANTS.APPRUNNER_INSTANCE_ROLE;
714
+ console.log(`\nEnsuring App Runner instance role (${roleName})...`);
715
+ const trustPolicy = JSON.stringify({
716
+ Version: "2012-10-17",
717
+ Statement: [{
718
+ Effect: "Allow",
719
+ Principal: { Service: "tasks.apprunner.amazonaws.com" },
720
+ Action: "sts:AssumeRole",
721
+ }],
722
+ });
723
+ try {
724
+ await iamClient.send(new CreateRoleCommand({
725
+ RoleName: roleName,
726
+ AssumeRolePolicyDocument: trustPolicy,
727
+ }));
728
+ console.log(` Created role: ${roleName}`);
729
+ }
730
+ catch (err) {
731
+ if (err.name === "EntityAlreadyExistsException") {
732
+ console.log(` Role already exists`);
733
+ }
734
+ else {
735
+ console.log(` Warning: could not create ${roleName}: ${err.message}`);
736
+ return input({ message: "App Runner instance role ARN:" });
737
+ }
738
+ }
739
+ // The instance role needs the same permissions as the operator:
740
+ // ECS, ECR, CodeBuild, S3, SecretsManager, CloudWatch Logs, IAM, Lambda, App Runner
741
+ const repoName = ecrRepository.split("/").pop();
742
+ const bucketName = AWS_CONSTANTS.buildBucket(accountId, region);
743
+ try {
744
+ await iamClient.send(new PutRolePolicyCommand({
745
+ RoleName: roleName,
746
+ PolicyName: "ActionLlamaScheduler",
747
+ PolicyDocument: JSON.stringify({
748
+ Version: "2012-10-17",
749
+ Statement: [
750
+ {
751
+ Sid: "Identity",
752
+ Effect: "Allow",
753
+ Action: "sts:GetCallerIdentity",
754
+ Resource: "*",
755
+ },
756
+ {
757
+ Sid: "ECS",
758
+ Effect: "Allow",
759
+ Action: [
760
+ "ecs:RegisterTaskDefinition",
761
+ "ecs:RunTask",
762
+ "ecs:DescribeTasks",
763
+ "ecs:ListTasks",
764
+ "ecs:StopTask",
765
+ ],
766
+ Resource: "*",
767
+ },
768
+ {
769
+ Sid: "Logs",
770
+ Effect: "Allow",
771
+ Action: [
772
+ "logs:CreateLogGroup",
773
+ "logs:CreateLogStream",
774
+ "logs:PutLogEvents",
775
+ "logs:GetLogEvents",
776
+ "logs:FilterLogEvents",
777
+ ],
778
+ Resource: [
779
+ `arn:aws:logs:${region}:${accountId}:log-group:/ecs/action-llama*`,
780
+ `arn:aws:logs:${region}:${accountId}:log-group:/aws/lambda/al-*`,
781
+ `arn:aws:logs:${region}:${accountId}:log-group:/apprunner/al-scheduler*`,
782
+ ],
783
+ },
784
+ {
785
+ Sid: "SecretsManager",
786
+ Effect: "Allow",
787
+ Action: [
788
+ "secretsmanager:ListSecrets",
789
+ "secretsmanager:CreateSecret",
790
+ "secretsmanager:PutSecretValue",
791
+ "secretsmanager:GetSecretValue",
792
+ ],
793
+ Resource: "*",
794
+ },
795
+ {
796
+ Sid: "PassRole",
797
+ Effect: "Allow",
798
+ Action: "iam:PassRole",
799
+ Resource: `arn:aws:iam::${accountId}:role/al-*`,
800
+ Condition: {
801
+ StringEquals: {
802
+ "iam:PassedToService": [
803
+ "ecs-tasks.amazonaws.com",
804
+ "codebuild.amazonaws.com",
805
+ "lambda.amazonaws.com",
806
+ "apprunner.amazonaws.com",
807
+ ],
808
+ },
809
+ },
810
+ },
811
+ {
812
+ Sid: "IAMAgentRoles",
813
+ Effect: "Allow",
814
+ Action: [
815
+ "iam:CreateRole",
816
+ "iam:GetRole",
817
+ "iam:GetRolePolicy",
818
+ "iam:PutRolePolicy",
819
+ "iam:DeleteRole",
820
+ "iam:DeleteRolePolicy",
821
+ "iam:AttachRolePolicy",
822
+ ],
823
+ Resource: `arn:aws:iam::${accountId}:role/al-*`,
824
+ },
825
+ {
826
+ Sid: "IAMListRoles",
827
+ Effect: "Allow",
828
+ Action: "iam:ListRoles",
829
+ Resource: "*",
830
+ },
831
+ {
832
+ Sid: "ECR",
833
+ Effect: "Allow",
834
+ Action: [
835
+ "ecr:BatchGetImage",
836
+ "ecr:GetDownloadUrlForLayer",
837
+ "ecr:BatchCheckLayerAvailability",
838
+ "ecr:GetAuthorizationToken",
839
+ "ecr:SetRepositoryPolicy",
840
+ ],
841
+ Resource: "*",
842
+ },
843
+ {
844
+ Sid: "CodeBuild",
845
+ Effect: "Allow",
846
+ Action: [
847
+ "codebuild:StartBuild",
848
+ "codebuild:BatchGetBuilds",
849
+ "codebuild:CreateProject",
850
+ ],
851
+ Resource: `arn:aws:codebuild:${region}:${accountId}:project/al-image-builder`,
852
+ },
853
+ {
854
+ Sid: "Lambda",
855
+ Effect: "Allow",
856
+ Action: [
857
+ "lambda:GetFunction",
858
+ "lambda:CreateFunction",
859
+ "lambda:UpdateFunctionCode",
860
+ "lambda:UpdateFunctionConfiguration",
861
+ "lambda:InvokeFunction",
862
+ ],
863
+ Resource: `arn:aws:lambda:${region}:${accountId}:function:al-*`,
864
+ },
865
+ {
866
+ Sid: "S3",
867
+ Effect: "Allow",
868
+ Action: [
869
+ "s3:CreateBucket",
870
+ "s3:PutObject",
871
+ "s3:GetObject",
872
+ "s3:ListBucket",
873
+ ],
874
+ Resource: [
875
+ `arn:aws:s3:::${bucketName}`,
876
+ `arn:aws:s3:::${bucketName}/*`,
877
+ ],
878
+ },
879
+ {
880
+ Sid: "AppRunner",
881
+ Effect: "Allow",
882
+ Action: [
883
+ "apprunner:CreateService",
884
+ "apprunner:UpdateService",
885
+ "apprunner:DescribeService",
886
+ "apprunner:DeleteService",
887
+ ],
888
+ Resource: `arn:aws:apprunner:${region}:${accountId}:service/al-scheduler/*`,
889
+ },
890
+ {
891
+ Sid: "AppRunnerList",
892
+ Effect: "Allow",
893
+ Action: "apprunner:ListServices",
894
+ Resource: "*",
895
+ },
896
+ ],
897
+ }),
898
+ }));
899
+ console.log(` Attached ActionLlamaScheduler policy`);
900
+ }
901
+ catch (err) {
902
+ console.log(` Warning: could not attach policy to ${roleName}: ${err.message}`);
903
+ }
904
+ const data = await iamClient.send(new GetRoleCommand({ RoleName: roleName }));
905
+ return data.Role.Arn;
906
+ }
612
907
  async function pickSecurityGroups(ec2Client, vpcId) {
613
908
  if (!vpcId) {
614
909
  const raw = await input({ message: "Security group IDs (comma-separated, optional):" });