@devramps/cli 0.1.22 → 0.1.24

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 (2) hide show
  1. package/dist/index.js +120 -78
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1556,25 +1556,36 @@ function addOidcProviderResource(template, conditional = true, oidcProviderUrl)
1556
1556
  }
1557
1557
  };
1558
1558
  }
1559
- function buildOidcTrustPolicy(accountId, subject, oidcProviderUrl) {
1559
+ function buildOidcTrustPolicy(accountId, subject, oidcProviderUrl, additionalTrustedAccounts, skipOidc) {
1560
1560
  const providerUrl = oidcProviderUrl || OIDC_PROVIDER_URL;
1561
- return {
1562
- Version: "2012-10-17",
1563
- Statement: [
1564
- {
1565
- Effect: "Allow",
1566
- Principal: {
1567
- Federated: `arn:aws:iam::${accountId}:oidc-provider/${providerUrl}`
1568
- },
1569
- Action: "sts:AssumeRoleWithWebIdentity",
1570
- Condition: {
1571
- StringEquals: {
1572
- [`${providerUrl}:sub`]: subject,
1573
- [`${providerUrl}:aud`]: "sts.amazonaws.com"
1574
- }
1561
+ const statements = [];
1562
+ if (!skipOidc) {
1563
+ statements.push({
1564
+ Effect: "Allow",
1565
+ Principal: {
1566
+ Federated: `arn:aws:iam::${accountId}:oidc-provider/${providerUrl}`
1567
+ },
1568
+ Action: "sts:AssumeRoleWithWebIdentity",
1569
+ Condition: {
1570
+ StringEquals: {
1571
+ [`${providerUrl}:sub`]: subject,
1572
+ [`${providerUrl}:aud`]: "sts.amazonaws.com"
1575
1573
  }
1576
1574
  }
1577
- ]
1575
+ });
1576
+ }
1577
+ if (additionalTrustedAccounts && additionalTrustedAccounts.length > 0) {
1578
+ statements.push({
1579
+ Effect: "Allow",
1580
+ Principal: {
1581
+ AWS: additionalTrustedAccounts.map((id) => `arn:aws:iam::${id}:root`)
1582
+ },
1583
+ Action: "sts:AssumeRole"
1584
+ });
1585
+ }
1586
+ return {
1587
+ Version: "2012-10-17",
1588
+ Statement: statements
1578
1589
  };
1579
1590
  }
1580
1591
  function createIamRoleResource(roleName, trustPolicy, policies, additionalTags = []) {
@@ -1938,7 +1949,7 @@ function createTerraformStateBucketPolicy(bucketName, cicdAccountId, allowedAcco
1938
1949
 
1939
1950
  // src/templates/org-stack.ts
1940
1951
  function generateOrgStackTemplate(options) {
1941
- const { orgSlug, cicdAccountId, targetAccountIds, oidcProviderUrl } = options;
1952
+ const { orgSlug, cicdAccountId, targetAccountIds, oidcProviderUrl, additionalTrustedAccounts, skipOidc } = options;
1942
1953
  const template = createBaseTemplate(`DevRamps Org Stack for ${orgSlug}`);
1943
1954
  const kmsKeyPolicy = buildKmsKeyPolicy(cicdAccountId, targetAccountIds);
1944
1955
  template.Resources.DevRampsKMSKey = createKmsKeyResource(
@@ -1968,7 +1979,7 @@ function generateOrgStackTemplate(options) {
1968
1979
  PolicyDocument: bucketPolicy
1969
1980
  }
1970
1981
  };
1971
- const trustPolicy = buildOidcTrustPolicy(cicdAccountId, `org:${orgSlug}/cicd`, oidcProviderUrl);
1982
+ const trustPolicy = buildOidcTrustPolicy(cicdAccountId, `org:${orgSlug}/cicd`, oidcProviderUrl, additionalTrustedAccounts, skipOidc);
1972
1983
  const orgRolePolicies = buildOrgRolePolicies(orgSlug);
1973
1984
  template.Resources.DevRampsCICDDeploymentRole = createIamRoleResource(
1974
1985
  getOrgRoleName(),
@@ -2433,13 +2444,15 @@ function generateStageStackTemplate(options) {
2433
2444
  additionalPolicies,
2434
2445
  dockerArtifacts,
2435
2446
  bundleArtifacts,
2436
- oidcProviderUrl
2447
+ oidcProviderUrl,
2448
+ additionalTrustedAccounts,
2449
+ skipOidc
2437
2450
  } = options;
2438
2451
  const template = createBaseTemplate(
2439
2452
  `DevRamps Stage Stack for ${pipelineSlug}/${stageName}`
2440
2453
  );
2441
2454
  const roleName = generateStageRoleName(pipelineSlug, stageName);
2442
- const trustPolicy = buildStageTrustPolicy(accountId, orgSlug, pipelineSlug, oidcProviderUrl);
2455
+ const trustPolicy = buildStageTrustPolicy(accountId, orgSlug, pipelineSlug, oidcProviderUrl, additionalTrustedAccounts, skipOidc);
2443
2456
  const policies = buildStagePolicies(steps, additionalPolicies);
2444
2457
  template.Resources.StageDeploymentRole = createIamRoleResource(
2445
2458
  roleName,
@@ -2483,8 +2496,6 @@ function generateStageStackTemplate(options) {
2483
2496
  );
2484
2497
  s3Outputs[artifact.name] = { resourceId };
2485
2498
  }
2486
- const providerUrl = oidcProviderUrl || OIDC_PROVIDER_URL;
2487
- const oidcProviderArn = `arn:aws:iam::${accountId}:oidc-provider/${providerUrl}`;
2488
2499
  template.Outputs = {
2489
2500
  StageRoleArn: {
2490
2501
  Description: "ARN of the stage deployment role",
@@ -2495,10 +2506,6 @@ function generateStageStackTemplate(options) {
2495
2506
  Description: "Name of the stage deployment role",
2496
2507
  Value: { Ref: "StageDeploymentRole" }
2497
2508
  },
2498
- OIDCProviderArn: {
2499
- Description: "ARN of the OIDC provider (created by Account Bootstrap stack)",
2500
- Value: oidcProviderArn
2501
- },
2502
2509
  PipelineSlug: {
2503
2510
  Description: "Pipeline slug",
2504
2511
  Value: pipelineSlug
@@ -2508,6 +2515,13 @@ function generateStageStackTemplate(options) {
2508
2515
  Value: stageName
2509
2516
  }
2510
2517
  };
2518
+ if (!skipOidc) {
2519
+ const providerUrl = oidcProviderUrl || OIDC_PROVIDER_URL;
2520
+ template.Outputs.OIDCProviderArn = {
2521
+ Description: "ARN of the OIDC provider (created by Account Bootstrap stack)",
2522
+ Value: `arn:aws:iam::${accountId}:oidc-provider/${providerUrl}`
2523
+ };
2524
+ }
2511
2525
  for (const [artifactName, { resourceId }] of Object.entries(ecrOutputs)) {
2512
2526
  const safeName = sanitizeResourceId(artifactName);
2513
2527
  template.Outputs[`${safeName}RepoUri`] = {
@@ -2524,9 +2538,9 @@ function generateStageStackTemplate(options) {
2524
2538
  }
2525
2539
  return template;
2526
2540
  }
2527
- function buildStageTrustPolicy(accountId, orgSlug, pipelineSlug, oidcProviderUrl) {
2541
+ function buildStageTrustPolicy(accountId, orgSlug, pipelineSlug, oidcProviderUrl, additionalTrustedAccounts, skipOidc) {
2528
2542
  const subject = `org:${orgSlug}/pipeline:${pipelineSlug}`;
2529
- return buildOidcTrustPolicy(accountId, subject, oidcProviderUrl);
2543
+ return buildOidcTrustPolicy(accountId, subject, oidcProviderUrl, additionalTrustedAccounts, skipOidc);
2530
2544
  }
2531
2545
  function buildStagePolicies(steps, additionalPolicies) {
2532
2546
  const policies = [];
@@ -2595,12 +2609,12 @@ function buildStagePolicies(steps, additionalPolicies) {
2595
2609
 
2596
2610
  // src/templates/import-stack.ts
2597
2611
  function generateImportStackTemplate(options) {
2598
- const { pipelineSlug, orgSlug, accountId, oidcProviderUrl } = options;
2612
+ const { pipelineSlug, orgSlug, accountId, oidcProviderUrl, additionalTrustedAccounts, skipOidc } = options;
2599
2613
  const template = createBaseTemplate(
2600
2614
  `DevRamps Import Stack for ${pipelineSlug} - grants read access for artifact imports`
2601
2615
  );
2602
2616
  const roleName = generateImportRoleName(pipelineSlug);
2603
- const trustPolicy = buildOidcTrustPolicy(accountId, `org:${orgSlug}/cicd`, oidcProviderUrl);
2617
+ const trustPolicy = buildOidcTrustPolicy(accountId, `org:${orgSlug}/cicd`, oidcProviderUrl, additionalTrustedAccounts, skipOidc);
2604
2618
  const policies = buildImportRolePolicies();
2605
2619
  template.Resources.ImportRole = createIamRoleResource(
2606
2620
  roleName,
@@ -2728,6 +2742,15 @@ function getOidcProviderUrlFromEndpoint(endpointOverride) {
2728
2742
  return void 0;
2729
2743
  }
2730
2744
  }
2745
+ function isLocalhostEndpoint(endpointOverride) {
2746
+ if (!endpointOverride) return false;
2747
+ try {
2748
+ const url = new URL(endpointOverride);
2749
+ return url.hostname === "localhost" || url.hostname === "127.0.0.1";
2750
+ } catch {
2751
+ return false;
2752
+ }
2753
+ }
2731
2754
  async function bootstrapCommand(options) {
2732
2755
  try {
2733
2756
  if (options.verbose) {
@@ -2778,7 +2801,12 @@ async function bootstrapCommand(options) {
2778
2801
  process.exit(0);
2779
2802
  }
2780
2803
  const oidcProviderUrl = getOidcProviderUrlFromEndpoint(options.endpointOverride);
2781
- await executeDeployment(plan, pipelines, pipelineArtifacts, authData, identity.accountId, options, oidcProviderUrl);
2804
+ const additionalTrustedAccounts = options.additionalTrustedAccounts ? options.additionalTrustedAccounts.split(",").map((s) => s.trim()) : void 0;
2805
+ const skipOidc = isLocalhostEndpoint(options.endpointOverride);
2806
+ if (skipOidc) {
2807
+ info("Localhost endpoint detected \u2014 OIDC provider creation will be skipped");
2808
+ }
2809
+ await executeDeployment(plan, pipelines, pipelineArtifacts, authData, identity.accountId, options, oidcProviderUrl, additionalTrustedAccounts, skipOidc);
2782
2810
  } catch (error2) {
2783
2811
  if (error2 instanceof DevRampsError) {
2784
2812
  error(error2.message);
@@ -3051,48 +3079,53 @@ async function confirmDeploymentPlan(plan) {
3051
3079
  ]
3052
3080
  });
3053
3081
  }
3054
- async function executeDeployment(plan, pipelines, pipelineArtifacts, authData, currentAccountId, options, oidcProviderUrl) {
3082
+ async function executeDeployment(plan, pipelines, pipelineArtifacts, authData, currentAccountId, options, oidcProviderUrl, additionalTrustedAccounts, skipOidc) {
3055
3083
  const results = { success: 0, failed: 0 };
3056
3084
  const remainingStacks = 1 + plan.pipelineStacks.length + plan.stageStacks.length + plan.importStacks.length;
3057
- newline();
3058
- header("Phase 1: Deploying Account Bootstrap Stacks");
3059
- info(`Deploying ${plan.accountStacks.length} account stack(s) in parallel...`);
3060
- newline();
3061
- const accountProgress = getMultiStackProgress();
3062
- for (const stack of plan.accountStacks) {
3063
- accountProgress.addStack(stack.stackName, "account", stack.accountId, stack.region, 1);
3064
- }
3065
- accountProgress.start();
3066
- const accountResults = await Promise.all(
3067
- plan.accountStacks.map(async (stack) => {
3068
- try {
3069
- await deployAccountStack(stack, currentAccountId, options, oidcProviderUrl);
3070
- return { stack: `${stack.stackName} (${stack.accountId})`, success: true };
3071
- } catch (error2) {
3072
- return {
3073
- stack: `${stack.stackName} (${stack.accountId})`,
3074
- success: false,
3075
- error: error2 instanceof Error ? error2.message : String(error2)
3076
- };
3085
+ if (skipOidc) {
3086
+ newline();
3087
+ header("Phase 1: Skipping Account Bootstrap Stacks (localhost endpoint, OIDC not needed)");
3088
+ } else {
3089
+ newline();
3090
+ header("Phase 1: Deploying Account Bootstrap Stacks");
3091
+ info(`Deploying ${plan.accountStacks.length} account stack(s) in parallel...`);
3092
+ newline();
3093
+ const accountProgress = getMultiStackProgress();
3094
+ for (const stack of plan.accountStacks) {
3095
+ accountProgress.addStack(stack.stackName, "account", stack.accountId, stack.region, 1);
3096
+ }
3097
+ accountProgress.start();
3098
+ const accountResults = await Promise.all(
3099
+ plan.accountStacks.map(async (stack) => {
3100
+ try {
3101
+ await deployAccountStack(stack, currentAccountId, options, oidcProviderUrl);
3102
+ return { stack: `${stack.stackName} (${stack.accountId})`, success: true };
3103
+ } catch (error2) {
3104
+ return {
3105
+ stack: `${stack.stackName} (${stack.accountId})`,
3106
+ success: false,
3107
+ error: error2 instanceof Error ? error2.message : String(error2)
3108
+ };
3109
+ }
3110
+ })
3111
+ );
3112
+ clearMultiStackProgress();
3113
+ newline();
3114
+ for (const result of accountResults) {
3115
+ if (result.success) {
3116
+ success(`${result.stack} deployed`);
3117
+ results.success++;
3118
+ } else {
3119
+ error(`${result.stack} failed: ${result.error}`);
3120
+ results.failed++;
3077
3121
  }
3078
- })
3079
- );
3080
- clearMultiStackProgress();
3081
- newline();
3082
- for (const result of accountResults) {
3083
- if (result.success) {
3084
- success(`${result.stack} deployed`);
3085
- results.success++;
3086
- } else {
3087
- error(`${result.stack} failed: ${result.error}`);
3088
- results.failed++;
3089
3122
  }
3090
- }
3091
- if (results.failed > 0) {
3092
- newline();
3093
- header("Deployment Summary");
3094
- error(`${results.failed} account stack(s) failed. Skipping remaining ${remainingStacks} stack(s).`);
3095
- process.exit(1);
3123
+ if (results.failed > 0) {
3124
+ newline();
3125
+ header("Deployment Summary");
3126
+ error(`${results.failed} account stack(s) failed. Skipping remaining ${remainingStacks} stack(s).`);
3127
+ process.exit(1);
3128
+ }
3096
3129
  }
3097
3130
  newline();
3098
3131
  header("Phase 2: Deploying Org, Pipeline, Stage, and Import Stacks");
@@ -3114,7 +3147,7 @@ async function executeDeployment(plan, pipelines, pipelineArtifacts, authData, c
3114
3147
  mainProgress.start();
3115
3148
  const orgPromise = (async () => {
3116
3149
  try {
3117
- await deployOrgStack(plan, pipelines, authData, currentAccountId, options, oidcProviderUrl);
3150
+ await deployOrgStack(plan, pipelines, authData, currentAccountId, options, oidcProviderUrl, additionalTrustedAccounts, skipOidc);
3118
3151
  return { stack: plan.orgStack.stackName, success: true };
3119
3152
  } catch (error2) {
3120
3153
  return {
@@ -3138,7 +3171,7 @@ async function executeDeployment(plan, pipelines, pipelineArtifacts, authData, c
3138
3171
  });
3139
3172
  const stagePromises = plan.stageStacks.map(async (stack) => {
3140
3173
  try {
3141
- await deployStageStack(stack, authData, currentAccountId, options, oidcProviderUrl);
3174
+ await deployStageStack(stack, authData, currentAccountId, options, oidcProviderUrl, additionalTrustedAccounts, skipOidc);
3142
3175
  return { stack: stack.stackName, success: true };
3143
3176
  } catch (error2) {
3144
3177
  return {
@@ -3150,7 +3183,7 @@ async function executeDeployment(plan, pipelines, pipelineArtifacts, authData, c
3150
3183
  });
3151
3184
  const importPromises = plan.importStacks.map(async (stack) => {
3152
3185
  try {
3153
- await deployImportStack(stack, currentAccountId, options, oidcProviderUrl);
3186
+ await deployImportStack(stack, currentAccountId, options, oidcProviderUrl, additionalTrustedAccounts, skipOidc);
3154
3187
  return { stack: `${stack.stackName} (${stack.accountId})`, success: true };
3155
3188
  } catch (error2) {
3156
3189
  return {
@@ -3187,7 +3220,7 @@ async function executeDeployment(plan, pipelines, pipelineArtifacts, authData, c
3187
3220
  process.exit(1);
3188
3221
  }
3189
3222
  }
3190
- async function deployOrgStack(plan, pipelines, authData, currentAccountId, options, oidcProviderUrl) {
3223
+ async function deployOrgStack(plan, pipelines, authData, currentAccountId, options, oidcProviderUrl, additionalTrustedAccounts, skipOidc) {
3191
3224
  const { orgSlug, cicdAccountId, cicdRegion } = authData;
3192
3225
  const credentials = cicdAccountId !== currentAccountId ? (await assumeRoleForAccount({
3193
3226
  targetAccountId: cicdAccountId,
@@ -3218,7 +3251,9 @@ async function deployOrgStack(plan, pipelines, authData, currentAccountId, optio
3218
3251
  orgSlug,
3219
3252
  cicdAccountId,
3220
3253
  targetAccountIds,
3221
- oidcProviderUrl
3254
+ oidcProviderUrl,
3255
+ additionalTrustedAccounts,
3256
+ skipOidc
3222
3257
  });
3223
3258
  const deployOptions = {
3224
3259
  stackName: plan.orgStack.stackName,
@@ -3270,7 +3305,7 @@ async function deployAccountStack(stack, currentAccountId, options, oidcProvider
3270
3305
  await previewStackChanges(deployOptions);
3271
3306
  await deployStack(deployOptions);
3272
3307
  }
3273
- async function deployStageStack(stack, authData, currentAccountId, options, oidcProviderUrl) {
3308
+ async function deployStageStack(stack, authData, currentAccountId, options, oidcProviderUrl, additionalTrustedAccounts, skipOidc) {
3274
3309
  const credentials = stack.accountId !== currentAccountId ? (await assumeRoleForAccount({
3275
3310
  targetAccountId: stack.accountId,
3276
3311
  currentAccountId,
@@ -3285,7 +3320,9 @@ async function deployStageStack(stack, authData, currentAccountId, options, oidc
3285
3320
  additionalPolicies: stack.additionalPolicies,
3286
3321
  dockerArtifacts: stack.dockerArtifacts,
3287
3322
  bundleArtifacts: stack.bundleArtifacts,
3288
- oidcProviderUrl
3323
+ oidcProviderUrl,
3324
+ additionalTrustedAccounts,
3325
+ skipOidc
3289
3326
  });
3290
3327
  const deployOptions = {
3291
3328
  stackName: stack.stackName,
@@ -3297,7 +3334,7 @@ async function deployStageStack(stack, authData, currentAccountId, options, oidc
3297
3334
  await previewStackChanges(deployOptions);
3298
3335
  await deployStack(deployOptions);
3299
3336
  }
3300
- async function deployImportStack(stack, currentAccountId, options, oidcProviderUrl) {
3337
+ async function deployImportStack(stack, currentAccountId, options, oidcProviderUrl, additionalTrustedAccounts, skipOidc) {
3301
3338
  const credentials = stack.accountId !== currentAccountId ? (await assumeRoleForAccount({
3302
3339
  targetAccountId: stack.accountId,
3303
3340
  currentAccountId,
@@ -3307,7 +3344,9 @@ async function deployImportStack(stack, currentAccountId, options, oidcProviderU
3307
3344
  pipelineSlug: stack.pipelineSlug,
3308
3345
  orgSlug: stack.orgSlug,
3309
3346
  accountId: stack.accountId,
3310
- oidcProviderUrl
3347
+ oidcProviderUrl,
3348
+ additionalTrustedAccounts,
3349
+ skipOidc
3311
3350
  });
3312
3351
  const deployOptions = {
3313
3352
  stackName: stack.stackName,
@@ -3337,5 +3376,8 @@ program.command("bootstrap").description("Bootstrap IAM roles in target AWS acco
3337
3376
  ).option(
3338
3377
  "--endpoint-override <url>",
3339
3378
  "Override the DevRamps API endpoint (for testing, e.g., http://localhost:3000)"
3379
+ ).option(
3380
+ "--additional-trusted-accounts <accounts>",
3381
+ "Comma-separated AWS account IDs to add to role trust policies (for local dev testing)"
3340
3382
  ).action(bootstrapCommand);
3341
3383
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devramps/cli",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "DevRamps CLI - Bootstrap AWS infrastructure for CI/CD pipelines",
5
5
  "main": "dist/index.js",
6
6
  "bin": {