Skip to content

Commit 32f3009

Browse files
authored
Resource: EMU External Groups Sync (#1094)
* Initial commit of testing emus * Initial commit of creating EMU group mapping * Add read and delete operations * Tweaks to make updating work * Refactor coercion into reusable function * Simplify case logic * Add import logic * Whitespace change to test commit verification * Rework importation logic a bit * Bump to go-github v43 in EMU resource * Removing some extraneous whitespaces * Import logic mostly working * Fully fix import logic * Add docs and doc link * Remove time.Sleep * Remove vendoring hack as bug has been fixed * Remove extraneous comment in examples/emu
1 parent 50320f5 commit 32f3009

File tree

9 files changed

+227
-5
lines changed

9 files changed

+227
-5
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ website/node_modules
1414
.vagrant/
1515
*.backup
1616
./*.tfstate
17+
**/*.terraform.lock.hcl
1718
.terraform/
1819
*.log
1920
*.bak

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ Once you have the repository cloned, there's a couple of additional steps you'll
4848
1. Write a test describing what you will fix. See [`github_label`](./github/resource_github_issue_label_test.go) for an example format.
4949
1. Run your test and observe it fail. Enabling debug output allows for observing the underlying requests and responses made as well as viewing state (search `STATE:`) generated during the acceptance test run.
5050
```sh
51-
TF_LOG=DEBUG TF_ACC=1 go test -v ./... -run ^TestAccGithubIssueLabel
51+
TF_LOG=DEBUG TF_ACC=1 go test -v ./... -run ^TestAccGithubIssueLabel
5252
```
5353
1. Align the resource's implementation to your test case and observe it pass:
5454
```sh
55-
TF_ACC=1 go test -v ./... -run ^TestAccGithubIssueLabel
55+
TF_ACC=1 go test -v ./... -run ^TestAccGithubIssueLabel
5656
```
5757

5858
Note that some resources still use a previous format that is incompatible with automated test runs, which depend on using the `skipUnlessMode` helper. When encountering these resources, tests are rewritten to the latest format.

examples/emu/main.tf

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
provider "github" {
2+
}
3+
4+
terraform {
5+
required_providers {
6+
github = {
7+
source = "integrations/github"
8+
}
9+
}
10+
}
11+
12+
resource "github_emu_group_mapping" "example_emu_group_mapping" {
13+
team_slug = "emu-test-team"
14+
group_id = 28836
15+
}

github/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ func Provider() terraform.ResourceProvider {
121121
"github_team_members": resourceGithubTeamMembers(),
122122
"github_team_repository": resourceGithubTeamRepository(),
123123
"github_team_sync_group_mapping": resourceGithubTeamSyncGroupMapping(),
124+
"github_emu_group_mapping": resourceGithubEMUGroupMapping(),
124125
"github_team": resourceGithubTeam(),
125126
"github_user_gpg_key": resourceGithubUserGpgKey(),
126127
"github_user_invitation_accepter": resourceGithubUserInvitationAccepter(),
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strconv"
7+
8+
"github.com/google/go-github/v43/github"
9+
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
10+
)
11+
12+
func resourceGithubEMUGroupMapping() *schema.Resource {
13+
return &schema.Resource{
14+
Create: resourceGithubEMUGroupMappingCreate,
15+
Read: resourceGithubEMUGroupMappingRead,
16+
Update: resourceGithubEMUGroupMappingUpdate,
17+
Delete: resourceGithubEMUGroupMappingDelete,
18+
Importer: &schema.ResourceImporter{
19+
State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
20+
id, err := strconv.Atoi(d.Id())
21+
if err != nil {
22+
return nil, err
23+
}
24+
if err := d.Set("group_id", id); err != nil {
25+
return nil, err
26+
}
27+
ctx := context.WithValue(context.Background(), ctxId, d.Id())
28+
client := meta.(*Owner).v3client
29+
orgName := meta.(*Owner).name
30+
group, _, err := client.Teams.GetExternalGroup(ctx, orgName, int64(id))
31+
if err != nil {
32+
return nil, err
33+
}
34+
if len(group.Teams) != 1 {
35+
return nil, fmt.Errorf("could not get team_slug from %v number of teams", len(group.Teams))
36+
}
37+
if err := d.Set("team_slug", group.Teams[0].TeamName); err != nil {
38+
return nil, err
39+
}
40+
d.SetId(fmt.Sprintf("teams/%s/external-groups", d.Id()))
41+
return []*schema.ResourceData{d}, nil
42+
},
43+
},
44+
Schema: map[string]*schema.Schema{
45+
"team_slug": {
46+
Type: schema.TypeString,
47+
Required: true,
48+
},
49+
"group_id": {
50+
Type: schema.TypeInt,
51+
Required: true,
52+
},
53+
"etag": {
54+
Type: schema.TypeString,
55+
Computed: true,
56+
},
57+
},
58+
}
59+
}
60+
61+
func resourceGithubEMUGroupMappingCreate(d *schema.ResourceData, meta interface{}) error {
62+
return resourceGithubEMUGroupMappingUpdate(d, meta)
63+
}
64+
65+
func resourceGithubEMUGroupMappingRead(d *schema.ResourceData, meta interface{}) error {
66+
err := checkOrganization(meta)
67+
if err != nil {
68+
return err
69+
}
70+
client := meta.(*Owner).v3client
71+
orgName := meta.(*Owner).name
72+
73+
id, ok := d.GetOk("group_id")
74+
if !ok {
75+
return fmt.Errorf("could not get group id from provided value")
76+
}
77+
id64, err := getInt64FromInterface(id)
78+
if err != nil {
79+
return err
80+
}
81+
82+
ctx := context.WithValue(context.Background(), ctxId, d.Id())
83+
84+
group, resp, err := client.Teams.GetExternalGroup(ctx, orgName, id64)
85+
if err != nil {
86+
return err
87+
}
88+
89+
d.Set("etag", resp.Header.Get("ETag"))
90+
d.Set("group", group)
91+
return nil
92+
}
93+
94+
func resourceGithubEMUGroupMappingUpdate(d *schema.ResourceData, meta interface{}) error {
95+
err := checkOrganization(meta)
96+
if err != nil {
97+
return err
98+
}
99+
client := meta.(*Owner).v3client
100+
orgName := meta.(*Owner).name
101+
ctx := context.WithValue(context.Background(), ctxId, d.Id())
102+
103+
teamSlug, ok := d.GetOk("team_slug")
104+
if !ok {
105+
return fmt.Errorf("could not get team slug from provided value")
106+
}
107+
108+
id, ok := d.GetOk("group_id")
109+
if !ok {
110+
return fmt.Errorf("could not get group id from provided value")
111+
}
112+
id64, err := getInt64FromInterface(id)
113+
if err != nil {
114+
return err
115+
}
116+
117+
eg := &github.ExternalGroup{
118+
GroupID: &id64,
119+
}
120+
121+
_, _, err = client.Teams.UpdateConnectedExternalGroup(ctx, orgName, teamSlug.(string), eg)
122+
if err != nil {
123+
return err
124+
}
125+
126+
d.SetId(fmt.Sprintf("teams/%s/external-groups", teamSlug))
127+
return resourceGithubEMUGroupMappingRead(d, meta)
128+
}
129+
130+
func resourceGithubEMUGroupMappingDelete(d *schema.ResourceData, meta interface{}) error {
131+
err := checkOrganization(meta)
132+
if err != nil {
133+
return err
134+
}
135+
client := meta.(*Owner).v3client
136+
orgName := meta.(*Owner).name
137+
138+
teamSlug, ok := d.GetOk("team_slug")
139+
if !ok {
140+
return fmt.Errorf("could not parse team slug from provided value")
141+
}
142+
143+
ctx := context.WithValue(context.Background(), ctxId, d.Id())
144+
145+
_, err = client.Teams.RemoveConnectedExternalGroup(ctx, orgName, teamSlug.(string))
146+
if err != nil {
147+
return err
148+
}
149+
return nil
150+
}
151+
152+
func getInt64FromInterface(val interface{}) (int64, error) {
153+
var id64 int64
154+
switch val := val.(type) {
155+
case int64:
156+
id64 = val
157+
case int:
158+
id64 = int64(val)
159+
case string:
160+
var err error
161+
id64, err = strconv.ParseInt(val, 10, 64)
162+
if err != nil {
163+
return 0, fmt.Errorf("could not parse id from string: %v", err)
164+
}
165+
default:
166+
return 0, fmt.Errorf("unexpected type converting to int64 from interface")
167+
}
168+
return id64, nil
169+
}

