diff --git a/github/resource_github_organization_settings.go b/github/resource_github_organization_settings.go index 5b8d0b7062..652bb502e0 100644 --- a/github/resource_github_organization_settings.go +++ b/github/resource_github_organization_settings.go @@ -84,6 +84,7 @@ func resourceGithubOrganizationSettings() *schema.Resource { "members_can_create_internal_repositories": { Type: schema.TypeBool, Optional: true, + Computed: true, Description: "Whether or not organization members can create new internal repositories. For Enterprise Organizations only.", }, "members_can_create_private_repositories": { @@ -291,9 +292,13 @@ func buildOrganizationSettings(d *schema.ResourceData, isEnterprise bool) *githu settings.SecretScanningPushProtectionEnabledForNewRepos = new(d.Get("secret_scanning_push_protection_enabled_for_new_repositories").(bool)) } - // Enterprise-specific field + // Enterprise-specific field: use GetOkExists to correctly detect explicit false if isEnterprise { - if shouldInclude("members_can_create_internal_repositories") { + if !isUpdate { + if _, ok := d.GetOkExists("members_can_create_internal_repositories"); ok { //nolint:staticcheck // SA1019 // GetOkExists needed for Computed+Optional bool fields + settings.MembersCanCreateInternalRepos = new(d.Get("members_can_create_internal_repositories").(bool)) + } + } else if d.HasChange("members_can_create_internal_repositories") { settings.MembersCanCreateInternalRepos = new(d.Get("members_can_create_internal_repositories").(bool)) } } diff --git a/github/resource_github_organization_settings_test.go b/github/resource_github_organization_settings_test.go index 742f779b3e..25eaa22c6a 100644 --- a/github/resource_github_organization_settings_test.go +++ b/github/resource_github_organization_settings_test.go @@ -611,3 +611,35 @@ func TestAccGithubOrganizationSettings(t *testing.T) { }) }) } + +func TestAccGithubOrganizationSettings_omittedInternalReposFieldProducesCleanPlan(t *testing.T) { + config := ` + resource "github_organization_settings" "test" { + billing_email = "test@example.com" + }` + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "billing_email", "test@example.com", + ), + resource.TestCheckResourceAttrSet( + "github_organization_settings.test", + "members_can_create_internal_repositories", + ), + ), + }, + { + Config: config, + PlanOnly: true, + ExpectNonEmptyPlan: false, + }, + }, + }) +} diff --git a/github/resource_github_organization_settings_unit_test.go b/github/resource_github_organization_settings_unit_test.go new file mode 100644 index 0000000000..8d11afc3bc --- /dev/null +++ b/github/resource_github_organization_settings_unit_test.go @@ -0,0 +1,101 @@ +package github + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func TestBuildOrganizationSettings_OmittedInternalReposNotSent(t *testing.T) { + resource := resourceGithubOrganizationSettings() + d := schema.TestResourceDataRaw(t, resource.Schema, map[string]any{ + "billing_email": "test@example.com", + }) + + settings := buildOrganizationSettings(d, true) + + if settings.MembersCanCreateInternalRepos != nil { + t.Error("MembersCanCreateInternalRepos should be nil when not configured") + } +} + +func TestBuildOrganizationSettings_ExplicitTrueInternalReposSent(t *testing.T) { + resource := resourceGithubOrganizationSettings() + d := schema.TestResourceDataRaw(t, resource.Schema, map[string]any{ + "billing_email": "test@example.com", + "members_can_create_internal_repositories": true, + }) + + settings := buildOrganizationSettings(d, true) + + if settings.MembersCanCreateInternalRepos == nil { + t.Fatal("MembersCanCreateInternalRepos should not be nil when explicitly set to true") + } + if *settings.MembersCanCreateInternalRepos != true { + t.Errorf("MembersCanCreateInternalRepos = %v, want true", *settings.MembersCanCreateInternalRepos) + } +} + +func TestBuildOrganizationSettings_ExplicitFalseInternalReposSent(t *testing.T) { + resource := resourceGithubOrganizationSettings() + d := schema.TestResourceDataRaw(t, resource.Schema, map[string]any{ + "billing_email": "test@example.com", + "members_can_create_internal_repositories": false, + }) + + settings := buildOrganizationSettings(d, true) + + if settings.MembersCanCreateInternalRepos == nil { + t.Fatal("MembersCanCreateInternalRepos should not be nil when explicitly set to false") + } + if *settings.MembersCanCreateInternalRepos != false { + t.Errorf("MembersCanCreateInternalRepos = %v, want false", *settings.MembersCanCreateInternalRepos) + } +} + +func TestBuildOrganizationSettings_UpdateOmittedInternalReposNotSent(t *testing.T) { + resource := resourceGithubOrganizationSettings() + d := schema.TestResourceDataRaw(t, resource.Schema, map[string]any{ + "billing_email": "test@example.com", + }) + d.SetId("test-org") + + settings := buildOrganizationSettings(d, true) + + if settings.MembersCanCreateInternalRepos != nil { + t.Error("MembersCanCreateInternalRepos should be nil on update when field has not changed") + } +} + +func TestBuildOrganizationSettings_NonEnterpriseExcludesInternalRepos(t *testing.T) { + resource := resourceGithubOrganizationSettings() + d := schema.TestResourceDataRaw(t, resource.Schema, map[string]any{ + "billing_email": "test@example.com", + "members_can_create_internal_repositories": true, + }) + + settings := buildOrganizationSettings(d, false) + + if settings.MembersCanCreateInternalRepos != nil { + t.Error("MembersCanCreateInternalRepos should be nil when not enterprise") + } +} + +func TestOrganizationSettingsSchemaInternalRepos(t *testing.T) { + resource := resourceGithubOrganizationSettings() + + field := resource.Schema["members_can_create_internal_repositories"] + if field == nil { + t.Fatal("members_can_create_internal_repositories not found in schema") + } + + if !field.Optional { + t.Error("members_can_create_internal_repositories should be Optional") + } + if !field.Computed { + t.Error("members_can_create_internal_repositories should be Computed") + } + if field.Default != nil { + t.Errorf("members_can_create_internal_repositories should have no Default, got %v", field.Default) + } +} diff --git a/website/docs/r/organization_settings.html.markdown b/website/docs/r/organization_settings.html.markdown index c8a4db54a6..270a914ba0 100644 --- a/website/docs/r/organization_settings.html.markdown +++ b/website/docs/r/organization_settings.html.markdown @@ -60,7 +60,7 @@ The following arguments are supported: * `members_can_create_repositories` - (Optional) Whether or not organization members can create new repositories. Defaults to `true`. * `members_can_create_public_repositories` - (Optional) Whether or not organization members can create new public repositories. Defaults to `true`. * `members_can_create_private_repositories` - (Optional) Whether or not organization members can create new private repositories. Defaults to `true`. -* `members_can_create_internal_repositories` - (Optional) Whether or not organization members can create new internal repositories. For Enterprise Organizations only. +* `members_can_create_internal_repositories` - (Optional) Whether or not organization members can create new internal repositories. For Enterprise Organizations only. When an enterprise policy controls this setting, omit this attribute to avoid API validation errors. * `members_can_create_pages` - (Optional) Whether or not organization members can create new pages. Defaults to `true`. * `members_can_create_public_pages` - (Optional) Whether or not organization members can create new public pages. Defaults to `true`. * `members_can_create_private_pages` - (Optional) Whether or not organization members can create new private pages. Defaults to `true`.