Skip to content

feat(credentials): surface External ID and trust policy after add [COMP-1405]#634

Merged
munishchouhan merged 7 commits into
masterfrom
feature/COMP-1405-trust-policy-output
Jun 12, 2026
Merged

feat(credentials): surface External ID and trust policy after add [COMP-1405]#634
munishchouhan merged 7 commits into
masterfrom
feature/COMP-1405-trust-policy-output

Conversation

@munishchouhan

@munishchouhan munishchouhan commented Jun 11, 2026

Copy link
Copy Markdown
Member

Description

Closes COMP-1884

Brings CLI output to parity with the web UI modal for AWS role-mode credentials. After tw credentials add aws --mode role --generate-external-id … succeeds, the CLI now follows up with GET /credentials/{id} and prints:

  • the External ID generated by Platform (so users don't have to do a separate fetch)
  • the trust policy snippet rendered server-side when the installation is configured with TOWER_AWS_JUMP_ROLE_ARN + TOWER_AWS_JUMP_EXTERNAL_ID (so users can paste it directly into their IAM role's trust relationship)

The follow-up describe is best-effort: if it fails, the credential is still created and the CLI returns the same minimal output as before. Both fields are nullable — for providers/flows that don't have either, the output is unchanged.

Sample output (AWS role mode with jump role configured)

$ tw credentials add aws --mode role --generate-external-id \
    --assume-role-arn arn:aws:iam::222:role/CustomerRole \
    -n my-aws-creds

  New AWS credentials 'my-aws-creds (abc123)' added at user workspace.

  External ID: a1b2c3d4-e5f6-...

  Trust policy (paste this into your IAM role's trust relationship):

    {
      "Version": "2012-10-17",
      ...
    }

Versioning

  • towerJavaSdkVersion: 1.150.01.167.0 (consume the new setupSnippet field on DescribeCredentialsResponse).
  • VERSION-API: 1.148.01.167.0 (matches the minimum API version where the dependent field was introduced).
  • service-info.json test fixture bumped to match.
  • conf/reflect-config.json regenerated via ./gradlew runReflectionConfigGenerator -Dtracing-agent=true on GraalVM CE 21 to register the new SDK model getters/setters (e.g. AwsCloudConfig.getDeletedResources) for the native binary. Without this the JVM tests passed but the binary tests failed with tried to reflectively invoke method … without it being registered for runtime reflection.

Dependency chain

This PR is the third in a chain landing the setupSnippet feature end-to-end:

  1. Platform (seqeralabs/platform PR #11417) — adds the setupSnippet field to DescribeCredentialsResponse and the AwsTrustPolicyRenderer that populates it. Merged + deployed.
  2. tower-sdk-gencode update-1.167.0 — regenerates the SDK from the updated OpenAPI spec. Published as tower-java-sdk 1.167.0.
  3. This PR — consumes the new SDK + adds the CLI UX.

Guidelines for testing

  1. Install this build locally (./gradlew installDist) or use the published artifact once available.
  2. Point at a Platform instance configured with both TOWER_AWS_JUMP_ROLE_ARN and TOWER_AWS_JUMP_EXTERNAL_ID (e.g. local dev with those env vars set).
  3. Create an AWS role-mode credential:
    tw credentials add aws --mode role --generate-external-id \
      --assume-role-arn arn:aws:iam::222:role/CustomerRole \
      -n my-aws-creds
    
    Expected: success message includes the generated External ID and the trust policy block.
  4. Negative paths:
    • On a Platform without the jump-role env vars: the External ID still appears, but no trust policy block.
    • On a non-AWS credential (e.g. tw credentials add google …): neither line appears.
    • Simulate the follow-up describe failing (e.g. revoke the token immediately after create) — the original success message still prints; nothing crashes.

Checklist

  • Create or update unit tests.
  • Create or update E2E tests.
  • Update the README / documentation.
  • Run @claude release-notes to generate release notes for EDU team
  • Document any new environment variables

(No new env vars introduced — TOWER_AWS_JUMP_ROLE_ARN / TOWER_AWS_JUMP_EXTERNAL_ID are already documented platform-side from COMP-1426.)

After `tw credentials add` succeeds, follow up with a GET /credentials/{id}
to enrich the success message with:
  - the generated External ID (AWS role-mode credentials)
  - the server-rendered provider-side setup snippet (e.g. AWS IAM trust
    policy when the installation is configured with a jump role)

This brings CLI output to parity with the web UI modal, so users no
longer need a follow-up `tw credentials view` (which doesn't exist) or a
trip to the docs to assemble the trust policy by hand.

The follow-up describe is best-effort: if it fails the credential is
still created and the CLI returns the same minimal output as before.

Bump tower-java-sdk to 1.167.0 (and VERSION-API + service-info fixture
to match) to consume the new `setupSnippet` field on
DescribeCredentialsResponse.
@munishchouhan

Copy link
Copy Markdown
Member Author

tested locally with stage

tower-cli % ./build/native/nativeCompile/tw --url https://cloud.stage-seqera.io/api credentials add aws \
    --mode role \
    --generate-external-id \
    --assume-role-arn arn:aws:iam::222222222222:role/CustomerRole \
    -n test-trust-policy

  New AWS credentials 'test-trust-policy (12121212121)' added at user workspace

  External ID: agagagsgsgsgsj

  Trust policy (paste this into your IAM role's trust relationship):

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
            "AWS": "arn:aws:iam::898507537698:role/SeqeraPlatformCloudAccessRoleStage"
          },
          "Action": "sts:AssumeRole",
          "Condition": {
            "StringEquals": {
              "sts:ExternalId": "agagagsgsgsgsj"
            }
          }
        },
        {
          "Effect": "Allow",
          "Principal": {
            "AWS": "arn:aws:iam::898507537698:role/SeqeraPlatformCloudAccessRoleStage"
          },
          "Action": "sts:TagSession"
        }
      ]
    }

@munishchouhan munishchouhan self-assigned this Jun 11, 2026
The indent() helper hardcoded '\n' while the surrounding format string used
%n. On Windows %n resolves to \r\n, producing a byte-level mismatch between
expected (built in JVM with %n=\r\n) and actual (native binary stdout) even
though the rendered output looked identical. Use String.format("%n") inside
indent() to match the rest of the message.
…n not needed

The follow-up describeCredentials() call after a credential add was running
for every provider and silently swallowing any failure. Now:

- Only call describe when useExternalId is true (AWS role mode or
  --generate-external-id) — the only flows where the response actually
  carries an External ID or trust policy. Avoids an unnecessary GET on
  every non-AWS / non-role credential add.
- On describe failure, print a yellow Warning to stderr stating that
  credential details could not be fetched and that the credential was
  created. Silent swallow gave the user no signal that enrichment failed.

Updates the existing AWS role/generate-external-id tests to mock the
describe call so their stderr stays empty.
@munishchouhan munishchouhan requested a review from a team June 11, 2026 22:14
Comment thread src/main/java/io/seqera/tower/cli/commands/credentials/add/AbstractAddCmd.java Outdated
CredentialsProvider.useExternalId() returned a nullable Boolean — null
for most providers, true/false for AWS. Auto-unboxing at the call site
triggered NPE on non-AWS adds. Switch to primitive boolean (default
false) so callers can use the value directly without null guards.

AwsProvider previously returned null to mean "feature off"; that now
maps to false, which the createCredentials call passes through to
Platform as useExternalId=false (semantically equivalent to omitting
the param for the toggle).
- Drop redundant field-level javadoc on CredentialsAdded.externalId and
  setupSnippet; field names are self-explanatory.
- Cache getProvider() and useExternalId() once at the top of
  AbstractAddCmd.exec() instead of recomputing on every reference.
- Use Java 21 pattern matching for the AwsSecurityKeys instanceof
  branch.
@munishchouhan munishchouhan merged commit 3bd98d3 into master Jun 12, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants