Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion docs/resources/membership.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,27 @@ resource "github_membership" "membership_for_some_user" {
}
```

## Example Usage with Downgrade on Destroy

```terraform
# Downgrade a member to an outside collaborator when the resource is destroyed
resource "github_membership" "outside_collaborator_on_destroy" {
username = "SomeUser"
role = "member"

downgrade_on_destroy = true
downgrade_to = "outside_collaborator"
}
```

## Argument Reference

The following arguments are supported:

- `username` - (Required) The user to add to the organization.
- `role` - (Optional) The role of the user within the organization. Must be one of `member` or `admin`. Defaults to `member`. `admin` role represents the `owner` role available via GitHub UI.
- `downgrade_on_destroy` - (Optional) Defaults to `false`. If set to true, when this resource is destroyed, the member will not be removed from the organization. Instead, the member's role will be downgraded to 'member'.
- `downgrade_on_destroy` - (Optional) Defaults to `false`. Instead of removing the member from the org, you can choose to downgrade their membership when this resource is destroyed. This is useful when wanting to downgrade admins while keeping them in the organization, or to downgrade members while keeping their access to public repositories intact.
- `downgrade_to` - (Optional) The target membership state when `downgrade_on_destroy` is true. Must be one of `member` or `outside_collaborator`. Defaults to `member`.

## Import

Expand Down
8 changes: 8 additions & 0 deletions examples/resources/membership/example_2.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Downgrade a member to an outside collaborator when the resource is destroyed
resource "github_membership" "outside_collaborator_on_destroy" {
username = "SomeUser"
role = "member"

downgrade_on_destroy = true
downgrade_to = "outside_collaborator"
}
42 changes: 36 additions & 6 deletions github/resource_github_membership.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

const (
membershipDowngradeToMember = "member"
membershipDowngradeToOutsideCollaborator = "outside_collaborator"
membershipStateActive = "active"
)

func resourceGithubMembership() *schema.Resource {
return &schema.Resource{
CreateContext: resourceGithubMembershipCreateOrUpdate,
Expand Down Expand Up @@ -45,7 +51,14 @@ func resourceGithubMembership() *schema.Resource {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "Instead of removing the member from the org, you can choose to downgrade their membership to 'member' when this resource is destroyed. This is useful when wanting to downgrade admins while keeping them in the organization",
Description: "Instead of removing the member from the org, you can choose to downgrade their membership when this resource is destroyed. This is useful when wanting to downgrade admins while keeping them in the organization, or to downgrade members while keeping their access to public repositories intact.",
},
"downgrade_to": {
Type: schema.TypeString,
Optional: true,
ValidateDiagFunc: validateValueFunc([]string{membershipDowngradeToMember, membershipDowngradeToOutsideCollaborator}),
Default: membershipDowngradeToMember,
Description: "The target membership state when downgrade_on_destroy is true. Must be one of 'member' or 'outside_collaborator'.",
},
},
}
Expand All @@ -64,6 +77,9 @@ func resourceGithubMembershipCreateOrUpdate(ctx context.Context, d *schema.Resou
roleName := d.Get("role").(string)
if !d.IsNewResource() {
ctx = context.WithValue(ctx, ctxId, d.Id())
if !d.HasChange("role") {
return resourceGithubMembershipRead(ctx, d, meta)
}
}

_, resp, err := client.Organizations.EditOrgMembership(ctx,
Expand Down Expand Up @@ -148,7 +164,10 @@ func resourceGithubMembershipDelete(ctx context.Context, d *schema.ResourceData,

username := d.Get("username").(string)
downgradeOnDestroy := d.Get("downgrade_on_destroy").(bool)
downgradeTo := "member"
downgradeTo, ok := d.Get("downgrade_to").(string)
if !ok || downgradeTo == "" {
downgradeTo = membershipDowngradeToMember
}

if downgradeOnDestroy {
tflog.Info(ctx, fmt.Sprintf("Downgrading '%s' membership for '%s' to '%s'", orgName, username, downgradeTo), map[string]any{
Expand Down Expand Up @@ -176,7 +195,7 @@ func resourceGithubMembershipDelete(ctx context.Context, d *schema.ResourceData,
return diag.FromErr(err)
}

if *membership.Role == downgradeTo {
if downgradeTo == membershipDowngradeToMember && membership.GetRole() == downgradeTo {
tflog.Info(ctx, fmt.Sprintf("Not downgrading '%s' membership for '%s' because they are already '%s'", orgName, username, downgradeTo), map[string]any{
"org_name": orgName,
"username": username,
Expand All @@ -185,9 +204,20 @@ func resourceGithubMembershipDelete(ctx context.Context, d *schema.ResourceData,
return nil
}

_, _, err = client.Organizations.EditOrgMembership(ctx, username, orgName, &github.Membership{
Role: new(downgradeTo),
})
if downgradeTo == membershipDowngradeToOutsideCollaborator && membership.GetState() != membershipStateActive {
return diag.Errorf("cannot downgrade %q to outside collaborator because organization membership is %q, not active; the user must accept the organization invitation first", username, membership.GetState())
}

switch downgradeTo {
case membershipDowngradeToMember:
_, _, err = client.Organizations.EditOrgMembership(ctx, username, orgName, &github.Membership{
Role: new(downgradeTo),
})
case membershipDowngradeToOutsideCollaborator:
_, err = client.Organizations.ConvertMemberToOutsideCollaborator(ctx, orgName, username)
default:
return diag.Errorf("%s is an invalid value for argument downgrade_to", downgradeTo)
}
} else {
tflog.Info(ctx, fmt.Sprintf("Revoking '%s' membership for '%s'", orgName, username), map[string]any{
"org_name": orgName,
Expand Down
Loading
Loading