github/util.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const (
2020

2121
func checkOrganization(meta interface{}) error {
2222
if !meta.(*Owner).IsOrganization {
23-
return fmt.Errorf("This resource can only be used in the context of an organization, %q is a user.", meta.(*Owner).name)
23+
return fmt.Errorf("this resource can only be used in the context of an organization, %q is a user", meta.(*Owner).name)
2424
}
2525

2626
return nil

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ
4242
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
4343
github.com/bombsimon/wsl/v3 v3.0.0 h1:w9f49xQatuaeTJFaNP4SpiWSR5vfT6IstPtM62JjcqA=
4444
github.com/bombsimon/wsl/v3 v3.0.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc=
45-
github.com/bradleyfalzon/ghinstallation/v2 v2.0.3/go.mod h1:tlgi+JWCXnKFx/Y4WtnDbZEINo31N5bcvnCoqieefmk=
4645
github.com/bradleyfalzon/ghinstallation/v2 v2.0.4/go.mod h1:B40qPqJxWE0jDZgOR1JmaMy+4AY1eBP+IByOvqyAKp0=
4746
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
4847
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
@@ -158,7 +157,6 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
158157
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
159158
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
160159
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
161-
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
162160
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
163161
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
164162
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
layout: "github"
3+
page_title: "GitHub: github_emu_group_mapping"
4+
description: |-
5+
Manages mappings between external groups for enterprise managed users.
6+
---
7+
8+
# github_emu_group_mapping
9+
10+
This resource manages mappings between external groups for enterprise managed users and GitHub teams. It wraps the API detailed [here](https://docs.github.com/en/rest/reference/teams#external-groups). Note that this is a distinct resource from `github_team_sync_group_mapping`. `github_emu_group_mapping` is special to the Enterprise Managed User (EMU) external group feature, whereas `github_team_sync_group_mapping` is specific to Identity Provider Groups.
11+
12+
## Example Usage
13+
14+
```hcl
15+
resource "github_emu_group_mapping" "example_emu_group_mapping" {
16+
team_slug = "emu-test-team" # The GitHub team name to modify
17+
group_id = 28836 # The group ID of the external group to link
18+
}
19+
20+
# Note that here GITHUB_OWNER and GITHUB_TOKEN have been set in the environment.
21+
```
22+
23+
## Argument Reference
24+
25+
The following arguments are supported:
26+
* `team_slug` - (Required) Slug of the GitHub team
27+
* `group_id` - (Required) Integer corresponding to the external group ID to be linked
28+
29+
## Import
30+
31+
GitHub EMU External Group Mappings can be imported using the external `group_id`, e.g.
32+
33+
```
34+
$ terraform import github_emu_group_mapping.example_emu_group_mapping 28836
35+
```

website/github.erb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@
169169
<li>
170170
<a href="/docs/providers/github/r/team_sync_group_mapping.html">github_team_sync_group_mapping</a>
171171
</li>
172+
<li>
173+
<a href="/docs/providers/github/r/emu_group_mapping.html">github_emu_group_mapping</a>
174+
</li>
172175
<li>
173176
<a href="/docs/providers/github/r/user_gpg_key.html">github_user_gpg_key</a>
174177
</li>

0 commit comments

Comments
 (0)