feat: Add ResourceShareAccepter CRD for accepting RAM share invita… - #43
feat: Add ResourceShareAccepter CRD for accepting RAM share invita…#43rhysxevans wants to merge 7 commits into
Conversation
|
Hi @rhysxevans. Thanks for your PR. I'm waiting for a aws-controllers-k8s member to verify that this patch is reasonable to test. If it is, they should reply with Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
michaelhtm
left a comment
There was a problem hiding this comment.
Thanks for the contribution @rhysxevans
Should we consider making ResourceShareAccepter a functionality of the ResourceShare CRD?
Similar to what we do with EKS AccessPolicies
|
Hi Thanks for reviewing, I am ultimately happy to take direction from you. I do however feel that there may be a subtle difference
I don't know if my interpretation is in any way relevant though. Let me know your thoughts Thanks |
This commit integrates resource share invitation acceptance functionality into the existing ResourceShare CRD, enabling dual-mode operation for cross-account AWS RAM sharing scenarios. ## Key Changes ### 1. Dual-Mode ResourceShare CRD - Added acceptInvitation field to enable Accept Mode - Made name field optional (required in Create Mode, not needed in Accept Mode) - Added invitation-specific status fields (invitationARN, invitationStatus, etc.) - Added resources field to status for tracking shared resources ### 2. Accept Mode Implementation - Implemented sdkAcceptInvitation() for accepting pending invitations - Implemented sdkFindAcceptedInvitation() for finding existing accepted invitations - Modified sdkCreate() to route to acceptance logic when acceptInvitation is set - Modified sdkFind() to route to invitation lookup in Accept Mode - Added validation to ensure name is provided in Create Mode ### 3. Deletion Handling - Implemented sdkRejectOrLeaveShare() for proper cleanup in Accept Mode - PENDING invitations: Rejected using RejectResourceShareInvitation - ACCEPTED invitations: Left using DisassociateResourceShare - Added graceful handling for OperationNotPermittedException (some resource types like Network Firewall don't support self-disassociation) ### 4. Deprecated API Replacement - Removed usage of deprecated ResourceShareAssociations field - Implemented populateInvitationResources() using ListPendingInvitationResources API - Added logging and metrics for resource population - Handles invitations with no resources gracefully ### 5. Documentation - Added comprehensive function-level documentation for Accept Mode - Documented AWS resource type limitations (Network Firewall, etc.) - Explained deletion behavior and OperationNotPermittedException handling - Added inline comments for error handling logic ### 6. E2E Tests - Created test_resource_share_accept_mode.py with Accept Mode tests - Added test for name validation in Create Mode - Added test templates for Accept Mode scenarios - Updated ram_resource_share.py with invitation helper functions ### 7. Examples - Added resource-share-integrated-modes.yaml with usage examples - Documented Create Mode and Accept Mode workflows - Included cross-account sharing examples ## Testing - ✅ Accept invitation with resources (Network Firewall) - ✅ Resources populated in status using new API - ✅ Deletion with OperationNotPermittedException handling - ✅ Name validation in Create Mode - ✅ All status fields populated correctly ## AWS Limitations Handled Some AWS resource types (e.g., Network Firewall) do not support self-disassociation. For these resources, the Kubernetes resource is deleted successfully, but the AWS invitation remains ACCEPTED until the share owner removes the principal. This is expected AWS behavior and is handled gracefully with appropriate logging. Addresses reviewer feedback on PR aws-controllers-k8s#43 regarding integrated vs separate CRD approach.
|
FYI: @michaelhtm I have created #44 as a response to your comment #43 (review) |
You are right, thanks for clarifying the differences. Another quirk i see is, an Accepter is not necessarily a resource, so it feels weird to have a CRD for it, although i do see Terraform does support it |
|
Hi, yep, apologies around not using the code generator. Once we have a direction of travel, I will look at re-implementing via the code generator |
|
Hi @michaelhtm @a-hilaly @knottnt any thoughts ? |
|
Hey @rhysxevans |
|
Hi @michaelhtm Can you confirm which way you would like to go with the CRD. the way outlined in this PR (separate) or in the current CRD #44 ? I can then look at the the code generator for the relevant solution ? Thanks |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: rhysxevans The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
Replaces the hand-written ResourceShareAccepter implementation with the standard ACK code-generation model, addressing reviewer feedback on aws-controllers-k8s#43. Naming: renamed from ResourceShareAccepter (verb) to ResourceShareInvitation (noun), matching the AWS API entity returned by GetResourceShareInvitations / AcceptResourceShareInvitation. This follows the same convention route53resolver-controller used for ResolverRuleAssociation. UX: the user provides spec.resourceShareInvitationARN directly. The controller calls AcceptResourceShareInvitation on Create, GetResourceShareInvitations on Read, and DisassociateResourceShare on Delete. generator.yaml: - operations: AcceptResourceShareInvitation (Create), GetResourceShareInvitations (Read_Many), DisassociateResourceShare (Delete) - ResourceShareInvitationArn: primary key, immutable, required - Status and ResourceShareArn: read-only with print columns - synced.when: Status.Status == ACCEPTED - terminal_codes: MalformedArn, AlreadyAccepted, AlreadyRejected, Expired - 404 mapping: ResourceShareInvitationArnNotFoundException templates/hooks/resource_share_invitation/: - sdk_read_many_post_build_request: filter list by spec ARN - sdk_delete_post_build_request: set Principals from Status.ReceiverAccountID E2E tests renamed to *_resource_share_invitation. The "no pending invitation" test is replaced with a "malformed ARN terminal" test, which exercises the terminal-error path under the new model via MalformedArnException. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
|
In apiVersion: ram.services.k8s.aws/v1alpha1
kind: ResourceShareInvitation
metadata:
name: test-ack
namespace: default
spec:
resourceShareInvitationARN: arn:aws:ram:eu-west-1:512345678910:resource-share-invitation/e014ac15-6db0-4bac-8545-6aase8db915ccOut apiVersion: ram.services.k8s.aws/v1alpha1
kind: ResourceShareInvitation
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"ram.services.k8s.aws/v1alpha1","kind":"ResourceShareInvitation","metadata":{"annotations":{},"name":"test-ack","namespace":"default"},"spec":{"resourceShareInvitationARN":"arn:aws:ram:eu-west-1:512345678910:resource-share-invitation/e014ac15-6db0-4bac-8545-6aase8db915cc"}}
creationTimestamp: "2026-06-20T09:44:05Z"
finalizers:
- finalizers.ram.services.k8s.aws/ResourceShareInvitation
generation: 1
name: test-ack
namespace: default
resourceVersion: "2277"
uid: f94f923c-618c-4802-8cd9-c49b9cae26bf
spec:
resourceShareInvitationARN: arn:aws:ram:eu-west-1:512345678910:resource-share-invitation/e014ac15-6db0-4bac-8545-6aase8db915cc
status:
ackResourceMetadata:
arn: arn:aws:ram:eu-west-1:512345678910:resource-share-invitation/e014ac15-6db0-4bac-8545-6aase8db915cc
ownerAccountID: "00012345678910"
partition: aws
region: eu-west-1
conditions:
- lastTransitionTime: "2026-06-20T09:54:28Z"
message: Resource synced successfully
reason: ""
status: "True"
type: ACK.ResourceSynced
- lastTransitionTime: "2026-06-20T09:54:28Z"
message: Resource synced successfully
reason: ""
status: "True"
type: Ready
invitationTimestamp: "2026-06-20T09:54:28Z"
receiverAccountID: "00012345678910"
resourceShareARN: arn:aws:ram:eu-west-1:512345678910:resource-share/e014ac15-6db0-4bac-8545-6aase8db915cc
resourceShareName: test-ack
senderAccountID: "512345678910"
status: ACCEPTED |
Implements a custom ACK resource to accept AWS RAM (Resource Access Manager)
share invitations. This addresses the limitation that the generated controller
only supports creating outgoing shares, not accepting incoming invitations.
Implementation details:
- Created ResourceShareAccepter CRD following Terraform's aws_ram_resource_share_accepter pattern
- Maps non-standard AWS RAM operations to Kubernetes resource lifecycle:
* Create: Find pending invitation by ShareARN and accept it
* Read: Get invitation status from AWS
* Update: No-op (invitations are immutable)
* Delete: Disassociate from the share (leave it)
- Implements full ACK resource manager interface with custom SDK operations
- Uses finalizer-based resource management for proper lifecycle handling
- Includes Helm chart integration for easy deployment
New CRD: ResourceShareAccepter (ram.services.k8s.aws/v1alpha1)
Spec:
- shareARN: ARN of the resource share to accept
Status:
- invitationARN: ARN of the accepted invitation
- invitationStatus: Current status (PENDING, ACCEPTED, REJECTED, EXPIRED)
- senderAccountID: Account that sent the invitation
- receiverAccountID: Account receiving the invitation
- shareName: Name of the resource share
Files changed:
- apis/v1alpha1/resource_share_invitation.go: CRD type definitions
- pkg/resource/resource_share_invitation/: Complete resource implementation
- config/crd/bases/ram.services.k8s.aws_resourceshareaccepters.yaml: Generated CRD
- helm/crds/ram.services.k8s.aws_resourceshareaccepters.yaml: Helm CRD
- cmd/controller/main.go: Register ResourceShareAccepter resource
- helm/values.yaml: Add ResourceShareAccepter to reconcile resources
- config/crd/kustomization.yaml: Include new CRD in kustomization
Testing:
- Verified controller builds successfully
- Tested locally with AWS profile authentication
- Confirmed resource reconciliation and error handling
- Validated CRD generation and Helm integration
…eAccepter
This commit fixes two critical issues with the ResourceShareAccepter controller:
1. Terminal Error Handling
- Wrap "no pending invitation" error with ackerr.NewTerminalError() to prevent
infinite retry loops when no invitation exists
- Add updateConditions() method to properly detect and handle Terminal/Recoverable errors
- Add terminalAWSError() method to identify AWS errors that should be terminal
(e.g., MalformedArnException)
- Update onError() in manager.go to call updateConditions() and return ackerr.Terminal
when Terminal condition is set
2. Status Population
- Add setStatusDefaults() method to initialize resource status metadata
- Call setStatusDefaults() in sdkCreate() before setting invitation-specific fields
- Call setStatusDefaults() in sdkFind() before processing invitations
- Ensures ackResourceMetadata (region, ownerAccountID, ARN) and conditions array
are properly initialized
3. Test Infrastructure
- Add e2e integration test for ResourceShareAccepter
- Test validates Terminal condition is set when no pending invitation exists
- Add helper functions for working with RAM share invitations
- Add test documentation and resource templates
Changes to pkg/resource/resource_share_invitation/sdk.go:
- Import corev1 for condition handling
- Wrap "no pending invitation" error with ackerr.NewTerminalError() (line 127)
- Add setStatusDefaults() method (lines 254-267)
- Add updateConditions() method (lines 270-346)
- Add terminalAWSError() method (lines 349-364)
- Call setStatusDefaults() in sdkFind() (line 72)
- Call setStatusDefaults() in sdkCreate() (line 147)
Changes to pkg/resource/resource_share_invitation/manager.go:
- Import ackerr package
- Replace onError() method to properly handle Terminal conditions (lines 185-207)
Test files added:
- test/e2e/tests/test_resource_share_accepter.py
- test/e2e/ram_resource_share_accepter.py
- test/e2e/resources/ram_resource_share_accepter.yaml
- test/e2e/README.md
Fixes:
- Controller no longer retries indefinitely when no invitation exists
- Kubernetes resource status is now fully populated with all fields
- Terminal errors are properly detected and stop reconciliation
- Integration tests validate expected behavior
Replaces the hand-written ResourceShareAccepter implementation with the standard ACK code-generation model, addressing reviewer feedback on aws-controllers-k8s#43. Naming: renamed from ResourceShareAccepter (verb) to ResourceShareInvitation (noun), matching the AWS API entity returned by GetResourceShareInvitations / AcceptResourceShareInvitation. This follows the same convention route53resolver-controller used for ResolverRuleAssociation. UX: the user provides spec.resourceShareInvitationARN directly. The controller calls AcceptResourceShareInvitation on Create, GetResourceShareInvitations on Read, and DisassociateResourceShare on Delete. generator.yaml: - operations: AcceptResourceShareInvitation (Create), GetResourceShareInvitations (Read_Many), DisassociateResourceShare (Delete) - ResourceShareInvitationArn: primary key, immutable, required - Status and ResourceShareArn: read-only with print columns - synced.when: Status.Status == ACCEPTED - terminal_codes: MalformedArn, AlreadyAccepted, AlreadyRejected, Expired - 404 mapping: ResourceShareInvitationArnNotFoundException templates/hooks/resource_share_invitation/: - sdk_read_many_post_build_request: filter list by spec ARN - sdk_delete_post_build_request: set Principals from Status.ReceiverAccountID E2E tests renamed to *_resource_share_invitation. The "no pending invitation" test is replaced with a "malformed ARN terminal" test, which exercises the terminal-error path under the new model via MalformedArnException. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
Add two hooks needed to make the Create (Accept) lifecycle work end to end against the real RAM API: - sdk_create_post_build_request: populate AcceptResourceShareInvitation input from Spec.ResourceShareInvitationARN. The field is marked is_primary_key so the generator otherwise reads it from Status.ACKResourceMetadata.ARN, which is empty on first Create. - sdk_read_many_post_set_output: return NotFound when the invitation status is PENDING, so the runtime invokes sdkCreate and accepts the invitation rather than treating it as already-existing. Validated live against a real cross-account invitation: Apply -> AcceptResourceShareInvitation succeeds, status flips to ACCEPTED; Delete -> DisassociateResourceShare succeeds and AWS confirms the receiver is no longer associated.
AWS purges accepted invitations from GetResourceShareInvitations after a retention window (7 days for most resource types, 12 hours for some). After that the call returns an empty list even though the account is still associated with the share. The runtime would then treat the resource as not-yet-created and call AcceptResourceShareInvitation again, which fails terminally with ResourceShareInvitationAlreadyAcceptedException and flips a healthy resource into a permanent Terminal condition. Add a sdk_read_many_post_request hook that, when the invitation list is empty but the share ARN was previously recorded, confirms the association still exists via GetResourceShares(OTHER-ACCOUNTS) and reports the resource as still ACCEPTED. If the share is no longer returned (genuine disassociation) the read falls through to NotFound as before.
Deleting a ResourceShareInvitation custom resource calls DisassociateResourceShare, which detaches the receiver account from the share and revokes access to all shared resources. Add a documentation.yaml override that appends a WARNING to the resourceShareInvitationARN field so the caveat surfaces in the generated Go type, the CRD description, and kubectl explain. Regenerate the affected API type and CRD.
The invitation ARN was both a user-supplied desired-state input and the resource's primary key. That conflation is fragile: the invitation ARN is purged ~7 days after acceptance, so the designated identity disappears and adoption keyed on it cannot resolve. Move is_primary_key from ResourceShareInvitationArn to the read-only ResourceShareArn, which is service-assigned from the accept response and remains resolvable via GetResourceShares after the invitation is purged. The invitation ARN stays a required, immutable spec input. Adoption and SetIdentifiers now key on resourceShareARN. Regenerate the affected code.
a7abc91 to
2965b9b
Compare
|
Hi, just checking in on this one. Is it still actively being worked on, or blocked on something? We have a use case waiting on this and would be happy to help move it forward if useful. Thanks for the work here! |
|
Hey @HansG89 @rhysxevans |
|
Hi @michaelhtm appreciate you taking another look at this. Genuinely curious about the control-plane/dataplane distinction here, since From operations:
AssociateResolverRule:
resource_name: ResolverRuleAssociation
operation_type: Create
DisassociateResolverRule:
resource_name: ResolverRuleAssociation
operation_type: Delete
GetResolverRuleAssociation:
resource_name: ResolverRuleAssociation
operation_type: Read_One
This is actually the exact pattern you pointed @rhysxevans toward back in June ( |
|
Hi, I have found another area where this sort of logic applies, account OU moves, https://docs.aws.amazon.com/cli/latest/reference/organizations/move-account.html, so it would be good to get some guidance. |
|
@a-hilaly @knottnt @michaelhtm, pinging directly for a decision on the CRD shape here. This has been open since February and stalled since the last exchange. On the "dataplane operation" concern: # route53resolver-controller/generator.yaml (main, merged via #65)
AssociateResolverRule:
resource_name: ResolverRuleAssociation
operation_type: Create
DisassociateResolverRule:
resource_name: ResolverRuleAssociation
operation_type: Delete
GetResolverRuleAssociation:
resource_name: ResolverRuleAssociation
operation_type: Read_OneSame for If @rhysxevans also flagged a second case with the same shape: Could we get a direct yes or no on whether a standalone |
Adds a
ResourceShareInvitationCRD that lets a receiver account accept incoming AWS RAM share invitations declaratively, addressing aws-controllers-k8s/community#2786.Per the reviewer feedback in #43 (comment), this PR has been reworked to use the standard ACK code-generation model rather than a hand-written resource manager.
Approach
Follows the same pattern route53resolver-controller#65 used for
ResolverRuleAssociation: pick the noun the AWS API returns (rather than a verb), and map AWS lifecycle operations to ACK's Create/Read/Delete viagenerator.yaml.ResourceShareInvitation— matches the AWS API entity returned byGetResourceShareInvitations/AcceptResourceShareInvitation.resourceShareInvitationARNdirectly.AcceptResourceShareInvitationGetResourceShareInvitations(filtered by the spec ARN)DisassociateResourceShareStatus.Status == ACCEPTED.Hand-written surface area
Everything else is generated. The only hand-written inputs are:
generator.yaml: operation → resource mapping, field annotations (primary key / immutable / required, print columns),terminal_codes, 404 mapping (ResourceShareInvitationArnNotFoundException),synced.when.templates/hooks/resource_share_invitation/sdk_read_many_post_build_request.go.tpl: filtersGetResourceShareInvitationsby the spec ARN.templates/hooks/resource_share_invitation/sdk_delete_post_build_request.go.tpl: setsPrincipalsfromStatus.ReceiverAccountIDforDisassociateResourceShare.Testing
go build ./...andgo test ./...pass.make build-controller SERVICE=ram+scripts/build-controller-release.sh ramagainst code-generatorv0.59.1-7-g2970ca9(same code-generator commit currently used by route53resolver-controller).MalformedArnException.Issue #, if available: aws-controllers-k8s/community#2786
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.