From 1e80afbfe40b8acbe95a8d722fd5fdc895f3f25b Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Sat, 21 Feb 2026 16:44:35 +0200 Subject: [PATCH 1/7] Update resource ID to be : Signed-off-by: Timo Sand --- github/resource_github_emu_group_mapping.go | 25 ++++----- ...urce_github_emu_group_mapping_migration.go | 54 +++++++++++++++++++ ...github_emu_group_mapping_migration_test.go | 42 +++++++++++++++ go.mod | 2 +- 4 files changed, 106 insertions(+), 17 deletions(-) create mode 100644 github/resource_github_emu_group_mapping_migration_test.go diff --git a/github/resource_github_emu_group_mapping.go b/github/resource_github_emu_group_mapping.go index 6a9d6ad238..cd781bd586 100644 --- a/github/resource_github_emu_group_mapping.go +++ b/github/resource_github_emu_group_mapping.go @@ -49,13 +49,18 @@ func resourceGithubEMUGroupMapping() *schema.Resource { Computed: true, }, }, - SchemaVersion: 1, + SchemaVersion: 2, StateUpgraders: []schema.StateUpgrader{ { Type: resourceGithubEMUGroupMappingV0().CoreConfigSchema().ImpliedType(), Upgrade: resourceGithubEMUGroupMappingStateUpgradeV0, Version: 0, }, + { + Type: resourceGithubEMUGroupMappingV1().CoreConfigSchema().ImpliedType(), + Upgrade: resourceGithubEMUGroupMappingStateUpgradeV1, + Version: 1, + }, }, } } @@ -94,7 +99,7 @@ func resourceGithubEMUGroupMappingCreate(ctx context.Context, d *schema.Resource return diag.FromErr(err) } - newResourceID, err := buildID(strconv.FormatInt(teamID, 10), teamSlug, strconv.FormatInt(groupID, 10)) + newResourceID, err := buildID(strconv.FormatInt(groupID, 10), strconv.FormatInt(teamID, 10)) if err != nil { return diag.FromErr(err) } @@ -226,7 +231,7 @@ func resourceGithubEMUGroupMappingUpdate(ctx context.Context, d *schema.Resource GroupID: github.Ptr(groupID), } - if d.HasChanges("group_id", "team_slug") { + if d.HasChange("team_slug") { tflog.Debug(ctx, "Updating connected external group via GitHub API") @@ -248,18 +253,6 @@ func resourceGithubEMUGroupMappingUpdate(ctx context.Context, d *schema.Resource if err := d.Set("group_name", group.GetGroupName()); err != nil { return diag.FromErr(err) } - - teamID := toInt64(d.Get("team_id")) - - newResourceID, err := buildID(strconv.FormatInt(teamID, 10), teamSlug, strconv.FormatInt(groupID, 10)) - if err != nil { - return diag.FromErr(err) - } - - tflog.Trace(ctx, "Setting resource ID", map[string]any{ - "resource_id": newResourceID, - }) - d.SetId(newResourceID) } tflog.Trace(ctx, "Updated successfully", map[string]any{ @@ -346,7 +339,7 @@ func resourceGithubEMUGroupMappingImport(ctx context.Context, d *schema.Resource return nil, err } - resourceID, err := buildID(strconv.FormatInt(teamID, 10), teamSlug, groupIDString) + resourceID, err := buildID(groupIDString, strconv.FormatInt(teamID, 10)) if err != nil { return nil, err } diff --git a/github/resource_github_emu_group_mapping_migration.go b/github/resource_github_emu_group_mapping_migration.go index f6aa87fb47..06de803907 100644 --- a/github/resource_github_emu_group_mapping_migration.go +++ b/github/resource_github_emu_group_mapping_migration.go @@ -2,6 +2,7 @@ package github import ( "context" + "fmt" "net/http" "strconv" @@ -66,3 +67,56 @@ func resourceGithubEMUGroupMappingStateUpgradeV0(ctx context.Context, rawState m tflog.Trace(ctx, "GitHub EMU Group Mapping State after migration", map[string]any{"state": rawState}) return rawState, nil } + +func resourceGithubEMUGroupMappingV1() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "team_id": { + Type: schema.TypeInt, + Computed: true, + Description: "ID of the GitHub team.", + }, + "team_slug": { + Type: schema.TypeString, + Required: true, + Description: "Slug of the GitHub team.", + }, + "group_id": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + Description: "Integer corresponding to the external group ID to be linked.", + }, + "group_name": { + Type: schema.TypeString, + Computed: true, + Description: "Name of the external group.", + }, + "etag": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceGithubEMUGroupMappingStateUpgradeV1(ctx context.Context, rawState map[string]any, meta any) (map[string]any, error) { + tflog.Trace(ctx, "GitHub EMU Group Mapping State before migration v1 => v2", map[string]any{"state": rawState}) + + oldResourceID, ok := rawState["id"].(string) + if !ok { + return nil, fmt.Errorf("id is not a string") + } + teamID, _, groupID, err := parseID3(oldResourceID) + if err != nil { + return nil, fmt.Errorf("could not parse ID: %w", err) + } + resourceID, err := buildID(groupID, teamID) + if err != nil { + return nil, fmt.Errorf("could not build ID: %w", err) + } + rawState["id"] = resourceID + + tflog.Trace(ctx, "GitHub EMU Group Mapping State after migration", map[string]any{"state": rawState}) + return rawState, nil +} diff --git a/github/resource_github_emu_group_mapping_migration_test.go b/github/resource_github_emu_group_mapping_migration_test.go new file mode 100644 index 0000000000..e34a45febc --- /dev/null +++ b/github/resource_github_emu_group_mapping_migration_test.go @@ -0,0 +1,42 @@ +package github + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func Test_resourceGithubEMUGroupMappingStateUpgradeV1(t *testing.T) { + t.Parallel() + + for _, d := range []struct { + testName string + rawState map[string]any + want map[string]any + shouldError bool + }{ + { + testName: "migrates v1 to v2", + rawState: map[string]any{ + "id": "123456:test-team:7765543", + }, + want: map[string]any{ + "id": "7765543:123456", + }, + shouldError: false, + }, + } { + t.Run(d.testName, func(t *testing.T) { + t.Parallel() + + got, err := resourceGithubEMUGroupMappingStateUpgradeV1(t.Context(), d.rawState, nil) + if (err != nil) != d.shouldError { + t.Fatalf("unexpected error state") + } + + if diff := cmp.Diff(got, d.want); !d.shouldError && diff != "" { + t.Fatalf("got %+v, want %+v: %s", got, d.want, diff) + } + }) + } +} diff --git a/go.mod b/go.mod index b1090713c9..886241cac7 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.24.4 require ( github.com/go-jose/go-jose/v3 v3.0.4 + github.com/google/go-cmp v0.7.0 github.com/google/go-github/v83 v83.0.0 github.com/google/uuid v1.6.0 github.com/hashicorp/go-cty v1.5.0 @@ -22,7 +23,6 @@ require ( github.com/cloudflare/circl v1.6.1 // indirect github.com/fatih/color v1.18.0 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-querystring v1.2.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect From 7c0157106a69871b79af638314bbdecccabaa616 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Sat, 21 Feb 2026 16:47:08 +0200 Subject: [PATCH 2/7] Fix `tflog.SetField` usage Signed-off-by: Timo Sand --- github/resource_github_emu_group_mapping.go | 44 ++++++++------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/github/resource_github_emu_group_mapping.go b/github/resource_github_emu_group_mapping.go index cd781bd586..6a733b8808 100644 --- a/github/resource_github_emu_group_mapping.go +++ b/github/resource_github_emu_group_mapping.go @@ -74,13 +74,13 @@ func resourceGithubEMUGroupMappingCreate(ctx context.Context, d *schema.Resource } client := meta.(*Owner).v3client orgName := meta.(*Owner).name - tflog.SetField(ctx, "org_name", orgName) + ctx = tflog.SetField(ctx, "org_name", orgName) teamSlug := d.Get("team_slug").(string) - tflog.SetField(ctx, "team_slug", teamSlug) + ctx = tflog.SetField(ctx, "team_slug", teamSlug) groupID := toInt64(d.Get("group_id")) - tflog.SetField(ctx, "group_id", groupID) + ctx = tflog.SetField(ctx, "group_id", groupID) eg := &github.ExternalGroup{ GroupID: github.Ptr(groupID), } @@ -133,9 +133,8 @@ func resourceGithubEMUGroupMappingCreate(ctx context.Context, d *schema.Resource } func resourceGithubEMUGroupMappingRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - tflog.Trace(ctx, "Reading EMU group mapping", map[string]any{ - "resource_id": d.Id(), - }) + ctx = tflog.SetField(ctx, "resource_id", d.Id()) + tflog.Trace(ctx, "Reading EMU group mapping") err := checkOrganization(meta) if err != nil { @@ -147,26 +146,22 @@ func resourceGithubEMUGroupMappingRead(ctx context.Context, d *schema.ResourceDa groupID := toInt64(d.Get("group_id")) teamSlug := d.Get("team_slug").(string) - tflog.SetField(ctx, "group_id", groupID) - tflog.SetField(ctx, "team_slug", teamSlug) - tflog.SetField(ctx, "org_name", orgName) + ctx = tflog.SetField(ctx, "group_id", groupID) + ctx = tflog.SetField(ctx, "team_slug", teamSlug) + ctx = tflog.SetField(ctx, "org_name", orgName) tflog.Debug(ctx, "Querying external groups linked to team from GitHub API") groupsList, resp, err := client.Teams.ListExternalGroupsForTeamBySlug(ctx, orgName, teamSlug) if err != nil { if resp != nil && resp.StatusCode == http.StatusBadRequest { - tflog.Info(ctx, "Removing EMU group mapping from state because the team has explicit members in GitHub", map[string]any{ - "resource_id": d.Id(), - }) + tflog.Info(ctx, "Removing EMU group mapping from state because the team has explicit members in GitHub") d.SetId("") return nil } if resp != nil && (resp.StatusCode == http.StatusNotFound) { // If the Group is not found, remove it from state - tflog.Info(ctx, "Removing EMU group mapping from state because team no longer exists in GitHub", map[string]any{ - "resource_id": d.Id(), - }) + tflog.Info(ctx, "Removing EMU group mapping from state because team no longer exists in GitHub") d.SetId("") return nil } @@ -174,9 +169,7 @@ func resourceGithubEMUGroupMappingRead(ctx context.Context, d *schema.ResourceDa } if len(groupsList.Groups) < 1 { - tflog.Info(ctx, "Removing EMU group mapping from state because no external groups are linked to the team", map[string]any{ - "resource_id": d.Id(), - }) + tflog.Info(ctx, "Removing EMU group mapping from state because no external groups are linked to the team") d.SetId("") return nil } @@ -210,9 +203,8 @@ func resourceGithubEMUGroupMappingRead(ctx context.Context, d *schema.ResourceDa } func resourceGithubEMUGroupMappingUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - tflog.Trace(ctx, "Updating EMU group mapping", map[string]any{ - "resource_id": d.Id(), - }) + ctx = tflog.SetField(ctx, "resource_id", d.Id()) + tflog.Trace(ctx, "Updating EMU group mapping") err := checkOrganization(meta) if err != nil { @@ -220,13 +212,13 @@ func resourceGithubEMUGroupMappingUpdate(ctx context.Context, d *schema.Resource } client := meta.(*Owner).v3client orgName := meta.(*Owner).name - tflog.SetField(ctx, "org_name", orgName) + ctx = tflog.SetField(ctx, "org_name", orgName) teamSlug := d.Get("team_slug").(string) - tflog.SetField(ctx, "team_slug", teamSlug) + ctx = tflog.SetField(ctx, "team_slug", teamSlug) groupID := toInt64(d.Get("group_id")) - tflog.SetField(ctx, "group_id", groupID) + ctx = tflog.SetField(ctx, "group_id", groupID) eg := &github.ExternalGroup{ GroupID: github.Ptr(groupID), } @@ -255,9 +247,7 @@ func resourceGithubEMUGroupMappingUpdate(ctx context.Context, d *schema.Resource } } - tflog.Trace(ctx, "Updated successfully", map[string]any{ - "resource_id": d.Id(), - }) + tflog.Trace(ctx, "Updated successfully") return nil } From 5ad32994773750174f79d51d69b4c37771d58a12 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Sat, 21 Feb 2026 19:22:40 +0200 Subject: [PATCH 3/7] Update tests to use `terraform-plugin-testing` and fix broken tests Signed-off-by: Timo Sand --- github/resource_github_emu_group_mapping.go | 2 +- .../resource_github_emu_group_mapping_test.go | 102 ++++++++++++------ 2 files changed, 68 insertions(+), 36 deletions(-) diff --git a/github/resource_github_emu_group_mapping.go b/github/resource_github_emu_group_mapping.go index 6a733b8808..e263681d75 100644 --- a/github/resource_github_emu_group_mapping.go +++ b/github/resource_github_emu_group_mapping.go @@ -125,7 +125,7 @@ func resourceGithubEMUGroupMappingCreate(ctx context.Context, d *schema.Resource return diag.FromErr(err) } - tflog.Trace(ctx, "Resource created or updated successfully", map[string]any{ + tflog.Trace(ctx, "Resource created successfully", map[string]any{ "resource_id": d.Id(), }) diff --git a/github/resource_github_emu_group_mapping_test.go b/github/resource_github_emu_group_mapping_test.go index 69bc257149..f4869aa5a6 100644 --- a/github/resource_github_emu_group_mapping_test.go +++ b/github/resource_github_emu_group_mapping_test.go @@ -6,9 +6,14 @@ import ( "strconv" "testing" + "github.com/hashicorp/terraform-plugin-testing/compare" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" ) func TestAccGithubEMUGroupMapping(t *testing.T) { @@ -16,7 +21,7 @@ func TestAccGithubEMUGroupMapping(t *testing.T) { if groupID == 0 { t.Skip("Skipping EMU group mapping tests because testEnterpriseEMUGroupId is not set") } - t.Run("creates and manages EMU group mapping", func(t *testing.T) { + t.Run("creates_without_error", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) teamName := fmt.Sprintf("%steam-emu-%s", testResourcePrefix, randomID) @@ -27,17 +32,18 @@ func TestAccGithubEMUGroupMapping(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccGithubEMUGroupMappingConfig(teamName, groupID), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrSet("github_emu_group_mapping.test", "group_id"), - resource.TestCheckResourceAttr("github_emu_group_mapping.test", "team_slug", teamName), - resource.TestCheckResourceAttrSet("github_emu_group_mapping.test", "etag"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_emu_group_mapping.test", tfjsonpath.New("group_id"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("github_emu_group_mapping.test", tfjsonpath.New("etag"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("github_emu_group_mapping.test", tfjsonpath.New("team_id"), knownvalue.NotNull()), + statecheck.CompareValuePairs("github_emu_group_mapping.test", tfjsonpath.New("team_slug"), "github_team.test", tfjsonpath.New("slug"), compare.ValuesSame()), + }, }, }, }) }) - t.Run("imports EMU group mapping", func(t *testing.T) { + t.Run("imports_without_error", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) teamName := fmt.Sprintf("%steam-emu-%s", testResourcePrefix, randomID) rn := "github_emu_group_mapping.test" @@ -51,15 +57,16 @@ func TestAccGithubEMUGroupMapping(t *testing.T) { Config: testAccGithubEMUGroupMappingConfig(teamName, groupID), }, { - ResourceName: rn, - ImportState: true, - ImportStateIdFunc: testAccGithubEMUGroupMappingImportStateIdFunc(rn), - ImportStateVerify: true, + ResourceName: rn, + ImportState: true, + ImportStateIdFunc: testAccGithubEMUGroupMappingImportStateIdFunc(rn), + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"etag"}, }, }, }) }) - t.Run("imports EMU group mapping with multiple teams", func(t *testing.T) { + t.Run("imports_multiple_teams_without_error", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) teamName := fmt.Sprintf("%steam1-emu-%s", testResourcePrefix, randomID) teamName2 := fmt.Sprintf("%steam2-emu-%s", testResourcePrefix, randomID) @@ -92,20 +99,22 @@ func TestAccGithubEMUGroupMapping(t *testing.T) { Config: config, }, { - ResourceName: rn, - ImportState: true, - ImportStateIdFunc: testAccGithubEMUGroupMappingImportStateIdFunc(rn), - ImportStateVerify: true, + ResourceName: rn, + ImportState: true, + ImportStateIdFunc: testAccGithubEMUGroupMappingImportStateIdFunc(rn), + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"etag"}, }, }, }) }) - t.Run("handles team slug update by recreating", func(t *testing.T) { + t.Run("updates_team_slug_without_error", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) teamName1 := fmt.Sprintf("%steam-emu-%s", testResourcePrefix, randomID) teamName2 := fmt.Sprintf("%s-upd", teamName1) + compareIDSame := statecheck.CompareValue(compare.ValuesSame()) resource.Test(t, resource.TestCase{ PreCheck: func() { skipUnlessEMUEnterprise(t) }, ProviderFactories: providerFactories, @@ -113,22 +122,32 @@ func TestAccGithubEMUGroupMapping(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccGithubEMUGroupMappingConfig(teamName1, groupID), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_emu_group_mapping.test", "team_slug", teamName1), - ), + ConfigStateChecks: []statecheck.StateCheck{ + compareIDSame.AddStateValue("github_emu_group_mapping.test", tfjsonpath.New("id")), + statecheck.ExpectKnownValue("github_emu_group_mapping.test", tfjsonpath.New("group_id"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("github_emu_group_mapping.test", tfjsonpath.New("etag"), knownvalue.NotNull()), + statecheck.CompareValuePairs("github_emu_group_mapping.test", tfjsonpath.New("team_slug"), "github_team.test", tfjsonpath.New("slug"), compare.ValuesSame()), + statecheck.ExpectKnownValue("github_emu_group_mapping.test", tfjsonpath.New("team_id"), knownvalue.NotNull()), + }, }, { Config: testAccGithubEMUGroupMappingConfig(teamName2, groupID), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_emu_group_mapping.test", "team_slug", teamName2), - ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownValue("github_emu_group_mapping.test", tfjsonpath.New("team_slug")), + plancheck.ExpectResourceAction("github_emu_group_mapping.test", plancheck.ResourceActionUpdate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + compareIDSame.AddStateValue("github_emu_group_mapping.test", tfjsonpath.New("id")), + statecheck.CompareValuePairs("github_emu_group_mapping.test", tfjsonpath.New("team_slug"), "github_team.test", tfjsonpath.New("slug"), compare.ValuesSame()), + }, }, }, }) }) - t.Run("forces new when switching to different team", func(t *testing.T) { - t.Skip("Skipping this test because we don't have terraform-plugin-testing available yet.") + t.Run("recreates_when_switching_to_different_team_without_error", func(t *testing.T) { randomID := acctest.RandString(5) teamName1 := fmt.Sprintf("%semu1-%s", testResourcePrefix, randomID) teamName2 := fmt.Sprintf("%semu2-%s", testResourcePrefix, randomID) @@ -155,18 +174,27 @@ resource "github_emu_group_mapping" "test" { Steps: []resource.TestStep{ { Config: fmt.Sprintf(config, teamName1, teamName2, "test1", groupID), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_emu_group_mapping.test", "team_slug", teamName1), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_emu_group_mapping.test", tfjsonpath.New("group_id"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("github_emu_group_mapping.test", tfjsonpath.New("team_id"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("github_emu_group_mapping.test", tfjsonpath.New("etag"), knownvalue.NotNull()), + statecheck.CompareValuePairs("github_emu_group_mapping.test", tfjsonpath.New("team_slug"), "github_team.test1", tfjsonpath.New("slug"), compare.ValuesSame()), + }, }, { Config: fmt.Sprintf(config, teamName1, teamName2, "test2", groupID), - // ConfigPlanChecks: resource.ConfigPlanChecks{ - // PreApply: []plancheck.PlanCheck{ - // plancheckExpectKnownValues("github_emu_group_mapping.test", "team_slug", teamName2), - // plancheck.ExpectResourceAction("github_emu_group_mapping.test", plancheck.ResourceActionDestroyBeforeCreate), // Verify that ForceNew is triggered - // }, - // }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue("github_emu_group_mapping.test", tfjsonpath.New("team_slug"), knownvalue.StringExact(teamName2)), + plancheck.ExpectResourceAction("github_emu_group_mapping.test", plancheck.ResourceActionReplace), // Verify that ForceNew is triggered + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_emu_group_mapping.test", tfjsonpath.New("group_id"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("github_emu_group_mapping.test", tfjsonpath.New("team_id"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("github_emu_group_mapping.test", tfjsonpath.New("etag"), knownvalue.NotNull()), + statecheck.CompareValuePairs("github_emu_group_mapping.test", tfjsonpath.New("team_slug"), "github_team.test2", tfjsonpath.New("slug"), compare.ValuesSame()), + }, }, }, }) @@ -212,7 +240,11 @@ func testAccGithubEMUGroupMappingImportStateIdFunc(resourceName string) resource if !ok { return "", fmt.Errorf("not found: %s", resourceName) } - return buildTwoPartID(rs.Primary.Attributes["group_id"], rs.Primary.Attributes["team_slug"]), nil + resourceID, err := buildID(rs.Primary.Attributes["group_id"], rs.Primary.Attributes["team_slug"]) + if err != nil { + return "", err + } + return resourceID, nil } } From c7cb2c221a86bc5d54fb4229c4428e383a0d056d Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Sat, 21 Feb 2026 19:25:03 +0200 Subject: [PATCH 4/7] Update docs Signed-off-by: Timo Sand --- website/docs/r/emu_group_mapping.html.markdown | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/website/docs/r/emu_group_mapping.html.markdown b/website/docs/r/emu_group_mapping.html.markdown index 6a443d782c..7525ec0823 100644 --- a/website/docs/r/emu_group_mapping.html.markdown +++ b/website/docs/r/emu_group_mapping.html.markdown @@ -23,12 +23,20 @@ resource "github_emu_group_mapping" "example_emu_group_mapping" { The following arguments are supported: - `team_slug` - (Required) Slug of the GitHub team -- `group_id` - (Required) Integer corresponding to the external group ID to be linked +- `group_id` - (Required) Integer corresponding to the external group ID to be linked + +## Attributes Reference + +The following additional attributes are exported: + +- `team_id` - The ID of the GitHub team +- `group_name` - The name of the external group +- `etag` - An etag representing the external group state ## Import GitHub EMU External Group Mappings can be imported using the external `group_id` and `team_slug` separated by a colon, e.g. ```sh -$ terraform import github_emu_group_mapping.example_emu_group_mapping 28836:emu-test-team +terraform import github_emu_group_mapping.example_emu_group_mapping 28836:emu-test-team ``` From 472d4d631c7c60259cb880a6fe80ff8f4e946e24 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Mon, 23 Feb 2026 20:47:27 +0200 Subject: [PATCH 5/7] Cleanup extra `tflog.SetField` calls Signed-off-by: Timo Sand --- github/resource_github_emu_group_mapping.go | 46 ++++++++++++--------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/github/resource_github_emu_group_mapping.go b/github/resource_github_emu_group_mapping.go index e263681d75..3c0fdde325 100644 --- a/github/resource_github_emu_group_mapping.go +++ b/github/resource_github_emu_group_mapping.go @@ -74,18 +74,19 @@ func resourceGithubEMUGroupMappingCreate(ctx context.Context, d *schema.Resource } client := meta.(*Owner).v3client orgName := meta.(*Owner).name - ctx = tflog.SetField(ctx, "org_name", orgName) teamSlug := d.Get("team_slug").(string) - ctx = tflog.SetField(ctx, "team_slug", teamSlug) groupID := toInt64(d.Get("group_id")) - ctx = tflog.SetField(ctx, "group_id", groupID) eg := &github.ExternalGroup{ GroupID: github.Ptr(groupID), } - tflog.Debug(ctx, "Connecting external group to team via GitHub API") + tflog.Debug(ctx, "Connecting external group to team via GitHub API", map[string]any{ + "org_name": orgName, + "team_slug": teamSlug, + "group_id": groupID, + }) group, resp, err := client.Teams.UpdateConnectedExternalGroup(ctx, orgName, teamSlug, eg) if err != nil { @@ -104,15 +105,18 @@ func resourceGithubEMUGroupMappingCreate(ctx context.Context, d *schema.Resource return diag.FromErr(err) } - if err := d.Set("team_id", int(teamID)); err != nil { - return diag.FromErr(err) - } - tflog.Trace(ctx, "Setting resource ID", map[string]any{ "resource_id": newResourceID, }) d.SetId(newResourceID) + tflog.Trace(ctx, "Setting team_id", map[string]any{ + "team_id": teamID, + }) + if err := d.Set("team_id", int(teamID)); err != nil { + return diag.FromErr(err) + } + etag := resp.Header.Get("ETag") tflog.Trace(ctx, "Setting state attribute: etag", map[string]any{ "etag": etag, @@ -121,6 +125,9 @@ func resourceGithubEMUGroupMappingCreate(ctx context.Context, d *schema.Resource return diag.FromErr(err) } + tflog.Trace(ctx, "Setting group_name", map[string]any{ + "group_name": group.GetGroupName(), + }) if err := d.Set("group_name", group.GetGroupName()); err != nil { return diag.FromErr(err) } @@ -146,11 +153,10 @@ func resourceGithubEMUGroupMappingRead(ctx context.Context, d *schema.ResourceDa groupID := toInt64(d.Get("group_id")) teamSlug := d.Get("team_slug").(string) - ctx = tflog.SetField(ctx, "group_id", groupID) - ctx = tflog.SetField(ctx, "team_slug", teamSlug) - ctx = tflog.SetField(ctx, "org_name", orgName) - - tflog.Debug(ctx, "Querying external groups linked to team from GitHub API") + tflog.Debug(ctx, "Querying external groups linked to team from GitHub API", map[string]any{ + "org_name": orgName, + "team_slug": teamSlug, + }) groupsList, resp, err := client.Teams.ListExternalGroupsForTeamBySlug(ctx, orgName, teamSlug) if err != nil { @@ -178,8 +184,9 @@ func resourceGithubEMUGroupMappingRead(ctx context.Context, d *schema.ResourceDa group := groupsList.Groups[0] tflog.Debug(ctx, "Successfully retrieved external group from GitHub API", map[string]any{ - "group_id": group.GetGroupID(), - "group_name": group.GetGroupName(), + "configured_group_id": groupID, + "upstream_group_id": group.GetGroupID(), + "group_name": group.GetGroupName(), }) if group.GetGroupID() != groupID { @@ -212,20 +219,21 @@ func resourceGithubEMUGroupMappingUpdate(ctx context.Context, d *schema.Resource } client := meta.(*Owner).v3client orgName := meta.(*Owner).name - ctx = tflog.SetField(ctx, "org_name", orgName) teamSlug := d.Get("team_slug").(string) - ctx = tflog.SetField(ctx, "team_slug", teamSlug) groupID := toInt64(d.Get("group_id")) - ctx = tflog.SetField(ctx, "group_id", groupID) eg := &github.ExternalGroup{ GroupID: github.Ptr(groupID), } if d.HasChange("team_slug") { - tflog.Debug(ctx, "Updating connected external group via GitHub API") + tflog.Debug(ctx, "Updating connected external group via GitHub API", map[string]any{ + "org_name": orgName, + "team_slug": teamSlug, + "group_id": groupID, + }) group, resp, err := client.Teams.UpdateConnectedExternalGroup(ctx, orgName, teamSlug, eg) if err != nil { From a954ec1c7e3c55c4046911215b047e1a4a541422 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Tue, 24 Feb 2026 21:24:50 +0200 Subject: [PATCH 6/7] Reducing logger complexity Signed-off-by: Timo Sand --- github/resource_github_emu_group_mapping.go | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/github/resource_github_emu_group_mapping.go b/github/resource_github_emu_group_mapping.go index 3c0fdde325..0157610cde 100644 --- a/github/resource_github_emu_group_mapping.go +++ b/github/resource_github_emu_group_mapping.go @@ -153,10 +153,7 @@ func resourceGithubEMUGroupMappingRead(ctx context.Context, d *schema.ResourceDa groupID := toInt64(d.Get("group_id")) teamSlug := d.Get("team_slug").(string) - tflog.Debug(ctx, "Querying external groups linked to team from GitHub API", map[string]any{ - "org_name": orgName, - "team_slug": teamSlug, - }) + tflog.Debug(ctx, "Querying external groups linked to team from GitHub API") groupsList, resp, err := client.Teams.ListExternalGroupsForTeamBySlug(ctx, orgName, teamSlug) if err != nil { @@ -184,9 +181,7 @@ func resourceGithubEMUGroupMappingRead(ctx context.Context, d *schema.ResourceDa group := groupsList.Groups[0] tflog.Debug(ctx, "Successfully retrieved external group from GitHub API", map[string]any{ - "configured_group_id": groupID, - "upstream_group_id": group.GetGroupID(), - "group_name": group.GetGroupName(), + "group_name": group.GetGroupName(), }) if group.GetGroupID() != groupID { @@ -229,11 +224,7 @@ func resourceGithubEMUGroupMappingUpdate(ctx context.Context, d *schema.Resource if d.HasChange("team_slug") { - tflog.Debug(ctx, "Updating connected external group via GitHub API", map[string]any{ - "org_name": orgName, - "team_slug": teamSlug, - "group_id": groupID, - }) + tflog.Debug(ctx, "Updating connected external group via GitHub API") group, resp, err := client.Teams.UpdateConnectedExternalGroup(ctx, orgName, teamSlug, eg) if err != nil { From 8977969720be60f877adec3e257cc1bc4c89734e Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Tue, 24 Feb 2026 21:27:27 +0200 Subject: [PATCH 7/7] Improve error messages in `Import` Signed-off-by: Timo Sand --- github/resource_github_emu_group_mapping.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/github/resource_github_emu_group_mapping.go b/github/resource_github_emu_group_mapping.go index 0157610cde..a20fd6f0f7 100644 --- a/github/resource_github_emu_group_mapping.go +++ b/github/resource_github_emu_group_mapping.go @@ -2,6 +2,7 @@ package github import ( "context" + "fmt" "net/http" "strconv" @@ -298,11 +299,11 @@ func resourceGithubEMUGroupMappingImport(ctx context.Context, d *schema.Resource // : groupIDString, teamSlug, err := parseID2(d.Id()) if err != nil { - return nil, err + return nil, fmt.Errorf("could not parse import ID (%s), expected format: :. Parse error: %w", importID, err) } groupID, err := strconv.Atoi(groupIDString) if err != nil { - return nil, err + return nil, unconvertibleIdErr(groupIDString, err) } tflog.Debug(ctx, "Parsed two-part import ID", map[string]any{