feat(aws): add IAM to Cloud Explorer - #145
Conversation
|
| Filename | Overview |
|---|---|
| packages/api/src/adapter-aws/AwsIamAdapter.ts | Implements CloudServiceAdapter for AWS IAM users (list, get, create, delete) with proper typed error handling, pagination guard, and client-side search filtering. Previous issues with ConflictError and ValidationError are resolved. |
| packages/api/src/adapter-aws/AwsIamAdapter.test.ts | Comprehensive unit tests covering all adapter operations including pagination, search, not-found, conflict, validation, and error mapping. All paths are exercised with a fake client. |
| packages/api/src/cloud-spi/iamSchema.ts | Defines IAM schema with correctly consolidated username pattern (length baked into regex) and separate path validation. Capabilities, columns, and filters follow the SPI contract. |
| packages/api/src/cloudProxy.ts | AwsIamAdapter is correctly registered with the per-account IAM client. No issues. |
| packages/api/src/cloud-spi/serviceCatalog.ts | identity service added to SERVICE_CATALOG under Security group with order:5. iconKey 'iam' will fall back to a generic icon if unmapped, which is acceptable. |
| packages/frontend/src/components/DynamicResourceView.tsx | Only adds a "Create user" label for aws:identity in resourceCreateLabel. No bespoke panel required; the generic DynamicFormRenderer handles the IAM create form correctly. |
Sequence Diagram
sequenceDiagram
participant Browser as Browser (React :4500)
participant API as Hono API (:4501)
participant Registry as CloudAdapterRegistry
participant Adapter as AwsIamAdapter
participant IAM as AWS IAM SDK (:4566)
Browser->>API: GET /clouds/aws/identity/schema
API->>Registry: adapter("aws:identity").schema()
Registry->>Adapter: schema()
Adapter-->>Browser: awsIamSchema (fields, columns, capabilities)
Browser->>API: "GET /clouds/aws/identity/resources?search=alice"
API->>Registry: "adapter("aws:identity").list({search})"
Registry->>Adapter: "list({search})"
loop paginate (max 50 pages)
Adapter->>IAM: "ListUsersCommand({Marker})"
IAM-->>Adapter: "{Users[], IsTruncated, Marker}"
end
Adapter-->>Browser: CloudResource[] (client-side filtered)
Browser->>API: "POST /clouds/aws/identity/resources {userName, path}"
API->>Registry: adapter("aws:identity").create(input)
Adapter->>Adapter: "validate userName & path"
Adapter->>IAM: "CreateUserCommand({UserName, Path})"
IAM-->>Adapter: "{User}"
Adapter-->>Browser: CloudResource
Browser->>API: DELETE /clouds/aws/identity/resources/:id
API->>Registry: adapter("aws:identity").delete(id)
Adapter->>IAM: "DeleteUserCommand({UserName: id})"
alt user not found
IAM-->>Adapter: NoSuchEntityException
Adapter-->>Browser: 404 NotFoundError
else user has attached resources
IAM-->>Adapter: DeleteConflictException
Adapter-->>Browser: 409 ConflictError (with guidance)
else success
IAM-->>Adapter: "{}"
Adapter-->>Browser: 204
end
Reviews (10): Last reviewed commit: "chore: merge upstream main" | Re-trigger Greptile
hectorvent
left a comment
There was a problem hiding this comment.
Thanks @thomhurst. This is the only AWS adapter in the repo that actually paginates, and you covered the IsTruncated loop with a real two-page test rather than a claim. Complete registration, honest region: null, route tests included. Strongest wiring of the batch.
Before merge
- CI just started (first-contribution gate); needs green. Merge order vs #153 is ours; if it lands after,
identitycollapses into a single catalog row, which is the cheap path. - Smallest real fix worth doing now:
delete()on a missing user surfaces as 502. IAM'sNoSuchEntityExceptionmessage doesn't contain "not found", so it dodges the normalizer. You already addederrorNamedispatch; two more lines map it to 404.
Nits: the IAM-specific DeleteConflictException copy sits in the generic normalizer, better thrown typed from the adapter; a max-pages guard on the ListUsers loop; the username and path rules exist in schema and adapter with nothing keeping them in sync; template Type and Area boxes.
Thanks again!
|
Thanks @hectorvent — all addressed in 1abc1f3:
Template Type and Area boxes are filled. Merge order vs #153 is yours — if it lands first I'll collapse |
apigatewaySchemaFor was dead code left over from the pre-floci-io#153 schema-dispatch pattern, where CloudProxyService.schema() called a per-service *SchemaFor(cloud) wrapper via an if/else chain. That dispatch no longer exists — schema() now calls adapter.schema() directly, so only awsApiGatewaySchema() is ever used. Confirmed by checking iamSchema.ts, written fresh against the current catalog architecture during floci-io#145's rebase: it exports no equivalent wrapper, just the cloud-specific schema function consumed directly by the adapter. The wrapper isn't a convention to preserve, it's leftover cruft. Flagged by automated review on floci-io#169.
There was a problem hiding this comment.
Thanks @thomhurst, this follow-up closes out everything from the last pass. bun.lock now carries the @aws-sdk/client-iam entry (that was the CI failure), NoSuchEntityException surfaces as a proper 404 via a typed error in the adapter, the delete conflict copy moved out of the shared normalizer into the adapter, the ListUsers loop has a page cap, and the user name and path rules are single-sourced from the schema. The merge onto current main also lands identity the new way: one catalog row in the Security group with no frontend nav edits. Nothing left on my list.
One heads up on timing: PR #162 (opened today) also adds an AWS IAM adapter. That collision is on us for not marking #79 as claimed once your PR was open, not on either of you, so I will pick a direction. Your branch being current with main and already through review should make that call straightforward.
CI just needs the workflow runs approved again after the new push; once green this looks ready from my side. Thanks for the fast turnaround!
apigatewaySchemaFor was dead code left over from the pre-floci-io#153 schema-dispatch pattern, where CloudProxyService.schema() called a per-service *SchemaFor(cloud) wrapper via an if/else chain. That dispatch no longer exists — schema() now calls adapter.schema() directly, so only awsApiGatewaySchema() is ever used. Confirmed by checking iamSchema.ts, written fresh against the current catalog architecture during floci-io#145's rebase: it exports no equivalent wrapper, just the cloud-specific schema function consumed directly by the adapter. The wrapper isn't a convention to preserve, it's leftover cruft. Flagged by automated review on floci-io#169.
6cd839c to
efd511a
Compare
apigatewaySchemaFor was dead code left over from the pre-floci-io#153 schema-dispatch pattern, where CloudProxyService.schema() called a per-service *SchemaFor(cloud) wrapper via an if/else chain. That dispatch no longer exists — schema() now calls adapter.schema() directly, so only awsApiGatewaySchema() is ever used. Confirmed by checking iamSchema.ts, written fresh against the current catalog architecture during floci-io#145's rebase: it exports no equivalent wrapper, just the cloud-specific schema function consumed directly by the adapter. The wrapper isn't a convention to preserve, it's leftover cruft. Flagged by automated review on floci-io#169.
hectorvent
left a comment
There was a problem hiding this comment.
Thank you for the rebases and for typing the empty-create path as a RuntimeError while you were in there; that closes the last bare Error in the adapter. I rebuilt the stack at c52dc26: creating a user returns the ARN and user id, a duplicate is a typed 409, a bad name is a typed 400, and the table and "Create user" button render from the schema. CI is green and the branch is mergeable.
#162 overlaps with this one, and picking between them is mine to do; I will follow up there. No blockers from my side.
|
🎉 This PR is included in version 0.4.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
# [0.4.0](floci-io/floci-ui@0.3.0...0.4.0) (2026-09-01) ### Bug Fixes * **ec2:** include catalog AMIs in launch selector ([floci-io#191](floci-io#191)) ([b72135d](floci-io@b72135d)) ### Features * **aws:** add CloudFormation adapter to Cloud Explorer ([floci-io#184](floci-io#184)) ([f3d6105](floci-io@f3d6105)), closes [floci-io#81](floci-io#81) [floci-io#75](floci-io#75) [floci-io#81](floci-io#81) [floci-io#75](floci-io#75) [floci-io#81](floci-io#81) * **aws:** add EventBridge explorer ([floci-io#146](floci-io#146)) ([42944e9](floci-io@42944e9)), closes [floci-io#85](floci-io#85) * **aws:** add IAM to Cloud Explorer ([floci-io#145](floci-io#145)) ([fc50d19](floci-io@fc50d19)), closes [floci-io#79](floci-io#79) * **aws:** add Secrets Manager resource adapter ([floci-io#193](floci-io#193)) ([4811866](floci-io@4811866)) * **azure:** add databases and split Cosmos NoSQL ([floci-io#149](floci-io#149)) ([2e704f4](floci-io@2e704f4)), closes [floci-io#92](floci-io#92) [floci-io#67](floci-io#67) [floci-io/floci-az#138](floci-io/floci-az#138) [floci-io#143](floci-io#143) * **azure:** add Service Bus explorer ([floci-io#144](floci-io#144)) ([a9f0d06](floci-io@a9f0d06)), closes [floci-io#89](floci-io#89) * **eks:** Manage nodegroups and Fargate profiles via Cloud Proxy ([floci-io#194](floci-io#194)) ([3091cc9](floci-io@3091cc9)), closes [floci-io#106](floci-io#106) * **loadbalancing:** add an AWS Elastic Load Balancing adapter ([floci-io#168](floci-io#168)) ([10d4298](floci-io@10d4298)), closes [floci-io#162](floci-io#162) [floci-io#162](floci-io#162) [floci-io#156](floci-io#156) * **messaging:** add a messaging category with SQS and Pub/Sub ([floci-io#157](floci-io#157)) ([dfa6d1c](floci-io@dfa6d1c)), closes [floci-io#155](floci-io#155) [floci-io#155](floci-io#155) [floci-io#156](floci-io#156) [floci-io#156](floci-io#156) * **secretsmanager:** add JSON key-value editor for secret values ([floci-io#195](floci-io#195)) ([8e88961](floci-io@8e88961)), closes [floci-io#151](floci-io#151) * **ses:** Add AWS SES mailbox to Cloud Explorer ([floci-io#196](floci-io#196)) ([6389a56](floci-io@6389a56)), closes [floci-io#130](floci-io#130)
Summary
identityCloud Explorer category and Identity sidebar entryType of change
fix:)feat:)feat!:orfix!:)Area
packages/frontend)packages/api)Verification
pnpm lintpnpm type-checkpnpm test— 173 tests passedpnpm buildidentityavailable and/cloud-explorer/aws/identityreturns 200:4566)Checklist
pnpm lint,pnpm type-check,pnpm test, andpnpm buildpass locallybun testinpackages/api)Closes #79