From 058a2fcc67087a5106cd18d35e2c8e6abb36044f Mon Sep 17 00:00:00 2001 From: Steve Hipwell Date: Tue, 27 Jan 2026 11:31:04 +0000 Subject: [PATCH] fix: Correct forking and vulnerability alert logic Signed-off-by: Steve Hipwell --- ...rce_github_organization_role_users_test.go | 8 + github/resource_github_repository.go | 59 +- github/resource_github_repository_test.go | 689 ++++++++---------- website/docs/r/repository.html.markdown | 10 +- 4 files changed, 328 insertions(+), 438 deletions(-) diff --git a/github/data_source_github_organization_role_users_test.go b/github/data_source_github_organization_role_users_test.go index 895d2380d7..df58d0bb85 100644 --- a/github/data_source_github_organization_role_users_test.go +++ b/github/data_source_github_organization_role_users_test.go @@ -10,6 +10,10 @@ import ( func TestAccDataSourceGithubOrganizationRoleUsers(t *testing.T) { t.Run("get the organization role users without error", func(t *testing.T) { + if testAccConf.testOrgUser == "" { + t.Skip("Skipping test because no organization user has been configured") + } + roleId := 8134 config := fmt.Sprintf(` resource "github_organization_role_user" "test" { @@ -44,6 +48,10 @@ func TestAccDataSourceGithubOrganizationRoleUsers(t *testing.T) { }) t.Run("get indirect organization role users without error", func(t *testing.T) { + if testAccConf.testOrgUser == "" { + t.Skip("Skipping test because no organization user has been configured") + } + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) teamName := fmt.Sprintf("%steam-%s", testResourcePrefix, randomID) roleId := 8134 diff --git a/github/resource_github_repository.go b/github/resource_github_repository.go index 0f0c3a5791..1eca0f28ff 100644 --- a/github/resource_github_repository.go +++ b/github/resource_github_repository.go @@ -23,12 +23,7 @@ func resourceGithubRepository() *schema.Resource { UpdateContext: resourceGithubRepositoryUpdate, DeleteContext: resourceGithubRepositoryDelete, Importer: &schema.ResourceImporter{ - StateContext: func(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { - if err := d.Set("auto_init", false); err != nil { - return nil, err - } - return []*schema.ResourceData{d}, nil - }, + StateContext: resourceGithubRepositoryImport, }, SchemaVersion: 1, @@ -409,6 +404,7 @@ func resourceGithubRepository() *schema.Resource { "ignore_vulnerability_alerts_during_read": { Type: schema.TypeBool, Optional: true, + Default: false, Description: "Set to true to not call the vulnerability alerts endpoint so the resource can also be used without admin permissions during read.", }, "full_name": { @@ -634,7 +630,7 @@ func resourceGithubRepositoryObject(d *schema.ResourceData) *github.Repository { } // only configure allow forking if repository is not public - if allowForking, ok := d.GetOk("allow_forking"); ok && visibility != "public" { + if allowForking, ok := d.GetOkExists("allow_forking"); ok && visibility != "public" { //nolint:staticcheck,SA1019 // We sometimes need to use GetOkExists for booleans if val, ok := allowForking.(bool); ok { repository.AllowForking = github.Ptr(val) } @@ -773,11 +769,6 @@ func resourceGithubRepositoryCreate(ctx context.Context, d *schema.ResourceData, } } - err := updateVulnerabilityAlerts(d, client, ctx, owner, repoName) - if err != nil { - return diag.FromErr(err) - } - return resourceGithubRepositoryUpdate(ctx, d, meta) } @@ -1013,10 +1004,12 @@ func resourceGithubRepositoryUpdate(ctx context.Context, d *schema.ResourceData, } } - if d.HasChange("vulnerability_alerts") { - err = updateVulnerabilityAlerts(d, client, ctx, owner, repoName) - if err != nil { - return diag.FromErr(err) + if v, ok := d.GetOkExists("vulnerability_alerts"); ok { //nolint:staticcheck,SA1019 // We sometimes need to use GetOkExists for booleans + if val, ok := v.(bool); ok { + err := updateVulnerabilityAlerts(ctx, client, owner, repoName, val) + if err != nil { + return diag.FromErr(err) + } } } @@ -1065,6 +1058,16 @@ func resourceGithubRepositoryDelete(ctx context.Context, d *schema.ResourceData, return diag.FromErr(err) } +func resourceGithubRepositoryImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + if err := d.Set("auto_init", false); err != nil { + return nil, err + } + if err := d.Set("ignore_vulnerability_alerts_during_read", true); err != nil { + return nil, err + } + return []*schema.ResourceData{d}, nil +} + func expandPages(input []any) *github.Pages { if len(input) == 0 || input[0] == nil { return nil @@ -1240,23 +1243,17 @@ func resourceGithubParseFullName(resourceDataLike interface { return parts[0], parts[1], true } -func updateVulnerabilityAlerts(d *schema.ResourceData, client *github.Client, ctx context.Context, owner, repoName string) error { - updateVulnerabilityAlertsSDK := client.Repositories.DisableVulnerabilityAlerts - vulnerabilityAlerts, ok := d.GetOk("vulnerability_alerts") +func updateVulnerabilityAlerts(ctx context.Context, client *github.Client, owner, repoName string, state bool) error { + var err error - // Only if the vulnerability alerts are specifically set to true, enable them. - // Otherwise, disable them as GitHub defaults to enabled and we have not wanted to introduce a breaking change for this yet. - if ok && vulnerabilityAlerts.(bool) { - updateVulnerabilityAlertsSDK = client.Repositories.EnableVulnerabilityAlerts + if state { + _, err = client.Repositories.EnableVulnerabilityAlerts(ctx, owner, repoName) + } else { + _, err = client.Repositories.DisableVulnerabilityAlerts(ctx, owner, repoName) } - - resp, err := updateVulnerabilityAlertsSDK(ctx, owner, repoName) if err != nil { - // Check if the error is because an Organization or Enterprise policy is preventing the change - // This is a temporary workaround while we extract Vulnerability Alerts into a separate resource. - if resp.StatusCode == http.StatusUnprocessableEntity && strings.Contains(err.Error(), "An enforced security configuration prevented modifying") && !ok { - return nil - } + return err } - return err + + return nil } diff --git a/github/resource_github_repository_test.go b/github/resource_github_repository_test.go index b83f0c7c62..3c1efff2a7 100644 --- a/github/resource_github_repository_test.go +++ b/github/resource_github_repository_test.go @@ -131,7 +131,7 @@ func TestAccGithubRepository(t *testing.T) { t.Run("imports repositories without error", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - testRepoName := fmt.Sprintf("%simport-%s", testResourcePrefix, randomID) + testRepoName := fmt.Sprintf("%s%s", testResourcePrefix, randomID) config := fmt.Sprintf(` resource "github_repository" "test" { name = "%s" @@ -153,9 +153,10 @@ func TestAccGithubRepository(t *testing.T) { Check: check, }, { - ResourceName: "github_repository.test", - ImportState: true, - ImportStateVerify: true, + ResourceName: "github_repository.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"auto_init", "ignore_vulnerability_alerts_during_read", "vulnerability_alerts"}, }, }, }) @@ -527,18 +528,148 @@ func TestAccGithubRepository(t *testing.T) { }) }) - t.Run("configures vulnerability alerts for a public repository", func(t *testing.T) { + t.Run("create_private_with_forking", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%s%s", testResourcePrefix, randomID) + + config := fmt.Sprintf(` +resource "github_repository" "test" { + name = "%s" + visibility = "private" + + allow_forking = true +} +`, repoName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_repository.test", "visibility", "private"), + resource.TestCheckResourceAttr("github_repository.test", "allow_forking", "true"), + ), + }, + }, + }) + }) + + t.Run("create_private_without_forking", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - repoName := fmt.Sprintf("%spub-vuln-%s", testResourcePrefix, randomID) + repoName := fmt.Sprintf("%s%s", testResourcePrefix, randomID) config := fmt.Sprintf(` resource "github_repository" "test" { name = "%s" - visibility = "public" + visibility = "private" + + allow_forking = false } `, repoName) - configUpdate := fmt.Sprintf(` + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_repository.test", "visibility", "private"), + resource.TestCheckResourceAttr("github_repository.test", "allow_forking", "false"), + ), + }, + }, + }) + }) + + t.Run("create_private_with_forking_unset", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%s%s", testResourcePrefix, randomID) + + config := fmt.Sprintf(` +resource "github_repository" "test" { + name = "%s" + visibility = "private" +} +`, repoName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_repository.test", "visibility", "private"), + resource.TestCheckResourceAttrSet("github_repository.test", "allow_forking"), + ), + }, + }, + }) + }) + + t.Run("update_public_to_private_allow_forking", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + testRepoName := fmt.Sprintf("%svisibility-%s", testResourcePrefix, randomID) + config := fmt.Sprintf(` + resource "github_repository" "test" { + name = "%s" + visibility = "public" + } + `, testRepoName) + + configPrivate := fmt.Sprintf(` + resource "github_repository" "test" { + name = "%s" + visibility = "private" + allow_forking = false + } + `, testRepoName) + + configPrivateForking := fmt.Sprintf(` + resource "github_repository" "test" { + name = "%s" + visibility = "private" + allow_forking = true + } + `, testRepoName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_repository.test", "visibility", "public"), + resource.TestCheckResourceAttr("github_repository.test", "allow_forking", "true"), + ), + }, + { + Config: configPrivate, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_repository.test", "visibility", "private"), + resource.TestCheckResourceAttr("github_repository.test", "allow_forking", "false"), + ), + }, + { + Config: configPrivateForking, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_repository.test", "visibility", "private"), + resource.TestCheckResourceAttr("github_repository.test", "allow_forking", "true"), + ), + }, + }, + }) + }) + + t.Run("create_with_vulnerability_alerts", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%s%s", testResourcePrefix, randomID) + + config := fmt.Sprintf(` resource "github_repository" "test" { name = "%s" visibility = "public" @@ -554,91 +685,118 @@ func TestAccGithubRepository(t *testing.T) { { Config: config, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "visibility", "public"), - resource.TestCheckResourceAttr("github_repository.test", "vulnerability_alerts", "false"), + resource.TestCheckResourceAttr("github_repository.test", "vulnerability_alerts", "true"), + func(s *terraform.State) error { + return nil + }, ), }, + }, + }) + }) + + t.Run("create_without_vulnerability_alerts", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%s%s", testResourcePrefix, randomID) + + config := fmt.Sprintf(` + resource "github_repository" "test" { + name = "%s" + visibility = "public" + + vulnerability_alerts = false + } + `, repoName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ { - Config: configUpdate, + Config: config, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "visibility", "public"), - resource.TestCheckResourceAttr("github_repository.test", "vulnerability_alerts", "true"), + resource.TestCheckResourceAttr("github_repository.test", "vulnerability_alerts", "false"), + func(s *terraform.State) error { + return nil + }, ), }, }, }) }) - t.Run("create_private_with_forking", func(t *testing.T) { + t.Run("create_with_vulnerability_alerts_unset", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) repoName := fmt.Sprintf("%s%s", testResourcePrefix, randomID) config := fmt.Sprintf(` resource "github_repository" "test" { name = "%s" - visibility = "private" - - allow_forking = true + visibility = "public" } `, repoName) resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnlessHasOrgs(t) }, + PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "visibility", "private"), - resource.TestCheckResourceAttr("github_repository.test", "allow_forking", "true"), + resource.TestCheckResourceAttrSet("github_repository.test", "vulnerability_alerts"), + func(s *terraform.State) error { + return nil + }, ), }, }, }) }) - t.Run("create_private_with_forking_unset", func(t *testing.T) { + t.Run("create_with_vulnerability_alerts_unset_no_read", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) repoName := fmt.Sprintf("%s%s", testResourcePrefix, randomID) config := fmt.Sprintf(` -resource "github_repository" "test" { - name = "%s" - description = "A private repository with forking disabled" - visibility = "private" -} -`, repoName) + resource "github_repository" "test" { + name = "%s" + visibility = "public" + + ignore_vulnerability_alerts_during_read = true + } + `, repoName) resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnlessHasOrgs(t) }, + PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "visibility", "private"), - resource.TestCheckResourceAttr("github_repository.test", "allow_forking", "false"), + resource.TestCheckNoResourceAttr("github_repository.test", "vulnerability_alerts"), ), }, }, }) }) - t.Run("configures vulnerability alerts for a private repository", func(t *testing.T) { + t.Run("update_vulnerability_alerts", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - repoName := fmt.Sprintf("%sprv-vuln-%s", testResourcePrefix, randomID) + repoName := fmt.Sprintf("%s%s", testResourcePrefix, randomID) config := fmt.Sprintf(` resource "github_repository" "test" { name = "%s" - visibility = "private" + visibility = "public" + + vulnerability_alerts = false } `, repoName) configUpdate := fmt.Sprintf(` resource "github_repository" "test" { name = "%s" - visibility = "private" + visibility = "public" vulnerability_alerts = true } @@ -651,14 +809,15 @@ resource "github_repository" "test" { { Config: config, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "visibility", "private"), resource.TestCheckResourceAttr("github_repository.test", "vulnerability_alerts", "false"), + func(s *terraform.State) error { + return nil + }, ), }, { Config: configUpdate, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "visibility", "private"), resource.TestCheckResourceAttr("github_repository.test", "vulnerability_alerts", "true"), ), }, @@ -1110,61 +1269,6 @@ resource "github_repository" "test" { }) }) - t.Run("update_public_to_private_allow_forking", func(t *testing.T) { - randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - testRepoName := fmt.Sprintf("%svisibility-%s", testResourcePrefix, randomID) - config := fmt.Sprintf(` - resource "github_repository" "test" { - name = "%s" - visibility = "public" - } - `, testRepoName) - - configPrivate := fmt.Sprintf(` - resource "github_repository" "test" { - name = "%s" - visibility = "private" - allow_forking = false - } - `, testRepoName) - - configPrivateForking := fmt.Sprintf(` - resource "github_repository" "test" { - name = "%s" - visibility = "private" - allow_forking = true - } - `, testRepoName) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnauthenticated(t) }, - ProviderFactories: providerFactories, - Steps: []resource.TestStep{ - { - Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "visibility", "public"), - resource.TestCheckResourceAttr("github_repository.test", "allow_forking", "true"), - ), - }, - { - Config: configPrivate, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "visibility", "private"), - resource.TestCheckResourceAttr("github_repository.test", "allow_forking", "false"), - ), - }, - { - Config: configPrivateForking, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "visibility", "private"), - resource.TestCheckResourceAttr("github_repository.test", "allow_forking", "true"), - ), - }, - }, - }) - }) - t.Run("updates repos to public visibility", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) testRepoName := fmt.Sprintf("%spublic-vuln-%s", testResourcePrefix, randomID) @@ -1339,110 +1443,7 @@ func Test_expandPages(t *testing.T) { t.Errorf("got %q; want %q", pages.GetSource().GetPath(), "/docs") } }) -} - -func TestGithubRepositoryTopicPassesValidation(t *testing.T) { - resource := resourceGithubRepository() - schema := resource.Schema["topics"].Elem.(*schema.Schema) - diags := schema.ValidateDiagFunc("ef69e1a3-66be-40ca-bb62-4f36186aa292", cty.Path{cty.GetAttrStep{Name: "topic"}}) - if diags.HasError() { - t.Error(fmt.Errorf("unexpected topic validation failure: %s", diags[0].Summary)) - } -} - -func TestGithubRepositoryTopicFailsValidationWhenOverMaxCharacters(t *testing.T) { - resource := resourceGithubRepository() - schema := resource.Schema["topics"].Elem.(*schema.Schema) - - diags := schema.ValidateDiagFunc(strings.Repeat("a", 51), cty.Path{cty.GetAttrStep{Name: "topic"}}) - if len(diags) != 1 { - t.Error(fmt.Errorf("unexpected number of topic validation failures; expected=1; actual=%d", len(diags))) - } - expectedFailure := "invalid value for topics (must include only lowercase alphanumeric characters or hyphens and cannot start with a hyphen and consist of 50 characters or less)" - actualFailure := diags[0].Summary - if expectedFailure != actualFailure { - t.Error(fmt.Errorf("unexpected topic validation failure; expected=%s; action=%s", expectedFailure, actualFailure)) - } -} - -func reconfigureVisibility(config, visibility string) string { - re := regexp.MustCompile(`visibility = "(.*)"`) - newConfig := re.ReplaceAllString( - config, - fmt.Sprintf(`visibility = "%s"`, visibility), - ) - return newConfig -} - -type resourceDataLike map[string]any - -func (d resourceDataLike) GetOk(key string) (any, bool) { - v, ok := d[key] - return v, ok -} - -func TestResourceGithubParseFullName(t *testing.T) { - repo, org, ok := resourceGithubParseFullName(resourceDataLike(map[string]any{"full_name": "myrepo/myorg"})) - assert.True(t, ok) - assert.Equal(t, "myrepo", repo) - assert.Equal(t, "myorg", org) - _, _, ok = resourceGithubParseFullName(resourceDataLike(map[string]any{})) - assert.False(t, ok) - _, _, ok = resourceGithubParseFullName(resourceDataLike(map[string]any{"full_name": "malformed"})) - assert.False(t, ok) -} - -func testCheckResourceAttrContains(resourceName, attributeName, substring string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[resourceName] - if !ok { - return fmt.Errorf("Resource not found: %s", resourceName) - } - - value, ok := rs.Primary.Attributes[attributeName] - if !ok { - return fmt.Errorf("Attribute not found: %s", attributeName) - } - - if !strings.Contains(value, substring) { - return fmt.Errorf("Attribute '%s' does not contain '%s'", value, substring) - } - - return nil - } -} - -func TestGithubRepositoryNameFailsValidationWhenOverMaxCharacters(t *testing.T) { - resource := resourceGithubRepository() - schema := resource.Schema["name"] - - diags := schema.ValidateDiagFunc(strings.Repeat("a", 101), cty.GetAttrPath("name")) - if len(diags) != 1 { - t.Error(fmt.Errorf("unexpected number of name validation failures; expected=1; actual=%d", len(diags))) - } - expectedFailure := "invalid value for name (must include only alphanumeric characters, underscores or hyphens and consist of 100 characters or less)" - actualFailure := diags[0].Summary - if expectedFailure != actualFailure { - t.Error(fmt.Errorf("unexpected name validation failure; expected=%s; action=%s", expectedFailure, actualFailure)) - } -} - -func TestGithubRepositoryNameFailsValidationWithSpace(t *testing.T) { - resource := resourceGithubRepository() - schema := resource.Schema["name"] - - diags := schema.ValidateDiagFunc("test space", cty.GetAttrPath("name")) - if len(diags) != 1 { - t.Error(fmt.Errorf("unexpected number of name validation failures; expected=1; actual=%d", len(diags))) - } - expectedFailure := "invalid value for name (must include only alphanumeric characters, underscores or hyphens and consist of 100 characters or less)" - actualFailure := diags[0].Summary - if expectedFailure != actualFailure { - t.Error(fmt.Errorf("unexpected name validation failure; expected=%s; action=%s", expectedFailure, actualFailure)) - } -} -func TestAccGithubRepository_fork(t *testing.T) { t.Run("forks a repository without error", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) testRepoName := fmt.Sprintf("%sfork-%s", testResourcePrefix, randomID) @@ -1561,226 +1562,110 @@ func TestAccGithubRepository_fork(t *testing.T) { ResourceName: "github_repository.forked_update", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"auto_init"}, + ImportStateVerifyIgnore: []string{"auto_init", "ignore_vulnerability_alerts_during_read", "vulnerability_alerts"}, }, }, }) }) +} - // t.Run("can migrate a forked repository from a previous framework version", func(t *testing.T) { - // rName := fmt.Sprintf("terraform-provider-github-%s", randomID) - // olderConfig := fmt.Sprintf(` - // import { - // to = github_repository.forked - // id = "%[1]s" - // } - // resource "github_repository" "forked" { - // name = "%[1]s" - // description = "Terraform acceptance test - forked repository %[1]s" - // } - // `, rName) - // newerConfig := fmt.Sprintf(` - // resource "github_repository" "forked" { - // name = "%[1]s" - // description = "Terraform acceptance test - forked repository %[1]s" - // fork = true - // source_owner = "integrations" - // source_repo = "terraform-provider-github" - // } - // `, rName) - - // providers := []*schema.Provider{testAccProvider} - // resource.Test(t, resource.TestCase{ - // PreCheck: func() { skipUnauthenticated(t) }, - // Steps: []resource.TestStep{ - // { - // ExternalProviders: map[string]resource.ExternalProvider{ - // "github": { - // VersionConstraint: "~> 6.7.0", - // Source: "integrations/github", - // }, - // }, - // PreConfig: func() { - // err := createForkedRepository(rName) - // if err != nil { - // t.Fatalf("failed to create fork of %s: %v", rName, err) - // } - // }, - // Config: olderConfig, - // Check: resource.ComposeTestCheckFunc( - // resource.TestCheckNoResourceAttr( - // "github_repository.forked", "fork", - // ), - // resource.TestCheckNoResourceAttr( - // "github_repository.forked", "source_owner", - // ), - // resource.TestCheckNoResourceAttr( - // "github_repository.forked", "source_repo", - // ), - // ), - // }, - // { - // ProviderFactories: testAccProviderFactories(&providers), - // Config: newerConfig, - // Check: resource.ComposeTestCheckFunc( - // resource.TestCheckResourceAttr( - // "github_repository.forked", "fork", - // "true", - // ), - // resource.TestCheckResourceAttr( - // "github_repository.forked", "source_owner", - // "integrations", - // ), - // resource.TestCheckResourceAttr( - // "github_repository.forked", "source_repo", - // "terraform-provider-github", - // ), - // ), - // }, - // }, - // }) - // }) +func TestGithubRepositoryTopicPassesValidation(t *testing.T) { + resource := resourceGithubRepository() + schema := resource.Schema["topics"].Elem.(*schema.Schema) + diags := schema.ValidateDiagFunc("ef69e1a3-66be-40ca-bb62-4f36186aa292", cty.Path{cty.GetAttrStep{Name: "topic"}}) + if diags.HasError() { + t.Error(fmt.Errorf("unexpected topic validation failure: %s", diags[0].Summary)) + } } -// func createForkedRepository(ctx context.Context, repositoryName string) error { -// config := Config{BaseURL: "https://api.github.com/", Owner: testAccConf.owner, Token: testAccConf.token} -// meta, err := config.Meta() -// if err != nil { -// return fmt.Errorf("failed to create client: %w", err) -// } -// client := meta.(*Owner).v3client -// orgName := meta.(*Owner).name - -// _, _, err = client.Repositories.CreateFork(ctx, "integrations", "snappydoo", &github.RepositoryCreateForkOptions{ -// Organization: orgName, -// Name: repositoryName, -// }) - -// acceptedError := &github.AcceptedError{} -// if err != nil { -// if errors.As(err, &acceptedError) { -// return nil -// } -// return fmt.Errorf("failed to create fork: %w", err) -// } -// return nil -// } - -func TestAccRepository_VulnerabilityAlerts(t *testing.T) { - t.Run("can enable vulnerability alerts", func(t *testing.T) { - randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - testRepoName := fmt.Sprintf("%svulnerability-alerts-%s", testResourcePrefix, randomID) - config := fmt.Sprintf(` - resource "github_repository" "test" { - name = "%s" - description = "Terraform acceptance test - repository %s" - vulnerability_alerts = true - } - `, testRepoName, randomID) +func TestGithubRepositoryTopicFailsValidationWhenOverMaxCharacters(t *testing.T) { + resource := resourceGithubRepository() + schema := resource.Schema["topics"].Elem.(*schema.Schema) - resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnauthenticated(t) }, - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository.test", "vulnerability_alerts", - "true", - ), - ), - }, - }, - }) - }) + diags := schema.ValidateDiagFunc(strings.Repeat("a", 51), cty.Path{cty.GetAttrStep{Name: "topic"}}) + if len(diags) != 1 { + t.Error(fmt.Errorf("unexpected number of topic validation failures; expected=1; actual=%d", len(diags))) + } + expectedFailure := "invalid value for topics (must include only lowercase alphanumeric characters or hyphens and cannot start with a hyphen and consist of 50 characters or less)" + actualFailure := diags[0].Summary + if expectedFailure != actualFailure { + t.Error(fmt.Errorf("unexpected topic validation failure; expected=%s; action=%s", expectedFailure, actualFailure)) + } +} - t.Run("sets vulnerability alerts to false when not set in config", func(t *testing.T) { - randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - testRepoName := fmt.Sprintf("%svulnerability-alerts-%s", testResourcePrefix, randomID) - config := fmt.Sprintf(` - resource "github_repository" "test" { - name = "%s" - description = "Terraform acceptance test - repository %s" - } - `, testRepoName, randomID) +func reconfigureVisibility(config, visibility string) string { + re := regexp.MustCompile(`visibility = "(.*)"`) + newConfig := re.ReplaceAllString( + config, + fmt.Sprintf(`visibility = "%s"`, visibility), + ) + return newConfig +} - resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnauthenticated(t) }, - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: config, - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository.test", "vulnerability_alerts", "false", - ), - ), - }, - }, - }) - }) +type resourceDataLike map[string]any - t.Run("can disable vulnerability alerts", func(t *testing.T) { - randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - testRepoName := fmt.Sprintf("%svulnerability-alerts-%s", testResourcePrefix, randomID) - config := fmt.Sprintf(` - resource "github_repository" "test" { - name = "%s" - description = "Terraform acceptance test - repository %s" - vulnerability_alerts = false - } - `, testRepoName, randomID) +func (d resourceDataLike) GetOk(key string) (any, bool) { + v, ok := d[key] + return v, ok +} - resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnauthenticated(t) }, - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository.test", "vulnerability_alerts", - "false", - ), - ), - }, - }, - }) - }) +func TestResourceGithubParseFullName(t *testing.T) { + repo, org, ok := resourceGithubParseFullName(resourceDataLike(map[string]any{"full_name": "myrepo/myorg"})) + assert.True(t, ok) + assert.Equal(t, "myrepo", repo) + assert.Equal(t, "myorg", org) + _, _, ok = resourceGithubParseFullName(resourceDataLike(map[string]any{})) + assert.False(t, ok) + _, _, ok = resourceGithubParseFullName(resourceDataLike(map[string]any{"full_name": "malformed"})) + assert.False(t, ok) +} - t.Run("can modify vulnerability alerts", func(t *testing.T) { - randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - testRepoName := fmt.Sprintf("%svulnerability-alerts-%s", testResourcePrefix, randomID) - config := fmt.Sprintf(` - resource "github_repository" "test" { - name = "%s" - description = "Terraform acceptance test - repository %s" - vulnerability_alerts = false - } - `, testRepoName, randomID) +func testCheckResourceAttrContains(resourceName, attributeName, substring string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Resource not found: %s", resourceName) + } - resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnauthenticated(t) }, - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository.test", "vulnerability_alerts", "false", - ), - ), - }, - { - Config: strings.Replace(config, "vulnerability_alerts = false", "vulnerability_alerts = true", 1), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository.test", "vulnerability_alerts", "true", - ), - ), - }, - }, - }) - }) + value, ok := rs.Primary.Attributes[attributeName] + if !ok { + return fmt.Errorf("Attribute not found: %s", attributeName) + } + + if !strings.Contains(value, substring) { + return fmt.Errorf("Attribute '%s' does not contain '%s'", value, substring) + } + + return nil + } +} + +func TestGithubRepositoryNameFailsValidationWhenOverMaxCharacters(t *testing.T) { + resource := resourceGithubRepository() + schema := resource.Schema["name"] + + diags := schema.ValidateDiagFunc(strings.Repeat("a", 101), cty.GetAttrPath("name")) + if len(diags) != 1 { + t.Error(fmt.Errorf("unexpected number of name validation failures; expected=1; actual=%d", len(diags))) + } + expectedFailure := "invalid value for name (must include only alphanumeric characters, underscores or hyphens and consist of 100 characters or less)" + actualFailure := diags[0].Summary + if expectedFailure != actualFailure { + t.Error(fmt.Errorf("unexpected name validation failure; expected=%s; action=%s", expectedFailure, actualFailure)) + } +} + +func TestGithubRepositoryNameFailsValidationWithSpace(t *testing.T) { + resource := resourceGithubRepository() + schema := resource.Schema["name"] + + diags := schema.ValidateDiagFunc("test space", cty.GetAttrPath("name")) + if len(diags) != 1 { + t.Error(fmt.Errorf("unexpected number of name validation failures; expected=1; actual=%d", len(diags))) + } + expectedFailure := "invalid value for name (must include only alphanumeric characters, underscores or hyphens and consist of 100 characters or less)" + actualFailure := diags[0].Summary + if expectedFailure != actualFailure { + t.Error(fmt.Errorf("unexpected name validation failure; expected=%s; action=%s", expectedFailure, actualFailure)) + } } diff --git a/website/docs/r/repository.html.markdown b/website/docs/r/repository.html.markdown index 92c67745de..628c8ffe8c 100644 --- a/website/docs/r/repository.html.markdown +++ b/website/docs/r/repository.html.markdown @@ -100,7 +100,7 @@ The following arguments are supported: * `allow_auto_merge` - (Optional) Set to `true` to allow auto-merging pull requests on the repository. -* `allow_forking` - (Optional) Set to `true` to allow private forking on the repository; this is only relevant if the repository is owned by an organization and is private or internal. +* `allow_forking` - (Optional) Configure private forking for organization owned private and internal repositories; set to `true` to enable, `false` to disable, and leave unset for the default behaviour. Configuring this requires that private forking is not being explicitly configured at the organization level. * `squash_merge_commit_title` - (Optional) Can be `PR_TITLE` or `COMMIT_OR_PR_TITLE` for a default squash merge commit title. Applicable only if `allow_squash_merge` is `true`. @@ -140,9 +140,9 @@ initial repository creation and create the target branch inside of the repositor * `template` - (Optional) Use a template repository to create this resource. See [Template Repositories](#template-repositories) below for details. -* `vulnerability_alerts` (Optional) - Set to `true` to enable security alerts for vulnerable dependencies. Enabling requires alerts to be enabled on the owner level. (Note for importing: GitHub enables the alerts on public repos but disables them on private repos by default.) See [GitHub Documentation](https://help.github.com/en/github/managing-security-vulnerabilities/about-security-alerts-for-vulnerable-dependencies) for details. Note that vulnerability alerts have not been successfully tested on any GitHub Enterprise instance and may be unavailable in those settings. +* `vulnerability_alerts` - (Optional) Configure [Dependabot security alerts](https://help.github.com/en/github/managing-security-vulnerabilities/about-security-alerts-for-vulnerable-dependencies) for vulnerable dependencies; set to `true` to enable, set to `false` to disable, and leave unset for the default behavior. Configuring this requires that alerts are not being explicitly configured at the organization level. -* `ignore_vulnerability_alerts_during_read` (Optional) - Set to `true` to not call the vulnerability alerts endpoint so the resource can also be used without admin permissions during read. +* `ignore_vulnerability_alerts_during_read` (Optional) - Set to `true` to not call the vulnerability alerts endpoint so the resource can also be used without admin permissions during read, if this is set to `true` and `vulnerability_alerts` is unset then the computed value of `vulnerability_alerts` will be nil. * `allow_update_branch` (Optional) - Set to `true` to always suggest updating pull request branches. @@ -245,6 +245,6 @@ The following additional attributes are exported: Repositories can be imported using the `name`, e.g. -```text -$ terraform import github_repository.terraform terraform +```shell +terraform import github_repository.terraform myrepo ```