diff --git a/github/resource_github_organization_settings.go b/github/resource_github_organization_settings.go index 5b8d0b7062..ad8e248a5d 100644 --- a/github/resource_github_organization_settings.go +++ b/github/resource_github_organization_settings.go @@ -119,8 +119,8 @@ func resourceGithubOrganizationSettings() *schema.Resource { "members_can_fork_private_repositories": { Type: schema.TypeBool, Optional: true, - Default: false, - Description: "Whether or not organization members can fork private repositories.", + Computed: true, + Description: "Whether or not organization members can fork private repositories. When an enterprise policy controls this setting, omit this attribute to avoid API validation errors.", }, "web_commit_signoff_required": { Type: schema.TypeBool, @@ -266,7 +266,11 @@ func buildOrganizationSettings(d *schema.ResourceData, isEnterprise bool) *githu if shouldInclude("members_can_create_private_pages") { settings.MembersCanCreatePrivatePages = new(d.Get("members_can_create_private_pages").(bool)) } - if shouldInclude("members_can_fork_private_repositories") { + if !isUpdate { + if _, ok := d.GetOkExists("members_can_fork_private_repositories"); ok { //nolint:staticcheck // SA1019 // GetOkExists needed for Computed+Optional bool fields + settings.MembersCanForkPrivateRepos = new(d.Get("members_can_fork_private_repositories").(bool)) + } + } else if d.HasChange("members_can_fork_private_repositories") { settings.MembersCanForkPrivateRepos = new(d.Get("members_can_fork_private_repositories").(bool)) } if shouldInclude("web_commit_signoff_required") { diff --git a/github/resource_github_organization_settings_test.go b/github/resource_github_organization_settings_test.go index 742f779b3e..2f9f96305c 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_omittedForkFieldProducesCleanPlan(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_fork_private_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..e431f52047 --- /dev/null +++ b/github/resource_github_organization_settings_unit_test.go @@ -0,0 +1,104 @@ +package github + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func TestBuildOrganizationSettings_OmittedFieldsNotSent(t *testing.T) { + resource := resourceGithubOrganizationSettings() + d := schema.TestResourceDataRaw(t, resource.Schema, map[string]any{ + "billing_email": "test@example.com", + }) + + settings := buildOrganizationSettings(d, false) + + if settings.MembersCanForkPrivateRepos != nil { + t.Error("MembersCanForkPrivateRepos should be nil when not configured") + } + if settings.BillingEmail == nil { + t.Error("BillingEmail should be set when configured") + } +} + +func TestBuildOrganizationSettings_ExplicitTrueFieldsSent(t *testing.T) { + resource := resourceGithubOrganizationSettings() + d := schema.TestResourceDataRaw(t, resource.Schema, map[string]any{ + "billing_email": "test@example.com", + "members_can_fork_private_repositories": true, + }) + + settings := buildOrganizationSettings(d, false) + + if settings.MembersCanForkPrivateRepos == nil { + t.Fatal("MembersCanForkPrivateRepos should not be nil when explicitly set to true") + } + if *settings.MembersCanForkPrivateRepos != true { + t.Errorf("MembersCanForkPrivateRepos = %v, want true", *settings.MembersCanForkPrivateRepos) + } +} + +func TestBuildOrganizationSettings_ExplicitFalseFieldsSent(t *testing.T) { + resource := resourceGithubOrganizationSettings() + d := schema.TestResourceDataRaw(t, resource.Schema, map[string]any{ + "billing_email": "test@example.com", + "members_can_fork_private_repositories": false, + }) + + settings := buildOrganizationSettings(d, false) + + if settings.MembersCanForkPrivateRepos == nil { + t.Fatal("MembersCanForkPrivateRepos should not be nil when explicitly set to false") + } + if *settings.MembersCanForkPrivateRepos != false { + t.Errorf("MembersCanForkPrivateRepos = %v, want false", *settings.MembersCanForkPrivateRepos) + } +} + +func TestBuildOrganizationSettings_UpdateOmittedFieldsNotSent(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, false) + + if settings.MembersCanForkPrivateRepos != nil { + t.Error("MembersCanForkPrivateRepos 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 TestOrganizationSettingsSchemaProperties(t *testing.T) { + resource := resourceGithubOrganizationSettings() + + field := resource.Schema["members_can_fork_private_repositories"] + if field == nil { + t.Fatal("members_can_fork_private_repositories not found in schema") + } + + if !field.Optional { + t.Error("members_can_fork_private_repositories should be Optional") + } + if !field.Computed { + t.Error("members_can_fork_private_repositories should be Computed") + } + if field.Default != nil { + t.Errorf("members_can_fork_private_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..639f5801cf 100644 --- a/website/docs/r/organization_settings.html.markdown +++ b/website/docs/r/organization_settings.html.markdown @@ -64,7 +64,7 @@ The following arguments are supported: * `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`. -* `members_can_fork_private_repositories` - (Optional) Whether or not organization members can fork private repositories. Defaults to `false`. +* `members_can_fork_private_repositories` - (Optional) Whether or not organization members can fork private repositories. When an enterprise policy controls this setting, omit this attribute to avoid API validation errors. * `web_commit_signoff_required` - (Optional) Whether or not commit signatures are required for commits to the organization. Defaults to `false`. * `advanced_security_enabled_for_new_repositories` - (Optional) Whether or not advanced security is enabled for new repositories. Defaults to `false`. * `dependabot_alerts_enabled_for_new_repositories` - (Optional) Whether or not dependabot alerts are enabled for new repositories. Defaults to `false`.