Skip to content

feat: support inviting organization members by email in github_membership - #3661

Open
kazuki-hanai wants to merge 6 commits into
integrations:mainfrom
kazuki-hanai:kazuki-hanai/Add-email-invitation-to-github_membership
Open

kazuki-hanai wants to merge 6 commits into
integrations:mainfrom
kazuki-hanai:kazuki-hanai/Add-email-invitation-to-github_membership

Conversation

@kazuki-hanai

Copy link
Copy Markdown

Resolves #2197


Before the change?

github_membership can only address people by username (it uses EditOrgMembership), so there is no way to invite someone to the organization who does not have a GitHub account yet.

After the change?

github_membership accepts email as an alternative to username and sends an organization invitation (POST /orgs/{org}/invitations):

resource "github_membership" "invite" {
  email = "newmember@example.com"
  role  = "member"
}

The design is shaped by an API limitation: there is no endpoint to look up an invitation — or the member who later accepted it — by email. Concretely:

  • Create adopts an existing pending invitation for the same address, and cancels + re-sends it when the configured role differs (invitations cannot be edited). A lingering failed invitation is cancelled first, so it cannot block the new one (same motivation as [BUG] ensure failed membership invite can be deleted #3058).
  • Read scans pending invitations, then failed ones. A failed invitation is removed from state so it gets re-sent. If neither exists, the invitation was either accepted or cancelled externally — indistinguishable by email — so the resource stays in state instead of re-inviting an existing member on every apply. This trade-off is documented in the resource docs.
  • Destroy cancels the pending/failed invitation. If none exists, a warning is emitted because an invitee who already accepted keeps their organization access; from that point the membership should be managed via username.
  • email + downgrade_on_destroy = true is rejected at plan time (ConflictsWith cannot express this because an explicit downgrade_on_destroy = false would also trigger it).

Relation to #3464: that PR proposes a separate github_organization_invitation resource. This PR instead extends the existing resource, which is what #2197 asks for and follows the review guidance there that additions to existing resources are preferred over new resources.

Testing

  • Unit tests, go build, go vet, and both golangci-lint configs (default and strict-on-new-code) pass; docs regenerated with make generatedocs.
  • Added an acceptance test gated on a new GH_TEST_INVITATION_EMAIL env var (the address must not belong to an existing org member). I have not yet run the acceptance tests against a real organization.

Per the contributing guidelines: this PR was largely generated with an AI assistant (Claude Code), driven and reviewed iteratively by me.

Pull request checklist

  • Schema migrations have been created if needed — not applicable
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been reviewed and added / updated if needed (for bug fixes / features)

Does this introduce a breaking change?

  • Yes
  • No

username changes from Required to Optional with ExactlyOneOf, which keeps all existing configurations valid.


🤖 Generated with Claude Code

…ship

GitHub's membership API (EditOrgMembership) can only address people by
username, so github_membership could not invite someone who does not
have a GitHub account yet. Add an 'email' argument that uses the
organization invitation API (CreateOrgInvitation) instead.

Because there is no endpoint to look up an invitation (or the resulting
member) by email, the email flavour of the resource manages the
invitation lifecycle: pending invitations are adopted, cancelled on
destroy (including failed invitations), and re-sent when the role
changes. Once an invitation is accepted the resource keeps it in state
rather than re-inviting an existing member on every apply.

Closes integrations#2197

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown

👋 Hi, and thank you for this contribution!

This repo is maintained by GitHub and community members on a best-effort basis. We'll get to this as soon as we can.

You can help us prioritize by joining the discussion on open issues and PRs, sharing details on the changes you need, and reviewing other contributions.


🤖 This is an automated message.

@deiga

deiga commented Oct 11, 2026

Copy link
Copy Markdown
Collaborator

Duplicate of #3229

@deiga deiga marked this as a duplicate of #3229 Oct 11, 2026
@deiga deiga closed this Oct 11, 2026
@kazuki-hanai

Copy link
Copy Markdown
Author

Thanks for the triage @deiga. I understand this addresses the same issue, but I would like to ask you to reconsider which PR to keep:

Given that, closing the stalled PR and keeping this one open seems like the better outcome for #2197. I put real effort into this implementation and I am motivated to address review feedback quickly, so I would appreciate the chance to get it reviewed. If the team has firmly settled on the separate-resource approach instead, let me know and I will help review #3229.

@deiga

deiga commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator

@kazuki-hanai I think you raise an excellent point. And while I can't promise how quickly we can get your change merged, I'll re-open this and mark the others as stale

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Core invitation update, adoption, and failed-invitation reconciliation paths lack test coverage.

1 open finding
What changed in this PR

These provider review instructions are being used. This PR extends github_membership to manage organization invitations by email.

Changes:

  • Adds email-based invitation lifecycle handling.
  • Adds schema validation, documentation, and examples.
  • Adds acceptance coverage for creation, import, and deletion.
File Description
github/​resource_github_membership.go Implements email invitation lifecycle.
github/​resource_github_membership_test.go Adds invitation tests.
github/​acc_test.go Adds invitation email configuration.
examples/​resources/​membership/​example_2.tf Demonstrates email invitations.
templates/​resources/​membership.md.tmpl Documents the new behavior.
docs/​resources/​membership.md Regenerates user-facing documentation.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +133 to +137
Steps: []resource.TestStep{
{
Config: testAccGithubMembershipConfigEmail(testAccConf.testInvitationEmail),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(rn, "email", testAccConf.testInvitationEmail),

@deiga deiga left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial review

Comment thread github/resource_github_membership.go Outdated
Comment on lines +156 to +168
// Inviting an email that already has a pending invitation fails, so adopt
// an existing pending invitation when it matches the configured role.
invitation, err := findPendingOrgInvitationByEmail(ctx, owner, email)
if err != nil {
return diag.FromErr(err)
}

if invitation != nil && invitationRoleToMembershipRole(invitation.GetRole()) != roleName {
if _, err := owner.v3client.Organizations.CancelInvite(ctx, orgName, invitation.GetID()); err != nil {
return diag.FromErr(err)
}
invitation = nil
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: This sounds like something that should be left to the user with an import statement and not part of Create 🤔

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 3741f90. Create now just sends the invitation; a pre-existing invitation surfaces the API error, which points the user at terraform import. Cancel-and-reinvite only runs on role updates, since invitations cannot be edited.

Comment thread github/resource_github_membership.go Outdated
orgName := meta.(*Owner).name
_, username, err := parseID2(d.Id())
orgName := owner.name
_, usernameOrEmail, err := parseID2(d.Id())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: lets not parse the ID, but instead d.Get from the attributes

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 7d52b47. Read now dispatches on d.Get("email"). To keep imports working, a custom importer maps an email-shaped import ID onto the email attribute, so ID parsing stays confined to the importer.

})
}

func TestAccGithubMembershipEmailInvitation(t *testing.T) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Add tests as subtests of the one existing test

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 5429008. The email test is now a subtest of TestAccGithubMembership, and it gained a role-change step covering the update path.

Comment on lines 15 to +22
## Example Usage

{{ tffile "examples/resources/membership/example_1.tf" }}

Inviting by email address:

{{ tffile "examples/resources/membership/example_2.tf" }}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Could you update this to use the auto-discovery of example files?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d69b9e9. The template now uses .ExampleFiles, and the example files moved to examples/resources/github_membership/resource_*.tf to match the discovery convention.

@kazuki-hanai

Copy link
Copy Markdown
Author

Thank you for reopening this and for the quick review, @deiga! I will address the feedback shortly.

@kazuki-hanai

kazuki-hanai commented Oct 14, 2026 •

Copy link
Copy Markdown
Author

All review feedback addressed, split into one commit per review point (3741f90, 7d52b47, 5429008, d69b9e9). See the reply on each review thread for details.

kazuki-hanai and others added 4 commits October 14, 2026 23:07
Create now just sends the invitation; a pre-existing invitation surfaces
the API error and points the user at terraform import. Cancelling and
re-inviting only happens on role updates, since invitations cannot be
edited.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A custom importer maps an email-shaped import ID onto the email
attribute, so ID parsing stays confined to the importer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Also add a role-change step covering the cancel-and-reinvite update
path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Example files move to examples/resources/github_membership/resource_*.tf
to match the tfplugindocs discovery convention.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kazuki-hanai
kazuki-hanai force-pushed the kazuki-hanai/Add-email-invitation-to-github_membership branch from aaae5d9 to d69b9e9 Compare October 14, 2026 14:14
@kazuki-hanai
kazuki-hanai requested a review from deiga October 14, 2026 14:50

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

r/membership Type: Feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT]: github_membership invite using the email address

3 participants