diff --git a/github/resource_github_organization_settings.go b/github/resource_github_organization_settings.go index 7eddd96b98..81e0a4d1ff 100644 --- a/github/resource_github_organization_settings.go +++ b/github/resource_github_organization_settings.go @@ -172,6 +172,134 @@ func resourceGithubOrganizationSettings() *schema.Resource { } } +// buildOrganizationSettings creates a github.Organization struct with only the fields that are explicitly configured +// For updates, it only includes fields that have actually changed to avoid API validation errors +func buildOrganizationSettings(d *schema.ResourceData, isEnterprise bool) *github.Organization { + settings := &github.Organization{} + + // Check if this is an update (has ID) or create (no ID) + isUpdate := d.Id() != "" + + // Helper function to check if field should be included + shouldInclude := func(fieldName string) bool { + if !isUpdate { + // For creates, include if explicitly configured + _, ok := d.GetOk(fieldName) + return ok + } + // For updates, only include if the field has changed + return d.HasChange(fieldName) + } + + // Required field - always include if configured (API requires it even if unchanged) + if billingEmail, ok := d.GetOk("billing_email"); ok { + settings.BillingEmail = github.String(billingEmail.(string)) + } + + // Optional string fields - only set if should be included + if shouldInclude("company") { + if company, ok := d.GetOk("company"); ok { + settings.Company = github.String(company.(string)) + } + } + if shouldInclude("email") { + if email, ok := d.GetOk("email"); ok { + settings.Email = github.String(email.(string)) + } + } + if shouldInclude("twitter_username") { + if twitterUsername, ok := d.GetOk("twitter_username"); ok { + settings.TwitterUsername = github.String(twitterUsername.(string)) + } + } + if shouldInclude("location") { + if location, ok := d.GetOk("location"); ok { + settings.Location = github.String(location.(string)) + } + } + if shouldInclude("name") { + if name, ok := d.GetOk("name"); ok { + settings.Name = github.String(name.(string)) + } + } + if shouldInclude("description") { + if description, ok := d.GetOk("description"); ok { + settings.Description = github.String(description.(string)) + } + } + if shouldInclude("blog") { + if blog, ok := d.GetOk("blog"); ok { + settings.Blog = github.String(blog.(string)) + } + } + + // Boolean fields - only set if should be included + // Use d.Get() instead of d.GetOk() when shouldInclude() returns true, + // because we already know the field should be included, and d.Get() correctly handles false values + if shouldInclude("has_organization_projects") { + settings.HasOrganizationProjects = github.Bool(d.Get("has_organization_projects").(bool)) + } + if shouldInclude("has_repository_projects") { + settings.HasRepositoryProjects = github.Bool(d.Get("has_repository_projects").(bool)) + } + if shouldInclude("default_repository_permission") { + if defaultRepoPermission, ok := d.GetOk("default_repository_permission"); ok { + settings.DefaultRepoPermission = github.String(defaultRepoPermission.(string)) + } + } + if shouldInclude("members_can_create_repositories") { + settings.MembersCanCreateRepos = github.Bool(d.Get("members_can_create_repositories").(bool)) + } + if shouldInclude("members_can_create_private_repositories") { + settings.MembersCanCreatePrivateRepos = github.Bool(d.Get("members_can_create_private_repositories").(bool)) + } + if shouldInclude("members_can_create_public_repositories") { + settings.MembersCanCreatePublicRepos = github.Bool(d.Get("members_can_create_public_repositories").(bool)) + } + if shouldInclude("members_can_create_pages") { + settings.MembersCanCreatePages = github.Bool(d.Get("members_can_create_pages").(bool)) + } + if shouldInclude("members_can_create_public_pages") { + settings.MembersCanCreatePublicPages = github.Bool(d.Get("members_can_create_public_pages").(bool)) + } + if shouldInclude("members_can_create_private_pages") { + settings.MembersCanCreatePrivatePages = github.Bool(d.Get("members_can_create_private_pages").(bool)) + } + if shouldInclude("members_can_fork_private_repositories") { + settings.MembersCanForkPrivateRepos = github.Bool(d.Get("members_can_fork_private_repositories").(bool)) + } + if shouldInclude("web_commit_signoff_required") { + settings.WebCommitSignoffRequired = github.Bool(d.Get("web_commit_signoff_required").(bool)) + } + if shouldInclude("advanced_security_enabled_for_new_repositories") { + settings.AdvancedSecurityEnabledForNewRepos = github.Bool(d.Get("advanced_security_enabled_for_new_repositories").(bool)) + } + if shouldInclude("dependabot_alerts_enabled_for_new_repositories") { + settings.DependabotAlertsEnabledForNewRepos = github.Bool(d.Get("dependabot_alerts_enabled_for_new_repositories").(bool)) + } + if shouldInclude("dependabot_security_updates_enabled_for_new_repositories") { + settings.DependabotSecurityUpdatesEnabledForNewRepos = github.Bool(d.Get("dependabot_security_updates_enabled_for_new_repositories").(bool)) + } + if shouldInclude("dependency_graph_enabled_for_new_repositories") { + settings.DependencyGraphEnabledForNewRepos = github.Bool(d.Get("dependency_graph_enabled_for_new_repositories").(bool)) + } + if shouldInclude("secret_scanning_enabled_for_new_repositories") { + settings.SecretScanningEnabledForNewRepos = github.Bool(d.Get("secret_scanning_enabled_for_new_repositories").(bool)) + } + if shouldInclude("secret_scanning_push_protection_enabled_for_new_repositories") { + settings.SecretScanningPushProtectionEnabledForNewRepos = github.Bool(d.Get("secret_scanning_push_protection_enabled_for_new_repositories").(bool)) + } + + // Enterprise-specific field + if isEnterprise { + if shouldInclude("members_can_create_internal_repositories") { + settings.MembersCanCreateInternalRepos = github.Bool(d.Get("members_can_create_internal_repositories").(bool)) + } + } + + return settings +} + func resourceGithubOrganizationSettingsCreateOrUpdate(d *schema.ResourceData, meta interface{}) error { err := checkOrganization(meta) if err != nil { @@ -181,120 +309,112 @@ func resourceGithubOrganizationSettingsCreateOrUpdate(d *schema.ResourceData, me ctx := context.WithValue(context.Background(), ctxId, d.Id()) org := meta.(*Owner).name - settings := github.Organization{ - BillingEmail: github.String(d.Get("billing_email").(string)), - Company: github.String(d.Get("company").(string)), - Email: github.String(d.Get("email").(string)), - TwitterUsername: github.String(d.Get("twitter_username").(string)), - Location: github.String(d.Get("location").(string)), - Name: github.String(d.Get("name").(string)), - Description: github.String(d.Get("description").(string)), - HasOrganizationProjects: github.Bool(d.Get("has_organization_projects").(bool)), - HasRepositoryProjects: github.Bool(d.Get("has_repository_projects").(bool)), - DefaultRepoPermission: github.String(d.Get("default_repository_permission").(string)), - MembersCanCreateRepos: github.Bool(d.Get("members_can_create_repositories").(bool)), - MembersCanCreatePrivateRepos: github.Bool(d.Get("members_can_create_private_repositories").(bool)), - MembersCanCreatePublicRepos: github.Bool(d.Get("members_can_create_public_repositories").(bool)), - MembersCanCreatePages: github.Bool(d.Get("members_can_create_pages").(bool)), - MembersCanCreatePublicPages: github.Bool(d.Get("members_can_create_public_pages").(bool)), - MembersCanCreatePrivatePages: github.Bool(d.Get("members_can_create_private_pages").(bool)), - MembersCanForkPrivateRepos: github.Bool(d.Get("members_can_fork_private_repositories").(bool)), - WebCommitSignoffRequired: github.Bool(d.Get("web_commit_signoff_required").(bool)), - Blog: github.String(d.Get("blog").(string)), - AdvancedSecurityEnabledForNewRepos: github.Bool(d.Get("advanced_security_enabled_for_new_repositories").(bool)), - DependabotAlertsEnabledForNewRepos: github.Bool(d.Get("dependabot_alerts_enabled_for_new_repositories").(bool)), - DependabotSecurityUpdatesEnabledForNewRepos: github.Bool(d.Get("dependabot_security_updates_enabled_for_new_repositories").(bool)), - DependencyGraphEnabledForNewRepos: github.Bool(d.Get("dependency_graph_enabled_for_new_repositories").(bool)), - SecretScanningEnabledForNewRepos: github.Bool(d.Get("secret_scanning_enabled_for_new_repositories").(bool)), - SecretScanningPushProtectionEnabledForNewRepos: github.Bool(d.Get("secret_scanning_push_protection_enabled_for_new_repositories").(bool)), + orgInfo, _, err := client.Organizations.Get(ctx, org) + if err != nil { + return err } - enterpriseSettings := github.Organization{ - BillingEmail: github.String(d.Get("billing_email").(string)), - Company: github.String(d.Get("company").(string)), - Email: github.String(d.Get("email").(string)), - TwitterUsername: github.String(d.Get("twitter_username").(string)), - Location: github.String(d.Get("location").(string)), - Name: github.String(d.Get("name").(string)), - Description: github.String(d.Get("description").(string)), - HasOrganizationProjects: github.Bool(d.Get("has_organization_projects").(bool)), - HasRepositoryProjects: github.Bool(d.Get("has_repository_projects").(bool)), - DefaultRepoPermission: github.String(d.Get("default_repository_permission").(string)), - MembersCanCreateRepos: github.Bool(d.Get("members_can_create_repositories").(bool)), - MembersCanCreateInternalRepos: github.Bool(d.Get("members_can_create_internal_repositories").(bool)), - MembersCanCreatePrivateRepos: github.Bool(d.Get("members_can_create_private_repositories").(bool)), - MembersCanCreatePublicRepos: github.Bool(d.Get("members_can_create_public_repositories").(bool)), - MembersCanCreatePages: github.Bool(d.Get("members_can_create_pages").(bool)), - MembersCanCreatePublicPages: github.Bool(d.Get("members_can_create_public_pages").(bool)), - MembersCanCreatePrivatePages: github.Bool(d.Get("members_can_create_private_pages").(bool)), - MembersCanForkPrivateRepos: github.Bool(d.Get("members_can_fork_private_repositories").(bool)), - WebCommitSignoffRequired: github.Bool(d.Get("web_commit_signoff_required").(bool)), - Blog: github.String(d.Get("blog").(string)), - AdvancedSecurityEnabledForNewRepos: github.Bool(d.Get("advanced_security_enabled_for_new_repositories").(bool)), - DependabotAlertsEnabledForNewRepos: github.Bool(d.Get("dependabot_alerts_enabled_for_new_repositories").(bool)), - DependabotSecurityUpdatesEnabledForNewRepos: github.Bool(d.Get("dependabot_security_updates_enabled_for_new_repositories").(bool)), - DependencyGraphEnabledForNewRepos: github.Bool(d.Get("dependency_graph_enabled_for_new_repositories").(bool)), - SecretScanningEnabledForNewRepos: github.Bool(d.Get("secret_scanning_enabled_for_new_repositories").(bool)), - SecretScanningPushProtectionEnabledForNewRepos: github.Bool(d.Get("secret_scanning_push_protection_enabled_for_new_repositories").(bool)), - } + // Build settings using helper function + isEnterprise := orgInfo.GetPlan().GetName() == "enterprise" + settings := buildOrganizationSettings(d, isEnterprise) - enterpriseSettingsNoFork := github.Organization{ - BillingEmail: github.String(d.Get("billing_email").(string)), - Company: github.String(d.Get("company").(string)), - Email: github.String(d.Get("email").(string)), - TwitterUsername: github.String(d.Get("twitter_username").(string)), - Location: github.String(d.Get("location").(string)), - Name: github.String(d.Get("name").(string)), - Description: github.String(d.Get("description").(string)), - HasOrganizationProjects: github.Bool(d.Get("has_organization_projects").(bool)), - HasRepositoryProjects: github.Bool(d.Get("has_repository_projects").(bool)), - DefaultRepoPermission: github.String(d.Get("default_repository_permission").(string)), - MembersCanCreateRepos: github.Bool(d.Get("members_can_create_repositories").(bool)), - MembersCanCreateInternalRepos: github.Bool(d.Get("members_can_create_internal_repositories").(bool)), - MembersCanCreatePrivateRepos: github.Bool(d.Get("members_can_create_private_repositories").(bool)), - MembersCanCreatePublicRepos: github.Bool(d.Get("members_can_create_public_repositories").(bool)), - MembersCanCreatePages: github.Bool(d.Get("members_can_create_pages").(bool)), - MembersCanCreatePublicPages: github.Bool(d.Get("members_can_create_public_pages").(bool)), - MembersCanCreatePrivatePages: github.Bool(d.Get("members_can_create_private_pages").(bool)), - WebCommitSignoffRequired: github.Bool(d.Get("web_commit_signoff_required").(bool)), - Blog: github.String(d.Get("blog").(string)), - AdvancedSecurityEnabledForNewRepos: github.Bool(d.Get("advanced_security_enabled_for_new_repositories").(bool)), - DependabotAlertsEnabledForNewRepos: github.Bool(d.Get("dependabot_alerts_enabled_for_new_repositories").(bool)), - DependabotSecurityUpdatesEnabledForNewRepos: github.Bool(d.Get("dependabot_security_updates_enabled_for_new_repositories").(bool)), - DependencyGraphEnabledForNewRepos: github.Bool(d.Get("dependency_graph_enabled_for_new_repositories").(bool)), - SecretScanningEnabledForNewRepos: github.Bool(d.Get("secret_scanning_enabled_for_new_repositories").(bool)), - SecretScanningPushProtectionEnabledForNewRepos: github.Bool(d.Get("secret_scanning_push_protection_enabled_for_new_repositories").(bool)), + // Debug: Log the settings being sent to the API with detailed field information + log.Printf("[DEBUG] Built settings for org %s (enterprise: %v)", org, isEnterprise) + if settings.BillingEmail != nil { + log.Printf("[DEBUG] BillingEmail: %s", *settings.BillingEmail) } - - orgPlan, _, err := client.Organizations.Edit(ctx, org, nil) - if err != nil { - return err + if settings.Company != nil { + log.Printf("[DEBUG] Company: %s", *settings.Company) + } + if settings.Email != nil { + log.Printf("[DEBUG] Email: %s", *settings.Email) + } + if settings.TwitterUsername != nil { + log.Printf("[DEBUG] TwitterUsername: %s", *settings.TwitterUsername) + } + if settings.Location != nil { + log.Printf("[DEBUG] Location: %s", *settings.Location) + } + if settings.Name != nil { + log.Printf("[DEBUG] Name: %s", *settings.Name) + } + if settings.Description != nil { + log.Printf("[DEBUG] Description: %s", *settings.Description) + } + if settings.Blog != nil { + log.Printf("[DEBUG] Blog: %s", *settings.Blog) + } + if settings.HasOrganizationProjects != nil { + log.Printf("[DEBUG] HasOrganizationProjects: %v", *settings.HasOrganizationProjects) + } + if settings.HasRepositoryProjects != nil { + log.Printf("[DEBUG] HasRepositoryProjects: %v", *settings.HasRepositoryProjects) + } + if settings.DefaultRepoPermission != nil { + log.Printf("[DEBUG] DefaultRepoPermission: %s", *settings.DefaultRepoPermission) + } + if settings.MembersCanCreateRepos != nil { + log.Printf("[DEBUG] MembersCanCreateRepos: %v", *settings.MembersCanCreateRepos) + } + if settings.MembersCanCreatePrivateRepos != nil { + log.Printf("[DEBUG] MembersCanCreatePrivateRepos: %v", *settings.MembersCanCreatePrivateRepos) + } + if settings.MembersCanCreatePublicRepos != nil { + log.Printf("[DEBUG] MembersCanCreatePublicRepos: %v", *settings.MembersCanCreatePublicRepos) + } + if settings.MembersCanCreateInternalRepos != nil { + log.Printf("[DEBUG] MembersCanCreateInternalRepos: %v", *settings.MembersCanCreateInternalRepos) + } + if settings.MembersCanCreatePages != nil { + log.Printf("[DEBUG] MembersCanCreatePages: %v", *settings.MembersCanCreatePages) + } + if settings.MembersCanCreatePublicPages != nil { + log.Printf("[DEBUG] MembersCanCreatePublicPages: %v", *settings.MembersCanCreatePublicPages) + } + if settings.MembersCanCreatePrivatePages != nil { + log.Printf("[DEBUG] MembersCanCreatePrivatePages: %v", *settings.MembersCanCreatePrivatePages) + } + if settings.MembersCanForkPrivateRepos != nil { + log.Printf("[DEBUG] MembersCanForkPrivateRepos: %v", *settings.MembersCanForkPrivateRepos) + } + if settings.WebCommitSignoffRequired != nil { + log.Printf("[DEBUG] WebCommitSignoffRequired: %v", *settings.WebCommitSignoffRequired) + } + if settings.AdvancedSecurityEnabledForNewRepos != nil { + log.Printf("[DEBUG] AdvancedSecurityEnabledForNewRepos: %v", *settings.AdvancedSecurityEnabledForNewRepos) + } + if settings.DependabotAlertsEnabledForNewRepos != nil { + log.Printf("[DEBUG] DependabotAlertsEnabledForNewRepos: %v", *settings.DependabotAlertsEnabledForNewRepos) + } + if settings.DependabotSecurityUpdatesEnabledForNewRepos != nil { + log.Printf("[DEBUG] DependabotSecurityUpdatesEnabledForNewRepos: %v", *settings.DependabotSecurityUpdatesEnabledForNewRepos) + } + if settings.DependencyGraphEnabledForNewRepos != nil { + log.Printf("[DEBUG] DependencyGraphEnabledForNewRepos: %v", *settings.DependencyGraphEnabledForNewRepos) + } + if settings.SecretScanningEnabledForNewRepos != nil { + log.Printf("[DEBUG] SecretScanningEnabledForNewRepos: %v", *settings.SecretScanningEnabledForNewRepos) + } + if settings.SecretScanningPushProtectionEnabledForNewRepos != nil { + log.Printf("[DEBUG] SecretScanningPushProtectionEnabledForNewRepos: %v", *settings.SecretScanningPushProtectionEnabledForNewRepos) } - if orgPlan.GetPlan().GetName() == "enterprise" { - if _, ok := d.GetOk("members_can_fork_private_repositories"); !ok { - orgSettings, _, err := client.Organizations.Edit(ctx, org, &enterpriseSettingsNoFork) - if err != nil { - return err - } - id := strconv.FormatInt(orgSettings.GetID(), 10) - d.SetId(id) - } else if _, ok := d.GetOk("members_can_fork_private_repositories"); ok { - orgSettings, _, err := client.Organizations.Edit(ctx, org, &enterpriseSettings) - if err != nil { - return err + orgSettings, _, err := client.Organizations.Edit(ctx, org, settings) + if err != nil { + // Log detailed error information for debugging + if ghErr, ok := err.(*github.ErrorResponse); ok { + log.Printf("[DEBUG] GitHub API Error: Status=%d, Message=%s", ghErr.Response.StatusCode, ghErr.Message) + if len(ghErr.Errors) > 0 { + for i, apiErr := range ghErr.Errors { + log.Printf("[DEBUG] Error[%d]: Resource=%s, Field=%s, Code=%s, Message=%s", + i, apiErr.Resource, apiErr.Field, apiErr.Code, apiErr.Message) + } } - id := strconv.FormatInt(orgSettings.GetID(), 10) - d.SetId(id) - } - } else { - orgSettings, _, err := client.Organizations.Edit(ctx, org, &settings) - if err != nil { - return err } - id := strconv.FormatInt(orgSettings.GetID(), 10) - d.SetId(id) + return err } + id := strconv.FormatInt(orgSettings.GetID(), 10) + d.SetId(id) return resourceGithubOrganizationSettingsRead(d, meta) } @@ -404,115 +524,29 @@ func resourceGithubOrganizationSettingsDelete(d *schema.ResourceData, meta inter ctx := context.WithValue(context.Background(), ctxId, d.Id()) org := meta.(*Owner).name - // This will set org settings to default values - settings := github.Organization{ - BillingEmail: github.String("email@example.com"), - Company: github.String(""), - Email: github.String(""), - TwitterUsername: github.String(""), - Location: github.String(""), - Name: github.String(""), - Description: github.String(""), - HasOrganizationProjects: github.Bool(true), - HasRepositoryProjects: github.Bool(true), - DefaultRepoPermission: github.String("read"), - MembersCanCreateRepos: github.Bool(true), - MembersCanCreatePrivateRepos: github.Bool(true), - MembersCanCreatePublicRepos: github.Bool(true), - MembersCanCreatePages: github.Bool(false), - MembersCanCreatePublicPages: github.Bool(true), - MembersCanCreatePrivatePages: github.Bool(true), - MembersCanForkPrivateRepos: github.Bool(false), - WebCommitSignoffRequired: github.Bool(false), - Blog: github.String(""), - AdvancedSecurityEnabledForNewRepos: github.Bool(false), - DependabotAlertsEnabledForNewRepos: github.Bool(false), - DependabotSecurityUpdatesEnabledForNewRepos: github.Bool(false), - DependencyGraphEnabledForNewRepos: github.Bool(false), - SecretScanningEnabledForNewRepos: github.Bool(false), - SecretScanningPushProtectionEnabledForNewRepos: github.Bool(false), + log.Printf("[DEBUG] Reverting Organization Settings to default values: %s", org) + + // Get organization info to determine if it's enterprise + orgInfo, _, err := client.Organizations.Get(ctx, org) + if err != nil { + return err } - enterpriseSettings := github.Organization{ - BillingEmail: github.String("email@example.com"), - Company: github.String(""), - Email: github.String(""), - TwitterUsername: github.String(""), - Location: github.String(""), - Name: github.String(""), - Description: github.String(""), - HasOrganizationProjects: github.Bool(true), - HasRepositoryProjects: github.Bool(true), - DefaultRepoPermission: github.String("read"), - MembersCanCreateRepos: github.Bool(true), - MembersCanCreatePrivateRepos: github.Bool(true), - MembersCanCreateInternalRepos: github.Bool(true), - MembersCanCreatePublicRepos: github.Bool(true), - MembersCanCreatePages: github.Bool(false), - MembersCanCreatePublicPages: github.Bool(true), - MembersCanCreatePrivatePages: github.Bool(true), - MembersCanForkPrivateRepos: github.Bool(false), - WebCommitSignoffRequired: github.Bool(false), - Blog: github.String(""), - AdvancedSecurityEnabledForNewRepos: github.Bool(false), - DependabotAlertsEnabledForNewRepos: github.Bool(false), - DependabotSecurityUpdatesEnabledForNewRepos: github.Bool(false), - DependencyGraphEnabledForNewRepos: github.Bool(false), - SecretScanningEnabledForNewRepos: github.Bool(false), - SecretScanningPushProtectionEnabledForNewRepos: github.Bool(false), + // Build minimal settings with only required fields + isEnterprise := orgInfo.GetPlan().GetName() == "enterprise" + defaultSettings := &github.Organization{ + BillingEmail: github.String("email@example.com"), } - enterpriseSettingsNoFork := github.Organization{ - BillingEmail: github.String("email@example.com"), - Company: github.String(""), - Email: github.String(""), - TwitterUsername: github.String(""), - Location: github.String(""), - Name: github.String(""), - Description: github.String(""), - HasOrganizationProjects: github.Bool(true), - HasRepositoryProjects: github.Bool(true), - DefaultRepoPermission: github.String("read"), - MembersCanCreateRepos: github.Bool(true), - MembersCanCreatePrivateRepos: github.Bool(true), - MembersCanCreateInternalRepos: github.Bool(true), - MembersCanCreatePublicRepos: github.Bool(true), - MembersCanCreatePages: github.Bool(false), - MembersCanCreatePublicPages: github.Bool(true), - MembersCanCreatePrivatePages: github.Bool(true), - WebCommitSignoffRequired: github.Bool(false), - Blog: github.String(""), - AdvancedSecurityEnabledForNewRepos: github.Bool(false), - DependabotAlertsEnabledForNewRepos: github.Bool(false), - DependabotSecurityUpdatesEnabledForNewRepos: github.Bool(false), - DependencyGraphEnabledForNewRepos: github.Bool(false), - SecretScanningEnabledForNewRepos: github.Bool(false), - SecretScanningPushProtectionEnabledForNewRepos: github.Bool(false), + // Only add enterprise-specific fields if it's an enterprise org + if isEnterprise { + defaultSettings.MembersCanCreateInternalRepos = github.Bool(true) } - log.Printf("[DEBUG] Reverting Organization Settings to default values: %s", org) - orgPlan, _, err := client.Organizations.Edit(ctx, org, nil) + _, _, err = client.Organizations.Edit(ctx, org, defaultSettings) if err != nil { return err } - if orgPlan.GetPlan().GetName() == "enterprise" { - if _, ok := d.GetOk("members_can_fork_private_repositories"); !ok { - _, _, err := client.Organizations.Edit(ctx, org, &enterpriseSettingsNoFork) - if err != nil { - return err - } - } else if _, ok := d.GetOk("members_can_fork_private_repositories"); ok { - _, _, err := client.Organizations.Edit(ctx, org, &enterpriseSettings) - if err != nil { - return err - } - } - } else { - _, _, err := client.Organizations.Edit(ctx, org, &settings) - if err != nil { - return err - } - } return nil } diff --git a/github/resource_github_organization_settings_test.go b/github/resource_github_organization_settings_test.go index c0b6a8b20d..115a396fb0 100644 --- a/github/resource_github_organization_settings_test.go +++ b/github/resource_github_organization_settings_test.go @@ -180,4 +180,580 @@ func TestAccGithubOrganizationSettings(t *testing.T) { testCase(t, organization) }) }) + + t.Run("handles boolean false values correctly", func(t *testing.T) { + config := ` + resource "github_organization_settings" "test" { + billing_email = "test@example.com" + members_can_create_private_repositories = false + members_can_create_internal_repositories = false + members_can_fork_private_repositories = false + web_commit_signoff_required = false + advanced_security_enabled_for_new_repositories = false + dependabot_alerts_enabled_for_new_repositories = false + dependabot_security_updates_enabled_for_new_repositories = false + dependency_graph_enabled_for_new_repositories = false + secret_scanning_enabled_for_new_repositories = false + secret_scanning_push_protection_enabled_for_new_repositories = false + }` + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "billing_email", "test@example.com", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "members_can_create_private_repositories", "false", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "members_can_create_internal_repositories", "false", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "members_can_fork_private_repositories", "false", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "web_commit_signoff_required", "false", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "advanced_security_enabled_for_new_repositories", "false", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "dependabot_alerts_enabled_for_new_repositories", "false", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "dependabot_security_updates_enabled_for_new_repositories", "false", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "dependency_graph_enabled_for_new_repositories", "false", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "secret_scanning_enabled_for_new_repositories", "false", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "secret_scanning_push_protection_enabled_for_new_repositories", "false", + ), + ) + testCase := func(t *testing.T, mode string) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, mode) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + }) + } + t.Run("run with an anonymous account", func(t *testing.T) { + t.Skip("anonymous account not supported for this operation") + }) + t.Run("run with an individual account", func(t *testing.T) { + t.Skip("individual account not supported for this operation") + }) + t.Run("run with an organization account", func(t *testing.T) { + testCase(t, organization) + }) + }) + + t.Run("handles mixed boolean values correctly", func(t *testing.T) { + config := ` + resource "github_organization_settings" "test" { + billing_email = "test@example.com" + members_can_create_private_repositories = false + members_can_create_internal_repositories = true + members_can_fork_private_repositories = false + web_commit_signoff_required = true + advanced_security_enabled_for_new_repositories = false + dependabot_alerts_enabled_for_new_repositories = true + dependabot_security_updates_enabled_for_new_repositories = false + dependency_graph_enabled_for_new_repositories = true + secret_scanning_enabled_for_new_repositories = false + secret_scanning_push_protection_enabled_for_new_repositories = true + }` + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "billing_email", "test@example.com", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "members_can_create_private_repositories", "false", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "members_can_create_internal_repositories", "true", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "members_can_fork_private_repositories", "false", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "web_commit_signoff_required", "true", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "advanced_security_enabled_for_new_repositories", "false", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "dependabot_alerts_enabled_for_new_repositories", "true", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "dependabot_security_updates_enabled_for_new_repositories", "false", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "dependency_graph_enabled_for_new_repositories", "true", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "secret_scanning_enabled_for_new_repositories", "false", + ), + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "secret_scanning_push_protection_enabled_for_new_repositories", "true", + ), + ) + testCase := func(t *testing.T, mode string) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, mode) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + }) + } + t.Run("run with an anonymous account", func(t *testing.T) { + t.Skip("anonymous account not supported for this operation") + }) + t.Run("run with an individual account", func(t *testing.T) { + t.Skip("individual account not supported for this operation") + }) + t.Run("run with an organization account", func(t *testing.T) { + testCase(t, organization) + }) + }) + + t.Run("handles minimal configuration without errors", func(t *testing.T) { + config := ` + resource "github_organization_settings" "test" { + billing_email = "test@example.com" + }` + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_organization_settings.test", + "billing_email", "test@example.com", + ), + ) + testCase := func(t *testing.T, mode string) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, mode) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + }) + } + t.Run("run with an anonymous account", func(t *testing.T) { + t.Skip("anonymous account not supported for this operation") + }) + t.Run("run with an individual account", func(t *testing.T) { + t.Skip("individual account not supported for this operation") + }) + t.Run("run with an organization account", func(t *testing.T) { + testCase(t, organization) + }) + }) + + t.Run("comprehensive parameter testing", func(t *testing.T) { + t.Run("test all string fields", func(t *testing.T) { + config := ` + resource "github_organization_settings" "test" { + billing_email = "test@example.com" + company = "Test Company" + email = "contact@test.com" + twitter_username = "testorg" + location = "Test City, Country" + name = "Test Organization" + description = "Test organization description" + blog = "https://test.com/blog" + }` + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_organization_settings.test", "billing_email", "test@example.com"), + resource.TestCheckResourceAttr("github_organization_settings.test", "company", "Test Company"), + resource.TestCheckResourceAttr("github_organization_settings.test", "email", "contact@test.com"), + resource.TestCheckResourceAttr("github_organization_settings.test", "twitter_username", "testorg"), + resource.TestCheckResourceAttr("github_organization_settings.test", "location", "Test City, Country"), + resource.TestCheckResourceAttr("github_organization_settings.test", "name", "Test Organization"), + resource.TestCheckResourceAttr("github_organization_settings.test", "description", "Test organization description"), + resource.TestCheckResourceAttr("github_organization_settings.test", "blog", "https://test.com/blog"), + ) + + testCase := resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, organization) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + } + + t.Run("run with an anonymous account", func(t *testing.T) { + t.Skip("anonymous account not supported for this operation") + }) + t.Run("run with an individual account", func(t *testing.T) { + t.Skip("individual account not supported for this operation") + }) + t.Run("run with an organization account", func(t *testing.T) { + resource.Test(t, testCase) + }) + }) + + t.Run("test all security boolean fields", func(t *testing.T) { + config := ` + resource "github_organization_settings" "test" { + billing_email = "test@example.com" + advanced_security_enabled_for_new_repositories = true + dependabot_alerts_enabled_for_new_repositories = true + dependabot_security_updates_enabled_for_new_repositories = true + dependency_graph_enabled_for_new_repositories = true + secret_scanning_enabled_for_new_repositories = true + secret_scanning_push_protection_enabled_for_new_repositories = true + }` + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_organization_settings.test", "billing_email", "test@example.com"), + resource.TestCheckResourceAttr("github_organization_settings.test", "advanced_security_enabled_for_new_repositories", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "dependabot_alerts_enabled_for_new_repositories", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "dependabot_security_updates_enabled_for_new_repositories", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "dependency_graph_enabled_for_new_repositories", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "secret_scanning_enabled_for_new_repositories", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "secret_scanning_push_protection_enabled_for_new_repositories", "true"), + ) + + testCase := resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, organization) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + } + + t.Run("run with an anonymous account", func(t *testing.T) { + t.Skip("anonymous account not supported for this operation") + }) + t.Run("run with an individual account", func(t *testing.T) { + t.Skip("individual account not supported for this operation") + }) + t.Run("run with an organization account", func(t *testing.T) { + resource.Test(t, testCase) + }) + }) + + t.Run("test repository creation fields", func(t *testing.T) { + config := ` + resource "github_organization_settings" "test" { + billing_email = "test@example.com" + members_can_create_private_repositories = true + members_can_create_internal_repositories = true + members_can_create_pages = true + members_can_create_public_pages = true + members_can_create_private_pages = true + }` + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_organization_settings.test", "billing_email", "test@example.com"), + resource.TestCheckResourceAttr("github_organization_settings.test", "members_can_create_private_repositories", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "members_can_create_internal_repositories", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "members_can_create_pages", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "members_can_create_public_pages", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "members_can_create_private_pages", "true"), + ) + + testCase := resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, organization) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + } + + t.Run("run with an anonymous account", func(t *testing.T) { + t.Skip("anonymous account not supported for this operation") + }) + t.Run("run with an individual account", func(t *testing.T) { + t.Skip("individual account not supported for this operation") + }) + t.Run("run with an organization account", func(t *testing.T) { + resource.Test(t, testCase) + }) + }) + + t.Run("test other boolean fields", func(t *testing.T) { + config := ` + resource "github_organization_settings" "test" { + billing_email = "test@example.com" + web_commit_signoff_required = true + has_organization_projects = true + has_repository_projects = true + }` + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_organization_settings.test", "billing_email", "test@example.com"), + resource.TestCheckResourceAttr("github_organization_settings.test", "web_commit_signoff_required", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "has_organization_projects", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "has_repository_projects", "true"), + ) + + testCase := resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, organization) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + } + + t.Run("run with an anonymous account", func(t *testing.T) { + t.Skip("anonymous account not supported for this operation") + }) + t.Run("run with an individual account", func(t *testing.T) { + t.Skip("individual account not supported for this operation") + }) + t.Run("run with an organization account", func(t *testing.T) { + resource.Test(t, testCase) + }) + }) + + t.Run("test enum fields", func(t *testing.T) { + config := ` + resource "github_organization_settings" "test" { + billing_email = "test@example.com" + default_repository_permission = "write" + }` + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_organization_settings.test", "billing_email", "test@example.com"), + resource.TestCheckResourceAttr("github_organization_settings.test", "default_repository_permission", "write"), + ) + + testCase := resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, organization) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + } + + t.Run("run with an anonymous account", func(t *testing.T) { + t.Skip("anonymous account not supported for this operation") + }) + t.Run("run with an individual account", func(t *testing.T) { + t.Skip("individual account not supported for this operation") + }) + t.Run("run with an organization account", func(t *testing.T) { + resource.Test(t, testCase) + }) + }) + + t.Run("test comprehensive configuration", func(t *testing.T) { + config := ` + resource "github_organization_settings" "test" { + billing_email = "test@example.com" + company = "Test Company" + email = "contact@test.com" + twitter_username = "testorg" + location = "Test City, Country" + name = "Test Organization" + description = "Test organization description" + blog = "https://test.com/blog" + + advanced_security_enabled_for_new_repositories = true + dependabot_alerts_enabled_for_new_repositories = true + dependabot_security_updates_enabled_for_new_repositories = true + dependency_graph_enabled_for_new_repositories = true + secret_scanning_enabled_for_new_repositories = true + secret_scanning_push_protection_enabled_for_new_repositories = true + + members_can_create_private_repositories = true + members_can_create_internal_repositories = true + members_can_create_pages = true + members_can_create_public_pages = true + members_can_create_private_pages = true + + web_commit_signoff_required = true + default_repository_permission = "write" + }` + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_organization_settings.test", "billing_email", "test@example.com"), + resource.TestCheckResourceAttr("github_organization_settings.test", "company", "Test Company"), + resource.TestCheckResourceAttr("github_organization_settings.test", "email", "contact@test.com"), + resource.TestCheckResourceAttr("github_organization_settings.test", "twitter_username", "testorg"), + resource.TestCheckResourceAttr("github_organization_settings.test", "location", "Test City, Country"), + resource.TestCheckResourceAttr("github_organization_settings.test", "name", "Test Organization"), + resource.TestCheckResourceAttr("github_organization_settings.test", "description", "Test organization description"), + resource.TestCheckResourceAttr("github_organization_settings.test", "blog", "https://test.com/blog"), + resource.TestCheckResourceAttr("github_organization_settings.test", "advanced_security_enabled_for_new_repositories", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "dependabot_alerts_enabled_for_new_repositories", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "dependabot_security_updates_enabled_for_new_repositories", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "dependency_graph_enabled_for_new_repositories", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "secret_scanning_enabled_for_new_repositories", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "secret_scanning_push_protection_enabled_for_new_repositories", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "members_can_create_private_repositories", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "members_can_create_internal_repositories", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "members_can_create_pages", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "members_can_create_public_pages", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "members_can_create_private_pages", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "web_commit_signoff_required", "true"), + resource.TestCheckResourceAttr("github_organization_settings.test", "default_repository_permission", "write"), + ) + + testCase := resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, organization) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + } + + t.Run("run with an anonymous account", func(t *testing.T) { + t.Skip("anonymous account not supported for this operation") + }) + t.Run("run with an individual account", func(t *testing.T) { + t.Skip("individual account not supported for this operation") + }) + t.Run("run with an organization account", func(t *testing.T) { + resource.Test(t, testCase) + }) + }) + + t.Run("test boolean false values for all fields", func(t *testing.T) { + config := ` + resource "github_organization_settings" "test" { + billing_email = "test@example.com" + advanced_security_enabled_for_new_repositories = false + dependabot_alerts_enabled_for_new_repositories = false + dependabot_security_updates_enabled_for_new_repositories = false + dependency_graph_enabled_for_new_repositories = false + secret_scanning_enabled_for_new_repositories = false + secret_scanning_push_protection_enabled_for_new_repositories = false + members_can_create_private_repositories = false + members_can_create_internal_repositories = false + members_can_create_pages = false + members_can_create_public_pages = false + members_can_create_private_pages = false + web_commit_signoff_required = false + }` + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_organization_settings.test", "billing_email", "test@example.com"), + resource.TestCheckResourceAttr("github_organization_settings.test", "advanced_security_enabled_for_new_repositories", "false"), + resource.TestCheckResourceAttr("github_organization_settings.test", "dependabot_alerts_enabled_for_new_repositories", "false"), + resource.TestCheckResourceAttr("github_organization_settings.test", "dependabot_security_updates_enabled_for_new_repositories", "false"), + resource.TestCheckResourceAttr("github_organization_settings.test", "dependency_graph_enabled_for_new_repositories", "false"), + resource.TestCheckResourceAttr("github_organization_settings.test", "secret_scanning_enabled_for_new_repositories", "false"), + resource.TestCheckResourceAttr("github_organization_settings.test", "secret_scanning_push_protection_enabled_for_new_repositories", "false"), + resource.TestCheckResourceAttr("github_organization_settings.test", "members_can_create_private_repositories", "false"), + resource.TestCheckResourceAttr("github_organization_settings.test", "members_can_create_internal_repositories", "false"), + resource.TestCheckResourceAttr("github_organization_settings.test", "members_can_create_pages", "false"), + resource.TestCheckResourceAttr("github_organization_settings.test", "members_can_create_public_pages", "false"), + resource.TestCheckResourceAttr("github_organization_settings.test", "members_can_create_private_pages", "false"), + resource.TestCheckResourceAttr("github_organization_settings.test", "web_commit_signoff_required", "false"), + ) + + testCase := resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, organization) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + } + + t.Run("run with an anonymous account", func(t *testing.T) { + t.Skip("anonymous account not supported for this operation") + }) + t.Run("run with an individual account", func(t *testing.T) { + t.Skip("individual account not supported for this operation") + }) + t.Run("run with an organization account", func(t *testing.T) { + resource.Test(t, testCase) + }) + }) + + t.Run("test enum field variations", func(t *testing.T) { + config := ` + resource "github_organization_settings" "test" { + billing_email = "test@example.com" + default_repository_permission = "admin" + }` + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_organization_settings.test", "billing_email", "test@example.com"), + resource.TestCheckResourceAttr("github_organization_settings.test", "default_repository_permission", "admin"), + ) + + testCase := resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, organization) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + } + + t.Run("run with an anonymous account", func(t *testing.T) { + t.Skip("anonymous account not supported for this operation") + }) + t.Run("run with an individual account", func(t *testing.T) { + t.Skip("individual account not supported for this operation") + }) + t.Run("run with an organization account", func(t *testing.T) { + resource.Test(t, testCase) + }) + }) + }) }