diff --git a/GNUmakefile b/GNUmakefile index 0409d0b9ed..47580a4c2f 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -59,7 +59,7 @@ test: testacc: @branch=$$(git rev-parse --abbrev-ref HEAD); \ printf "==> Running acceptance tests on branch: \033[1m%s\033[0m...\n" "🌿 $$branch 🌿" - TF_ACC=1 CGO_ENABLED=0 go test $(TEST) -v -run '^TestAcc' $(RUNARGS) $(TESTARGS) -timeout 120m -count=1 + TF_ACC=1 CGO_ENABLED=0 go test $(TEST) -parallel=4 -v -run '^TestAcc' $(RUNARGS) $(TESTARGS) -timeout 120m -count=1 sweep: @echo "WARNING: This will destroy infrastructure. Use only in development accounts." diff --git a/github/data_source_github_repository.go b/github/data_source_github_repository.go index 8fd515c74b..87d6b0dd2f 100644 --- a/github/data_source_github_repository.go +++ b/github/data_source_github_repository.go @@ -341,6 +341,11 @@ func dataSourceGithubRepository() *schema.Resource { Type: schema.TypeBool, Computed: true, }, + "web_commit_signoff_required": { + Type: schema.TypeBool, + Computed: true, + Description: "Require contributors to sign off on web-based commits.", + }, }, } } @@ -414,6 +419,7 @@ func dataSourceGithubRepositoryRead(ctx context.Context, d *schema.ResourceData, _ = d.Set("has_projects", repo.GetHasProjects()) _ = d.Set("delete_branch_on_merge", repo.GetDeleteBranchOnMerge()) _ = d.Set("allow_update_branch", repo.GetAllowUpdateBranch()) + _ = d.Set("web_commit_signoff_required", repo.GetWebCommitSignoffRequired()) if repo.GetHasPages() { pages, _, err := client.Repositories.GetPagesInfo(ctx, owner, repoName) diff --git a/github/resource_github_repository.go b/github/resource_github_repository.go index f6408bffe6..db955085af 100644 --- a/github/resource_github_repository.go +++ b/github/resource_github_repository.go @@ -4,12 +4,12 @@ import ( "context" "errors" "fmt" - "log" "net/http" "regexp" "strings" "github.com/google/go-github/v84/github" + "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -509,12 +509,31 @@ func valueChangedButNotEmpty(ctx context.Context, oldVal, newVal, meta any) bool return oldValStr != "" && oldValStr != newValStr } -func customDiffFunction(_ context.Context, diff *schema.ResourceDiff, v any) error { +func customDiffFunction(ctx context.Context, diff *schema.ResourceDiff, v any) error { if diff.HasChange("name") { if err := diff.SetNewComputed("full_name"); err != nil { return err } } + + // We need to check the `allow_squash_merge` flag by checking if the `squash_merge_commit_title` and `squash_merge_commit_message` are set in the configuration. + isSquashMergeCommitTitleSet := !diff.GetRawConfig().GetAttr("squash_merge_commit_title").IsNull() + isSquashMergeCommitMessageSet := !diff.GetRawConfig().GetAttr("squash_merge_commit_message").IsNull() + if isSquashMergeCommitMessageSet && isSquashMergeCommitTitleSet { + if !diff.Get("allow_squash_merge").(bool) { + return fmt.Errorf("allow_squash_merge is required when squash_merge_commit_title and squash_merge_commit_message is set") + } + } + + // We need to check the `allow_merge_commit` flag by checking if the `merge_commit_title` and `merge_commit_message` are set in the configuration. + isMergeCommitTitleSet := !diff.GetRawConfig().GetAttr("merge_commit_title").IsNull() + isMergeCommitMessageSet := !diff.GetRawConfig().GetAttr("merge_commit_message").IsNull() + if isMergeCommitMessageSet && isMergeCommitTitleSet { + if !diff.Get("allow_merge_commit").(bool) { + return fmt.Errorf("allow_merge_commit is required when merge_commit_title and merge_commit_message is set") + } + } + return nil } @@ -649,17 +668,20 @@ func resourceGithubRepositoryObject(d *schema.ResourceData) *github.Repository { return repository } -func resourceGithubRepositoryCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - client := meta.(*Owner).v3client +func resourceGithubRepositoryCreate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { + meta := m.(*Owner) + client := meta.v3client if branchName, hasDefaultBranch := d.GetOk("default_branch"); hasDefaultBranch && (branchName != "main") { return diag.Errorf("cannot set the default branch on a new repository to something other than 'main'") } repoReq := resourceGithubRepositoryObject(d) - owner := meta.(*Owner).name + owner := meta.name repoName := repoReq.GetName() + var upstreamRepo *github.Repository + if template, ok := d.GetOk("template"); ok { templateConfigBlocks := template.([]any) @@ -693,14 +715,14 @@ func resourceGithubRepositoryCreate(ctx context.Context, d *schema.ResourceData, return diag.FromErr(err) } - d.SetId(repo.GetName()) + upstreamRepo = repo } } else if d.Get("fork").(string) == "true" { // Handle repository forking sourceOwner := d.Get("source_owner").(string) sourceRepo := d.Get("source_repo").(string) requestedName := d.Get("name").(string) - log.Printf("[INFO] Creating fork of %s/%s in %s", sourceOwner, sourceRepo, owner) + tflog.Info(ctx, "Creating fork", map[string]any{"source_owner": sourceOwner, "source_repo": sourceRepo, "owner": owner}) if sourceOwner == "" || sourceRepo == "" { return diag.Errorf("source_owner and source_repo must be provided when forking a repository") @@ -711,7 +733,7 @@ func resourceGithubRepositoryCreate(ctx context.Context, d *schema.ResourceData, Name: requestedName, } - if meta.(*Owner).IsOrganization { + if meta.IsOrganization { opts.Organization = owner } @@ -721,48 +743,41 @@ func resourceGithubRepositoryCreate(ctx context.Context, d *schema.ResourceData, // Handle accepted error (202) which means the fork is being created asynchronously acceptedError := &github.AcceptedError{} if errors.As(err, &acceptedError) { - log.Printf("[INFO] Fork is being created asynchronously") + tflog.Info(ctx, "Fork is being created asynchronously") // Despite the 202 status, the API should still return preliminary fork information if fork == nil { return diag.Errorf("fork information not available after accepted status") } - log.Printf("[DEBUG] Fork name: %s", fork.GetName()) + tflog.Debug(ctx, "Fork name", map[string]any{"name": fork.GetName()}) } else { return diag.Errorf("failed to create fork: %s", err.Error()) } } else if resp != nil { - log.Printf("[DEBUG] Fork response status: %d", resp.StatusCode) - } - - if fork == nil { - return diag.Errorf("fork creation failed - no repository returned") + tflog.Debug(ctx, "Fork response status", map[string]any{"status": resp.StatusCode}) } - log.Printf("[INFO] Fork created with name: %s", fork.GetName()) - d.SetId(fork.GetName()) - log.Printf("[DEBUG] Set resource ID to just the name: %s", d.Id()) + upstreamRepo = fork - _ = d.Set("name", fork.GetName()) - _ = d.Set("full_name", fork.GetFullName()) // Add the full name for reference - _ = d.Set("html_url", fork.GetHTMLURL()) - _ = d.Set("ssh_clone_url", fork.GetSSHURL()) - _ = d.Set("git_clone_url", fork.GetGitURL()) - _ = d.Set("http_clone_url", fork.GetCloneURL()) + tflog.Info(ctx, "Fork created", map[string]any{"name": fork.GetName()}) } else { // Create without a repository template - var repo *github.Repository + var err error - if meta.(*Owner).IsOrganization { - repo, _, err = client.Repositories.Create(ctx, owner, repoReq) + var repoOwner string + if meta.IsOrganization { + repoOwner = owner } else { - // Create repository within authenticated user's account - repo, _, err = client.Repositories.Create(ctx, "", repoReq) + // Note: This exists due to the `go-github` library, which chooses endpoints based on the owner being empty or not + tflog.Info(ctx, "Creating repository for authenticated user") + repoOwner = "" } + + upstreamRepo, _, err = client.Repositories.Create(ctx, repoOwner, repoReq) if err != nil { return diag.FromErr(err) } - d.SetId(repo.GetName()) } + d.SetId(upstreamRepo.GetName()) // TODO: To be able to support repos for different owners, this needs to be updated to something like `buildID(owner,repo.GetName()` topics := repoReq.Topics if len(topics) > 0 { @@ -780,7 +795,31 @@ func resourceGithubRepositoryCreate(ctx context.Context, d *schema.ResourceData, } } - return resourceGithubRepositoryUpdate(ctx, d, meta) + tflog.Info(ctx, "Patching repository to ensure all configurations are applied", map[string]any{"owner": owner, "name": repoName}) + _, _, err := client.Repositories.Edit(ctx, owner, repoName, repoReq) + 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 { + if err := updateVulnerabilityAlerts(ctx, client, owner, repoName, val); err != nil { + return diag.FromErr(err) + } + } + } + + // Fetch final repo state after all mutations and set Computed fields + finalRepo, _, err := client.Repositories.Get(ctx, owner, d.Id()) + if err != nil { + return diag.FromErr(err) + } + + if diags := setResourceFieldsFromRepo(ctx, d, client, owner, finalRepo); diags.HasError() { + return diags + } + + return nil } func resourceGithubRepositoryRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { @@ -795,7 +834,6 @@ func resourceGithubRepositoryRead(ctx context.Context, d *schema.ResourceData, m owner = explicitOwner } - ctx = context.WithValue(ctx, ctxId, d.Id()) if !d.IsNewResource() { ctx = context.WithValue(ctx, ctxEtag, d.Get("etag").(string)) } @@ -808,8 +846,7 @@ func resourceGithubRepositoryRead(ctx context.Context, d *schema.ResourceData, m return nil } if ghErr.Response.StatusCode == http.StatusNotFound { - log.Printf("[INFO] Removing repository %s/%s from state because it no longer exists in GitHub", - owner, repoName) + tflog.Info(ctx, "Removing repository from state because it no longer exists in GitHub", map[string]any{"owner": owner, "name": repoName}) d.SetId("") return nil } @@ -817,100 +854,12 @@ func resourceGithubRepositoryRead(ctx context.Context, d *schema.ResourceData, m return diag.FromErr(err) } - _ = d.Set("etag", resp.Header.Get("ETag")) - _ = d.Set("name", repoName) - _ = d.Set("description", repo.GetDescription()) - _ = d.Set("primary_language", repo.GetLanguage()) - _ = d.Set("homepage_url", repo.GetHomepage()) - _ = d.Set("private", repo.GetPrivate()) - _ = d.Set("visibility", repo.GetVisibility()) - _ = d.Set("has_issues", repo.GetHasIssues()) - _ = d.Set("has_discussions", repo.GetHasDiscussions()) - _ = d.Set("has_projects", repo.GetHasProjects()) - _ = d.Set("has_wiki", repo.GetHasWiki()) - _ = d.Set("is_template", repo.GetIsTemplate()) - _ = d.Set("full_name", repo.GetFullName()) - _ = d.Set("default_branch", repo.GetDefaultBranch()) - _ = d.Set("html_url", repo.GetHTMLURL()) - _ = d.Set("ssh_clone_url", repo.GetSSHURL()) - _ = d.Set("svn_url", repo.GetSVNURL()) - _ = d.Set("git_clone_url", repo.GetGitURL()) - _ = d.Set("http_clone_url", repo.GetCloneURL()) - _ = d.Set("archived", repo.GetArchived()) - _ = d.Set("topics", flattenStringList(repo.Topics)) - _ = d.Set("node_id", repo.GetNodeID()) - _ = d.Set("repo_id", repo.GetID()) - - // TODO: Validate this behavior as I can see these fields being returned even when archived - // GitHub API doesn't respond following parameters when repository is archived - if !d.Get("archived").(bool) { - _ = d.Set("allow_auto_merge", repo.GetAllowAutoMerge()) - _ = d.Set("allow_merge_commit", repo.GetAllowMergeCommit()) - _ = d.Set("allow_rebase_merge", repo.GetAllowRebaseMerge()) - _ = d.Set("allow_squash_merge", repo.GetAllowSquashMerge()) - _ = d.Set("allow_update_branch", repo.GetAllowUpdateBranch()) - _ = d.Set("allow_forking", repo.GetAllowForking()) - _ = d.Set("delete_branch_on_merge", repo.GetDeleteBranchOnMerge()) - _ = d.Set("web_commit_signoff_required", repo.GetWebCommitSignoffRequired()) - _ = d.Set("has_downloads", repo.GetHasDownloads()) - _ = d.Set("merge_commit_message", repo.GetMergeCommitMessage()) - _ = d.Set("merge_commit_title", repo.GetMergeCommitTitle()) - _ = d.Set("squash_merge_commit_message", repo.GetSquashMergeCommitMessage()) - _ = d.Set("squash_merge_commit_title", repo.GetSquashMergeCommitTitle()) - } - - if repo.GetHasPages() { - pages, _, err := client.Repositories.GetPagesInfo(ctx, owner, repoName) - if err != nil { - return diag.FromErr(err) - } - if err := d.Set("pages", flattenPages(pages)); err != nil { - return diag.Errorf("error setting pages: %s", err.Error()) - } + if err := d.Set("etag", resp.Header.Get("ETag")); err != nil { + return diag.FromErr(err) } - // Set fork information if this is a fork - if repo.GetFork() { - _ = d.Set("fork", "true") - - // If the repository has parent information, set the source details - if repo.Parent != nil { - _ = d.Set("source_owner", repo.Parent.GetOwner().GetLogin()) - _ = d.Set("source_repo", repo.Parent.GetName()) - } - } else { - _ = d.Set("fork", "false") - _ = d.Set("source_owner", "") - _ = d.Set("source_repo", "") - } - - if repo.TemplateRepository != nil { - if err = d.Set("template", []any{ - map[string]any{ - "owner": repo.TemplateRepository.Owner.Login, - "repository": repo.TemplateRepository.Name, - }, - }); err != nil { - return diag.FromErr(err) - } - } else { - if err = d.Set("template", []any{}); err != nil { - return diag.FromErr(err) - } - } - - if repo.GetSecurityAndAnalysis() != nil { - vulnerabilityAlerts, _, err := client.Repositories.GetVulnerabilityAlerts(ctx, owner, repoName) - if err != nil { - return diag.Errorf("error reading repository vulnerability alerts: %s", err.Error()) - } - if err = d.Set("vulnerability_alerts", vulnerabilityAlerts); err != nil { - return diag.FromErr(err) - } - - if err = d.Set("security_and_analysis", flattenSecurityAndAnalysis(repo.SecurityAndAnalysis)); err != nil { - return diag.FromErr(err) - } + if diags := setResourceFieldsFromRepo(ctx, d, client, owner, repo); diags.HasError() { + return diags } return nil @@ -920,7 +869,7 @@ func resourceGithubRepositoryUpdate(ctx context.Context, d *schema.ResourceData, // Can only update a repository if it is not archived or the update is to // archive the repository (unarchiving is not supported by the GitHub API) if d.Get("archived").(bool) && !d.HasChange("archived") { - log.Printf("[INFO] Skipping update of archived repository") + tflog.Info(ctx, "Skipping update of archived repository") return nil } @@ -940,7 +889,7 @@ func resourceGithubRepositoryUpdate(ctx context.Context, d *schema.ResourceData, if !d.HasChange("security_and_analysis") { repoReq.SecurityAndAnalysis = nil - log.Print("[DEBUG] No security_and_analysis update required. Removing this field from the payload.") + tflog.Debug(ctx, "No security_and_analysis update required, removing from payload") } // The documentation for `default_branch` states: "This can only be set @@ -953,7 +902,6 @@ func resourceGithubRepositoryUpdate(ctx context.Context, d *schema.ResourceData, repoName := d.Id() owner := meta.(*Owner).name - ctx = context.WithValue(ctx, ctxId, d.Id()) repo, _, err := client.Repositories.Edit(ctx, owner, repoName, repoReq) if err != nil { @@ -1009,41 +957,45 @@ func resourceGithubRepositoryUpdate(ctx context.Context, d *schema.ResourceData, repoReq.Visibility = new(visibility) repoReq.AllowForking = allowForking - log.Printf("[DEBUG] Updating repository visibility from %s to %s", repo.GetVisibility(), visibility) - _, resp, err := client.Repositories.Edit(ctx, owner, repoName, repoReq) + tflog.Debug(ctx, "Updating repository visibility", map[string]any{"from": repo.GetVisibility(), "to": visibility}) + updatedRepo, resp, err := client.Repositories.Edit(ctx, owner, repoName, repoReq) if err != nil { - if resp.StatusCode != 422 || !strings.Contains(err.Error(), fmt.Sprintf("Visibility is already %s", visibility)) { + if resp.StatusCode != http.StatusUnprocessableEntity || !strings.Contains(err.Error(), fmt.Sprintf("Visibility is already %s", visibility)) { return diag.FromErr(err) } } + repo = updatedRepo } - return resourceGithubRepositoryRead(ctx, d, meta) + if diags := setResourceFieldsFromRepo(ctx, d, client, owner, repo); diags.HasError() { + return diags + } + + return nil } func resourceGithubRepositoryDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*Owner).v3client repoName := d.Id() owner := meta.(*Owner).name - ctx = context.WithValue(ctx, ctxId, d.Id()) archiveOnDestroy := d.Get("archive_on_destroy").(bool) if archiveOnDestroy { if d.Get("archived").(bool) { - log.Printf("[DEBUG] Repository already archived, nothing to do on delete: %s/%s", owner, repoName) + tflog.Debug(ctx, "Repository already archived, nothing to do on delete", map[string]any{"owner": owner, "name": repoName}) return nil } else { if err := d.Set("archived", true); err != nil { return diag.FromErr(err) } repoReq := resourceGithubRepositoryObject(d) - log.Printf("[DEBUG] Archiving repository on delete: %s/%s", owner, repoName) + tflog.Debug(ctx, "Archiving repository on delete", map[string]any{"owner": owner, "name": repoName}) _, _, err := client.Repositories.Edit(ctx, owner, repoName, repoReq) return diag.FromErr(err) } } - log.Printf("[DEBUG] Deleting repository: %s/%s", owner, repoName) + tflog.Debug(ctx, "Deleting repository", map[string]any{"owner": owner, "name": repoName}) _, err := client.Repositories.Delete(ctx, owner, repoName) return diag.FromErr(err) } @@ -1055,6 +1007,117 @@ func resourceGithubRepositoryImport(ctx context.Context, d *schema.ResourceData, return []*schema.ResourceData{d}, nil } +// setResourceFieldsFromRepo populates all Computed schema fields +// from a *github.Repository API response. +// For fields requiring separate API calls (pages, vulnerability alerts), targeted calls are made here. +func setResourceFieldsFromRepo(ctx context.Context, d *schema.ResourceData, client *github.Client, owner string, repo *github.Repository) diag.Diagnostics { + repoName := repo.GetName() + + if err := errors.Join( + d.Set("name", repoName), + d.Set("description", repo.GetDescription()), + d.Set("primary_language", repo.GetLanguage()), + d.Set("homepage_url", repo.GetHomepage()), + d.Set("private", repo.GetPrivate()), + d.Set("visibility", repo.GetVisibility()), + d.Set("has_issues", repo.GetHasIssues()), + d.Set("has_discussions", repo.GetHasDiscussions()), + d.Set("has_projects", repo.GetHasProjects()), + d.Set("has_wiki", repo.GetHasWiki()), + d.Set("is_template", repo.GetIsTemplate()), + d.Set("full_name", repo.GetFullName()), + d.Set("default_branch", repo.GetDefaultBranch()), + d.Set("html_url", repo.GetHTMLURL()), + d.Set("ssh_clone_url", repo.GetSSHURL()), + d.Set("svn_url", repo.GetSVNURL()), + d.Set("git_clone_url", repo.GetGitURL()), + d.Set("http_clone_url", repo.GetCloneURL()), + d.Set("archived", repo.GetArchived()), + d.Set("topics", flattenStringList(repo.Topics)), + d.Set("node_id", repo.GetNodeID()), + d.Set("repo_id", repo.GetID()), + d.Set("allow_auto_merge", repo.GetAllowAutoMerge()), + d.Set("allow_merge_commit", repo.GetAllowMergeCommit()), + d.Set("allow_rebase_merge", repo.GetAllowRebaseMerge()), + d.Set("allow_squash_merge", repo.GetAllowSquashMerge()), + d.Set("allow_update_branch", repo.GetAllowUpdateBranch()), + d.Set("allow_forking", repo.GetAllowForking()), + d.Set("delete_branch_on_merge", repo.GetDeleteBranchOnMerge()), + d.Set("web_commit_signoff_required", repo.GetWebCommitSignoffRequired()), + d.Set("has_downloads", repo.GetHasDownloads()), + d.Set("merge_commit_message", repo.GetMergeCommitMessage()), + d.Set("merge_commit_title", repo.GetMergeCommitTitle()), + d.Set("squash_merge_commit_message", repo.GetSquashMergeCommitMessage()), + d.Set("squash_merge_commit_title", repo.GetSquashMergeCommitTitle()), + ); err != nil { + return diag.FromErr(err) + } + + // Fork info + if repo.GetFork() { + if err := d.Set("fork", "true"); err != nil { + return diag.FromErr(err) + } + if repo.Parent != nil { + if err := errors.Join( + d.Set("source_owner", repo.Parent.GetOwner().GetLogin()), + d.Set("source_repo", repo.Parent.GetName()), + ); err != nil { + return diag.FromErr(err) + } + } + } else { + if err := errors.Join( + d.Set("fork", "false"), + d.Set("source_owner", ""), + d.Set("source_repo", ""), + ); err != nil { + return diag.FromErr(err) + } + } + + // Template info + if repo.TemplateRepository != nil { + if err := d.Set("template", []any{ + map[string]any{ + "owner": repo.TemplateRepository.Owner.Login, + "repository": repo.TemplateRepository.Name, + }, + }); err != nil { + return diag.FromErr(err) + } + } else { + if err := d.Set("template", []any{}); err != nil { + return diag.FromErr(err) + } + } + + if repo.GetHasPages() { + pages, _, err := client.Repositories.GetPagesInfo(ctx, owner, repoName) + if err != nil { + return diag.FromErr(err) + } + if err := d.Set("pages", flattenPages(pages)); err != nil { + return diag.FromErr(err) + } + } + + if repo.GetSecurityAndAnalysis() != nil { + vulnerabilityAlerts, _, err := client.Repositories.GetVulnerabilityAlerts(ctx, owner, repoName) + if err != nil { + return diag.Errorf("error reading repository vulnerability alerts: %s", err.Error()) + } + if err := errors.Join( + d.Set("vulnerability_alerts", vulnerabilityAlerts), + d.Set("security_and_analysis", flattenSecurityAndAnalysis(repo.SecurityAndAnalysis)), + ); err != nil { + return diag.FromErr(err) + } + } + + return nil +} + func expandPages(input []any) *github.Pages { if len(input) == 0 || input[0] == nil { return nil diff --git a/github/resource_github_repository_test.go b/github/resource_github_repository_test.go index cc6df3c64d..40bd2aab6d 100644 --- a/github/resource_github_repository_test.go +++ b/github/resource_github_repository_test.go @@ -2,15 +2,19 @@ package github import ( "fmt" + "regexp" "strings" "testing" "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/compare" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" ) func TestAccGithubRepository(t *testing.T) { @@ -38,36 +42,19 @@ func TestAccGithubRepository(t *testing.T) { } `, testRepoName, testAccConf.testRepositoryVisibility) - check := resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository.test", "has_issues", - "true", - ), - resource.TestCheckResourceAttr( - "github_repository.test", "has_discussions", - "true", - ), - resource.TestCheckResourceAttr( - "github_repository.test", "allow_auto_merge", - "true", - ), - resource.TestCheckResourceAttr( - "github_repository.test", "merge_commit_title", - "MERGE_MESSAGE", - ), - resource.TestCheckResourceAttr( - "github_repository.test", "web_commit_signoff_required", - "true", - ), - ) - - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, - Check: check, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("has_issues"), knownvalue.Bool(true)), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("has_discussions"), knownvalue.Bool(true)), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("allow_auto_merge"), knownvalue.Bool(true)), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("merge_commit_title"), knownvalue.StringExact("MERGE_MESSAGE")), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("web_commit_signoff_required"), knownvalue.Bool(true)), + }, }, }, }) @@ -78,52 +65,34 @@ func TestAccGithubRepository(t *testing.T) { oldName := fmt.Sprintf(`%srename-%s`, testResourcePrefix, randomID) newName := fmt.Sprintf(`%s-renamed`, oldName) - config := fmt.Sprintf(` + config := ` resource "github_repository" "test" { name = "%[1]s" description = "Terraform acceptance tests %[2]s" visibility = "%s" } - `, oldName, randomID, testAccConf.testRepositoryVisibility) - - checks := map[string]resource.TestCheckFunc{ - "before": resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository.test", "name", - oldName, - ), - resource.ComposeTestCheckFunc( - testCheckResourceAttrContains("github_repository.test", "full_name", - oldName), - ), - ), - "after": resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository.test", "name", - newName, - ), - resource.ComposeTestCheckFunc( - testCheckResourceAttrContains("github_repository.test", "full_name", - newName), - ), - ), - } + ` - resource.Test(t, resource.TestCase{ + nameDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { - Config: config, - Check: checks["before"], + Config: fmt.Sprintf(config, oldName, randomID, testAccConf.testRepositoryVisibility), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("name"), knownvalue.StringExact(oldName)), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("full_name"), knownvalue.StringRegexp(regexp.MustCompile(regexp.QuoteMeta(oldName)))), + nameDiffer.AddStateValue("github_repository.test", tfjsonpath.New("name")), + }, }, { - // Rename the repo to something else - Config: strings.Replace( - config, - oldName, - newName, 1), - Check: checks["after"], + Config: fmt.Sprintf(config, newName, randomID, testAccConf.testRepositoryVisibility), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("name"), knownvalue.StringExact(newName)), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("full_name"), knownvalue.StringRegexp(regexp.MustCompile(regexp.QuoteMeta(newName)))), + nameDiffer.AddStateValue("github_repository.test", tfjsonpath.New("name")), + }, }, }, }) @@ -141,23 +110,21 @@ func TestAccGithubRepository(t *testing.T) { } `, testRepoName, testAccConf.testRepositoryVisibility) - check := resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrSet("github_repository.test", "name"), - ) - - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, - Check: check, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("name"), knownvalue.NotNull()), + }, }, { ResourceName: "github_repository.test", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"auto_init", "vulnerability_alerts", "ignore_vulnerability_alerts_during_read"}, + ImportStateVerifyIgnore: []string{"auto_init", "vulnerability_alerts", "ignore_vulnerability_alerts_during_read", "etag"}, }, }, }) @@ -175,21 +142,21 @@ resource "github_repository" "test" { } ` - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: fmt.Sprintf(config, testRepoName, "false", testAccConf.testRepositoryVisibility), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "archived", "false"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("archived"), knownvalue.Bool(false)), + }, }, { Config: fmt.Sprintf(config, testRepoName, "true", testAccConf.testRepositoryVisibility), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "archived", "true"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("archived"), knownvalue.Bool(true)), + }, }, }, }) @@ -207,34 +174,23 @@ resource "github_repository" "test" { } `, testRepoName, testAccConf.testRepositoryVisibility) - checks := map[string]resource.TestCheckFunc{ - "before": resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository.test", "has_projects", - "false", - ), - ), - "after": resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository.test", "has_projects", - "true", - ), - ), - } - - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, - Check: checks["before"], + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("has_projects"), knownvalue.Bool(false)), + }, }, { Config: strings.Replace(config, `has_projects = false`, `has_projects = true`, 1), - Check: checks["after"], + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("has_projects"), knownvalue.Bool(true)), + }, }, }, }) @@ -243,11 +199,11 @@ resource "github_repository" "test" { t.Run("manages the default branch feature for a repository", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) testRepoName := fmt.Sprintf("%sbranch-%s", testResourcePrefix, randomID) - config := fmt.Sprintf(` + config := ` resource "github_repository" "test" { name = "%s" description = "Terraform acceptance tests %[1]s" - default_branch = "main" + default_branch = "%s" auto_init = true visibility = "%s" } @@ -256,48 +212,36 @@ resource "github_repository" "test" { repository = github_repository.test.name branch = "default" } - `, testRepoName, testAccConf.testRepositoryVisibility) - - checks := map[string]resource.TestCheckFunc{ - "before": resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository.test", "default_branch", - "main", - ), - ), - "after": resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository.test", "default_branch", - "default", - ), - ), - } + ` - resource.Test(t, resource.TestCase{ + defaultBranchChangeCheck := statecheck.CompareValue(compare.ValuesDiffer()) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { - Config: config, - Check: checks["before"], + Config: fmt.Sprintf(config, testRepoName, "main", testAccConf.testRepositoryVisibility), + ConfigStateChecks: []statecheck.StateCheck{ + defaultBranchChangeCheck.AddStateValue("github_repository.test", tfjsonpath.New("default_branch")), + }, }, - // Test changing default_branch { - Config: strings.Replace(config, - `default_branch = "main"`, - `default_branch = "default"`, 1), - Check: checks["after"], + Config: fmt.Sprintf(config, testRepoName, "default", testAccConf.testRepositoryVisibility), + ConfigStateChecks: []statecheck.StateCheck{ + defaultBranchChangeCheck.AddStateValue("github_repository.test", tfjsonpath.New("default_branch")), + }, }, - // Test changing default_branch back to main again { - Config: config, - Check: checks["before"], + Config: fmt.Sprintf(config, testRepoName, "main", testAccConf.testRepositoryVisibility), + ConfigStateChecks: []statecheck.StateCheck{ + defaultBranchChangeCheck.AddStateValue("github_repository.test", tfjsonpath.New("default_branch")), + }, }, }, }) }) - t.Run("allows setting default_branch on an empty repository", func(t *testing.T) { + t.Run("updates_default_branchon_an_empty_repository_without_error", func(t *testing.T) { // Although default_branch is deprecated, for backwards compatibility // we allow setting it to "main". @@ -312,29 +256,25 @@ resource "github_repository" "test" { } `, testRepoName, testAccConf.testRepositoryVisibility) - check := resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository.test", "default_branch", - "main", - ), - ) - - resource.Test(t, resource.TestCase{ + defaultBranchChangeCheck := statecheck.CompareValue(compare.ValuesSame()) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ - // Test creation with default_branch set { Config: config, - Check: check, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("default_branch"), knownvalue.StringExact("main")), + defaultBranchChangeCheck.AddStateValue("github_repository.test", tfjsonpath.New("default_branch")), + }, }, - // Test that changing another property does not try to set - // default_branch (which would crash). { Config: strings.Replace(config, `acceptance tests`, `acceptance test`, 1), - Check: check, + ConfigStateChecks: []statecheck.StateCheck{ + defaultBranchChangeCheck.AddStateValue("github_repository.test", tfjsonpath.New("default_branch")), + }, }, }, }) @@ -353,24 +293,16 @@ resource "github_repository" "test" { } `, testRepoName, testAccConf.testRepositoryVisibility) - check := resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository.test", "license_template", - "ms-pl", - ), - resource.TestCheckResourceAttr( - "github_repository.test", "gitignore_template", - "C++", - ), - ) - - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, - Check: check, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("license_template"), knownvalue.StringExact("ms-pl")), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("gitignore_template"), knownvalue.StringExact("C++")), + }, }, }, }) @@ -390,21 +322,21 @@ resource "github_repository" "test" { } ` - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: fmt.Sprintf(config, testRepoName, topicsBefore, testAccConf.testRepositoryVisibility), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "topics.#", "2"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("topics"), knownvalue.SetSizeExact(2)), + }, }, { Config: fmt.Sprintf(config, testRepoName, topicsAfter, testAccConf.testRepositoryVisibility), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "topics.#", "3"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("topics"), knownvalue.SetSizeExact(3)), + }, }, }, }) @@ -426,15 +358,15 @@ resource "github_repository" "test" { } `, testRepoName, testAccConf.testRepositoryVisibility, testAccConf.testPublicTemplateRepositoryOwner, testAccConf.testPublicTemplateRepository) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t); skipIfEMUEnterprise(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "is_template", "false"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("is_template"), knownvalue.Bool(false)), + }, }, }, }) @@ -456,15 +388,15 @@ resource "github_repository" "test" { } `, testRepoName, testAccConf.testRepositoryVisibility, testAccConf.owner, testAccConf.testOrgTemplateRepository) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnlessHasOrgs(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "is_template", "false"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("is_template"), knownvalue.Bool(false)), + }, }, }, }) @@ -484,21 +416,21 @@ resource "github_repository" "test" { } ` - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: fmt.Sprintf(config, testRepoName, "false", testAccConf.testRepositoryVisibility), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "archived", "false"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("archived"), knownvalue.Bool(false)), + }, }, { Config: fmt.Sprintf(config, testRepoName, "true", testAccConf.testRepositoryVisibility), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "archived", "true"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("archived"), knownvalue.Bool(true)), + }, }, }, }) @@ -517,16 +449,16 @@ resource "github_repository" "test" { } `, repoName) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(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"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("visibility"), knownvalue.StringExact("private")), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("allow_forking"), knownvalue.Bool(true)), + }, }, }, }) @@ -545,16 +477,16 @@ resource "github_repository" "test" { } `, repoName) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(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"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("visibility"), knownvalue.StringExact("private")), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("allow_forking"), knownvalue.Bool(false)), + }, }, }, }) @@ -571,16 +503,16 @@ resource "github_repository" "test" { } `, repoName) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(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"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("visibility"), knownvalue.StringExact("private")), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("allow_forking"), knownvalue.NotNull()), + }, }, }, }) @@ -612,7 +544,7 @@ resource "github_repository" "test" { } `, testRepoName) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) skipIfEMUEnterprise(t) @@ -621,24 +553,24 @@ resource "github_repository" "test" { Steps: []resource.TestStep{ { Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "visibility", "public"), - resource.TestCheckResourceAttr("github_repository.test", "allow_forking", "true"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("visibility"), knownvalue.StringExact("public")), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("allow_forking"), knownvalue.Bool(true)), + }, }, { Config: configPrivate, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "visibility", "private"), - resource.TestCheckResourceAttr("github_repository.test", "allow_forking", "false"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("visibility"), knownvalue.StringExact("private")), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("allow_forking"), knownvalue.Bool(false)), + }, }, { Config: configPrivateForking, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "visibility", "private"), - resource.TestCheckResourceAttr("github_repository.test", "allow_forking", "true"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("visibility"), knownvalue.StringExact("private")), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("allow_forking"), knownvalue.Bool(true)), + }, }, }, }) @@ -657,15 +589,15 @@ resource "github_repository" "test" { } `, repoName, testAccConf.testRepositoryVisibility) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "vulnerability_alerts", "true"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("vulnerability_alerts"), knownvalue.Bool(true)), + }, }, }, }) @@ -684,15 +616,15 @@ resource "github_repository" "test" { } `, repoName, testAccConf.testRepositoryVisibility) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "vulnerability_alerts", "false"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("vulnerability_alerts"), knownvalue.Bool(false)), + }, }, }, }) @@ -709,15 +641,14 @@ resource "github_repository" "test" { } `, repoName, testAccConf.testRepositoryVisibility) - resource.Test(t, resource.TestCase{ + // NOTE: terraform-plugin-testing does not support asserting the nonexistence of a value + // (no equivalent to the legacy TestCheckNoResourceAttr). We only verify creation succeeds. + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckNoResourceAttr("github_repository.test", "vulnerability_alerts"), // This will error if it's run as an unprivileged user but that shouldn't ever happen - ), }, }, }) @@ -736,21 +667,21 @@ resource "github_repository" "test" { } ` - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: fmt.Sprintf(config, repoName, testAccConf.testRepositoryVisibility, false), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "vulnerability_alerts", "false"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("vulnerability_alerts"), knownvalue.Bool(false)), + }, }, { Config: fmt.Sprintf(config, repoName, testAccConf.testRepositoryVisibility, true), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "vulnerability_alerts", "true"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("vulnerability_alerts"), knownvalue.Bool(true)), + }, }, }, }) @@ -774,28 +705,92 @@ resource "github_repository" "test" { } ` - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: fmt.Sprintf(config, testRepoName, mergeCommitTitle, mergeCommitMessage, testAccConf.testRepositoryVisibility), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "merge_commit_title", mergeCommitTitle), - resource.TestCheckResourceAttr("github_repository.test", "merge_commit_message", mergeCommitMessage), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("merge_commit_title"), knownvalue.StringExact(mergeCommitTitle)), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("merge_commit_message"), knownvalue.StringExact(mergeCommitMessage)), + }, }, { Config: fmt.Sprintf(config, testRepoName, updatedMergeCommitTitle, updatedMergeCommitMessage, testAccConf.testRepositoryVisibility), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "merge_commit_title", updatedMergeCommitTitle), - resource.TestCheckResourceAttr("github_repository.test", "merge_commit_message", updatedMergeCommitMessage), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("merge_commit_title"), knownvalue.StringExact(updatedMergeCommitTitle)), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("merge_commit_message"), knownvalue.StringExact(updatedMergeCommitMessage)), + }, }, }, }) }) + t.Run("validate_required_fields_for_squash_merge_commit_strategy", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + testRepoName := fmt.Sprintf("%smodify-sq-str-%s", testResourcePrefix, randomID) + + config := ` + resource "github_repository" "test" { + name = "%s" + squash_merge_commit_title = "PR_TITLE" + squash_merge_commit_message = "PR_BODY" + visibility = "%s" + %s + } +` + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(config, testRepoName, testAccConf.testRepositoryVisibility, "allow_squash_merge = false"), + ExpectError: regexp.MustCompile("allow_squash_merge is required.*"), + }, + { + Config: fmt.Sprintf(config, testRepoName, testAccConf.testRepositoryVisibility, ""), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("allow_squash_merge"), knownvalue.Bool(true)), + }, + }, + }, + }, + ) + }) + + t.Run("validate_required_fields_for_merge_commit_strategy", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + testRepoName := fmt.Sprintf("%smodify-sq-str-%s", testResourcePrefix, randomID) + + config := ` + resource "github_repository" "test" { + name = "%s" + merge_commit_title = "PR_TITLE" + merge_commit_message = "PR_BODY" + visibility = "%s" + %s + } +` + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(config, testRepoName, testAccConf.testRepositoryVisibility, "allow_merge_commit = false"), + ExpectError: regexp.MustCompile("allow_merge_commit is required.*"), + }, + { + Config: fmt.Sprintf(config, testRepoName, testAccConf.testRepositoryVisibility, ""), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("allow_merge_commit"), knownvalue.Bool(true)), + }, + }, + }, + }, + ) + }) + t.Run("create and modify squash merge commit strategy without error", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) testRepoName := fmt.Sprintf("%smodify-sq-str-%s", testResourcePrefix, randomID) @@ -815,68 +810,61 @@ resource "github_repository" "test" { } ` - checks := map[string]resource.TestCheckFunc{ - "before": resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "squash_merge_commit_title", squashMergeCommitTitle), - resource.TestCheckResourceAttr("github_repository.test", "squash_merge_commit_message", squashMergeCommitMessage), - ), - "after": resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "squash_merge_commit_title", updatedSquashMergeCommitTitle), - resource.TestCheckResourceAttr("github_repository.test", "squash_merge_commit_message", updatedSquashMergeCommitMessage), - ), - } - - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: fmt.Sprintf(config, testRepoName, squashMergeCommitTitle, squashMergeCommitMessage, testAccConf.testRepositoryVisibility), - Check: checks["before"], + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("squash_merge_commit_title"), knownvalue.StringExact(squashMergeCommitTitle)), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("squash_merge_commit_message"), knownvalue.StringExact(squashMergeCommitMessage)), + }, }, { Config: fmt.Sprintf(config, testRepoNameAfter, updatedSquashMergeCommitTitle, updatedSquashMergeCommitMessage, testAccConf.testRepositoryVisibility), - Check: checks["after"], + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("squash_merge_commit_title"), knownvalue.StringExact(updatedSquashMergeCommitTitle)), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("squash_merge_commit_message"), knownvalue.StringExact(updatedSquashMergeCommitMessage)), + }, }, }, }) }) - // t.Run("create a repository with go as primary_language", func(t *testing.T) { - // randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - // testResourceName := fmt.Sprintf("%srepo-%s", testResourcePrefix, randomID) - // config := fmt.Sprintf(` - // resource "github_repository" "test" { - // name = "%s" - // auto_init = true - // } - // resource "github_repository_file" "test" { - // repository = github_repository.test.name - // file = "test.go" - // content = "package main" - // } - // `, testResourceName) - - // check := resource.ComposeTestCheckFunc( - // resource.TestCheckResourceAttr("github_repository.test", "primary_language", "Go"), - // ) - - // resource.Test(t, resource.TestCase{ - // PreCheck: func() { skipUnauthenticated(t) }, - // ProviderFactories: providerFactories, - // Steps: []resource.TestStep{ - // { - // // Not doing any checks since the file needs to be created before the language can be updated - // Config: config, - // }, - // { - // // Re-running the terraform will refresh the language since the go-file has been created - // Config: config, - // Check: check, - // }, - // }, - // }) - // }) + t.Run("create a repository with go as primary_language", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + testResourceName := fmt.Sprintf("%srepo-%s", testResourcePrefix, randomID) + config := fmt.Sprintf(` + resource "github_repository" "test" { + name = "%s" + auto_init = true + } + resource "github_repository_file" "test" { + repository = github_repository.test.name + file = "test.go" + content = "package main" + } + `, testResourceName) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + // Not doing any checks since the file needs to be created before the language can be updated + Config: config, + }, + { + // Re-running the terraform will refresh the language since the go-file has been created + Config: config, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("primary_language"), knownvalue.StringExact("Go")), + }, + }, + }, + }) + }) t.Run("manages the legacy pages feature for a repository", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) @@ -896,16 +884,20 @@ resource "github_repository" "test" { } `, testRepoName, testAccConf.testRepositoryVisibility) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "pages.0.source.0.branch", "main"), - resource.TestCheckResourceAttr("github_repository.test", "pages.0.source.0.path", "/"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", + tfjsonpath.New("pages").AtSliceIndex(0).AtMapKey("source").AtSliceIndex(0).AtMapKey("branch"), + knownvalue.StringExact("main")), + statecheck.ExpectKnownValue("github_repository.test", + tfjsonpath.New("pages").AtSliceIndex(0).AtMapKey("source").AtSliceIndex(0).AtMapKey("path"), + knownvalue.StringExact("/")), + }, }, }, }) @@ -925,15 +917,19 @@ resource "github_repository" "test" { } `, testRepoName, testAccConf.testRepositoryVisibility) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckNoResourceAttr("github_repository.test", "pages.0.source.#"), - ), + // NOTE: terraform-plugin-testing does not support asserting the nonexistence of a value; + // TypeList nil is represented as empty slice in JSON state + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", + tfjsonpath.New("pages").AtSliceIndex(0).AtMapKey("source"), + knownvalue.ListSizeExact(0)), + }, }, }, }) @@ -974,20 +970,21 @@ resource "github_repository" "test" { } `, testRepoName) - resource.Test(t, resource.TestCase{ + securityPath := tfjsonpath.New("security_and_analysis").AtSliceIndex(0) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "security_and_analysis.0.advanced_security.0.status", "enabled"), - resource.TestCheckResourceAttr("github_repository.test", "security_and_analysis.0.code_security.0.status", "enabled"), - resource.TestCheckResourceAttr("github_repository.test", "security_and_analysis.0.secret_scanning.0.status", "enabled"), - resource.TestCheckResourceAttr("github_repository.test", "security_and_analysis.0.secret_scanning_push_protection.0.status", "enabled"), - resource.TestCheckResourceAttr("github_repository.test", "security_and_analysis.0.secret_scanning_ai_detection.0.status", "enabled"), - resource.TestCheckResourceAttr("github_repository.test", "security_and_analysis.0.secret_scanning_non_provider_patterns.0.status", "enabled"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", securityPath.AtMapKey("advanced_security").AtSliceIndex(0).AtMapKey("status"), knownvalue.StringExact("enabled")), + statecheck.ExpectKnownValue("github_repository.test", securityPath.AtMapKey("code_security").AtSliceIndex(0).AtMapKey("status"), knownvalue.StringExact("enabled")), + statecheck.ExpectKnownValue("github_repository.test", securityPath.AtMapKey("secret_scanning").AtSliceIndex(0).AtMapKey("status"), knownvalue.StringExact("enabled")), + statecheck.ExpectKnownValue("github_repository.test", securityPath.AtMapKey("secret_scanning_push_protection").AtSliceIndex(0).AtMapKey("status"), knownvalue.StringExact("enabled")), + statecheck.ExpectKnownValue("github_repository.test", securityPath.AtMapKey("secret_scanning_ai_detection").AtSliceIndex(0).AtMapKey("status"), knownvalue.StringExact("enabled")), + statecheck.ExpectKnownValue("github_repository.test", securityPath.AtMapKey("secret_scanning_non_provider_patterns").AtSliceIndex(0).AtMapKey("status"), knownvalue.StringExact("enabled")), + }, }, }, }) @@ -1013,7 +1010,8 @@ resource "github_repository" "test" { } `, testRepoName) - resource.Test(t, resource.TestCase{ + securityPath := tfjsonpath.New("security_and_analysis").AtSliceIndex(0) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) skipIfEMUEnterprise(t) @@ -1022,10 +1020,10 @@ resource "github_repository" "test" { Steps: []resource.TestStep{ { Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "security_and_analysis.0.secret_scanning.0.status", "enabled"), - resource.TestCheckResourceAttr("github_repository.test", "security_and_analysis.0.secret_scanning_push_protection.0.status", "disabled"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", securityPath.AtMapKey("secret_scanning").AtSliceIndex(0).AtMapKey("status"), knownvalue.StringExact("enabled")), + statecheck.ExpectKnownValue("github_repository.test", securityPath.AtMapKey("secret_scanning_push_protection").AtSliceIndex(0).AtMapKey("status"), knownvalue.StringExact("disabled")), + }, }, }, }) @@ -1041,15 +1039,15 @@ resource "github_repository" "test" { } ` - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: fmt.Sprintf(config, testRepoName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.private", "visibility", "private"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.private", tfjsonpath.New("visibility"), knownvalue.StringExact("private")), + }, }, }, }) @@ -1065,15 +1063,15 @@ resource "github_repository" "test" { } `, testRepoName) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnlessMode(t, enterprise) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "visibility", "internal"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("visibility"), knownvalue.StringExact("internal")), + }, }, }, }) @@ -1090,21 +1088,21 @@ resource "github_repository" "test" { } ` - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t); skipIfEMUEnterprise(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: fmt.Sprintf(config, testRepoName, "public"), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.public", "visibility", "public"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.public", tfjsonpath.New("visibility"), knownvalue.StringExact("public")), + }, }, { Config: fmt.Sprintf(config, testRepoName, "private"), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.public", "visibility", "private"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.public", tfjsonpath.New("visibility"), knownvalue.StringExact("private")), + }, }, }, }) @@ -1120,37 +1118,24 @@ resource "github_repository" "test" { } `, testRepoName) - checks := map[string]resource.TestCheckFunc{ - "before": resource.ComposeTestCheckFunc( - resource.TestCheckNoResourceAttr( - "github_repository.test", "vulnerability_alerts", - ), - ), - "after": resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository.test", "vulnerability_alerts", - "true", - ), - resource.TestCheckResourceAttr( - "github_repository.test", "visibility", - "private", - ), - ), - } - - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, - Check: checks["before"], + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("vulnerability_alerts"), knownvalue.Null()), + }, }, { Config: strings.Replace(config, `}`, "vulnerability_alerts = true\n}", 1), - Check: checks["after"], + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("vulnerability_alerts"), knownvalue.Bool(true)), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("visibility"), knownvalue.StringExact("private")), + }, }, }, }) @@ -1166,29 +1151,24 @@ resource "github_repository" "test" { } `, testRepoName) - checks := map[string]resource.TestCheckFunc{ - "before": resource.ComposeTestCheckFunc( - resource.TestCheckNoResourceAttr("github_repository.test", "vulnerability_alerts"), - ), - "after": resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "vulnerability_alerts", "true"), - resource.TestCheckResourceAttr("github_repository.test", "visibility", "private"), - ), - } - - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnlessMode(t, enterprise) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, - Check: checks["before"], + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("vulnerability_alerts"), knownvalue.Null()), + }, }, { Config: strings.Replace(config, `}`, "vulnerability_alerts = true\n}", 1), - Check: checks["after"], + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("vulnerability_alerts"), knownvalue.Bool(true)), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("visibility"), knownvalue.StringExact("private")), + }, }, }, }) @@ -1208,24 +1188,16 @@ resource "github_repository" "test" { } `, testRepoName, testAccConf.testPublicTemplateRepositoryOwner, testAccConf.testPublicTemplateRepository) - check := resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository.private", "visibility", - "private", - ), - resource.TestCheckResourceAttr( - "github_repository.private", "private", - "true", - ), - ) - - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t); skipIfEMUEnterprise(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, - Check: check, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.private", tfjsonpath.New("visibility"), knownvalue.StringExact("private")), + statecheck.ExpectKnownValue("github_repository.private", tfjsonpath.New("private"), knownvalue.Bool(true)), + }, }, }, }) @@ -1245,16 +1217,16 @@ resource "github_repository" "test" { } `, testRepoName, testAccConf.testPublicTemplateRepositoryOwner, testAccConf.testPublicTemplateRepository) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnlessMode(t, enterprise); skipIfEMUEnterprise(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "visibility", "internal"), - resource.TestCheckResourceAttr("github_repository.test", "private", "true"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("visibility"), knownvalue.StringExact("internal")), + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("private"), knownvalue.Bool(true)), + }, }, }, }) @@ -1271,15 +1243,15 @@ resource "github_repository" "test" { } ` - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: fmt.Sprintf(config, testRepoName, "true"), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "web_commit_signoff_required", "true"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("web_commit_signoff_required"), knownvalue.Bool(true)), + }, }, }, }) @@ -1296,15 +1268,15 @@ resource "github_repository" "test" { } ` - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: fmt.Sprintf(config, testRepoName, "false"), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "web_commit_signoff_required", "false"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("web_commit_signoff_required"), knownvalue.Bool(false)), + }, }, }, }) @@ -1320,15 +1292,15 @@ resource "github_repository" "test" { } ` - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: fmt.Sprintf(config, testRepoName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "web_commit_signoff_required", "false"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("web_commit_signoff_required"), knownvalue.Bool(false)), + }, }, }, }) @@ -1348,15 +1320,15 @@ resource "github_repository" "test" { } ` - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: fmt.Sprintf(config, testRepoName, "foo"), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.test", "web_commit_signoff_required", "true"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.test", tfjsonpath.New("web_commit_signoff_required"), knownvalue.Bool(true)), + }, }, { Config: fmt.Sprintf(config, testRepoName, "bar"), @@ -1379,15 +1351,15 @@ resource "github_repository" "private" { } ` - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: fmt.Sprintf(config, testRepoName, "foo"), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.private", "allow_forking", "false"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.private", tfjsonpath.New("allow_forking"), knownvalue.Bool(false)), + }, }, { Config: fmt.Sprintf(config, testRepoName, "bar"), @@ -1410,15 +1382,15 @@ resource "github_repository" "private" { } ` - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: fmt.Sprintf(config, testRepoName, "foo"), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_repository.private", "vulnerability_alerts", "true"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.private", tfjsonpath.New("vulnerability_alerts"), knownvalue.Bool(true)), + }, }, { Config: fmt.Sprintf(config, testRepoName, "bar"), @@ -1484,32 +1456,19 @@ func Test_expandPages(t *testing.T) { } `, testRepoName) - check := resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository.forked", "fork", - "true", - ), - resource.TestCheckResourceAttrSet( - "github_repository.forked", "html_url", - ), - resource.TestCheckResourceAttrSet( - "github_repository.forked", "ssh_clone_url", - ), - resource.TestCheckResourceAttrSet( - "github_repository.forked", "git_clone_url", - ), - resource.TestCheckResourceAttrSet( - "github_repository.forked", "http_clone_url", - ), - ) - - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: config, - Check: check, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.forked", tfjsonpath.New("fork"), knownvalue.StringExact("true")), + statecheck.ExpectKnownValue("github_repository.forked", tfjsonpath.New("html_url"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("github_repository.forked", tfjsonpath.New("ssh_clone_url"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("github_repository.forked", tfjsonpath.New("git_clone_url"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("github_repository.forked", tfjsonpath.New("http_clone_url"), knownvalue.NotNull()), + }, }, }, }) @@ -1542,48 +1501,25 @@ func Test_expandPages(t *testing.T) { } `, testRepoName) - checks := map[string]resource.TestCheckFunc{ - "before": resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository.forked_update", "description", - "Initial description for forked repo", - ), - resource.TestCheckResourceAttr( - "github_repository.forked_update", "has_wiki", - "true", - ), - resource.TestCheckResourceAttr( - "github_repository.forked_update", "has_issues", - "false", - ), - ), - "after": resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository.forked_update", "description", - "Updated description for forked repo", - ), - resource.TestCheckResourceAttr( - "github_repository.forked_update", "has_wiki", - "false", - ), - resource.TestCheckResourceAttr( - "github_repository.forked_update", "has_issues", - "true", - ), - ), - } - - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { Config: initialConfig, - Check: checks["before"], + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.forked_update", tfjsonpath.New("description"), knownvalue.StringExact("Initial description for forked repo")), + statecheck.ExpectKnownValue("github_repository.forked_update", tfjsonpath.New("has_wiki"), knownvalue.Bool(true)), + statecheck.ExpectKnownValue("github_repository.forked_update", tfjsonpath.New("has_issues"), knownvalue.Bool(false)), + }, }, { Config: updatedConfig, - Check: checks["after"], + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository.forked_update", tfjsonpath.New("description"), knownvalue.StringExact("Updated description for forked repo")), + statecheck.ExpectKnownValue("github_repository.forked_update", tfjsonpath.New("has_wiki"), knownvalue.Bool(false)), + statecheck.ExpectKnownValue("github_repository.forked_update", tfjsonpath.New("has_issues"), knownvalue.Bool(true)), + }, }, { ResourceName: "github_repository.forked_update", @@ -1659,26 +1595,6 @@ func TestResourceGithubParseFullName(t *testing.T) { }) } -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"] diff --git a/website/docs/r/repository.html.markdown b/website/docs/r/repository.html.markdown index 157fd01caa..89a1f1ce7d 100644 --- a/website/docs/r/repository.html.markdown +++ b/website/docs/r/repository.html.markdown @@ -36,7 +36,7 @@ resource "github_repository" "example" { name = "example" description = "My awesome web page" - private = false + visibility = public pages { source { @@ -63,156 +63,156 @@ resource "github_repository" "forked_repo" { The following arguments are supported: -* `name` - (Required) The name of the repository. +- `name` - (Required) The name of the repository. -* `description` - (Optional) A description of the repository. +- `description` - (Optional) A description of the repository. -* `homepage_url` - (Optional) URL of a page describing the project. +- `homepage_url` - (Optional) URL of a page describing the project. -* `fork` - (Optional) Set to `true` to create a fork of an existing repository. When set to `true`, both `source_owner` and `source_repo` must also be specified. +- `fork` - (Optional) Set to `true` to create a fork of an existing repository. When set to `true`, both `source_owner` and `source_repo` must also be specified. -* `source_owner` - (Optional) The GitHub username or organization that owns the repository being forked. Required when `fork` is `true`. +- `source_owner` - (Optional) The GitHub username or organization that owns the repository being forked. Required when `fork` is `true`. -* `source_repo` - (Optional) The name of the repository to fork. Required when `fork` is `true`. +- `source_repo` - (Optional) The name of the repository to fork. Required when `fork` is `true`. -* `private` - (Optional) Set to `true` to create a private repository. - Repositories are created as public (e.g. open source) by default. +- `private` - (**DEPRECATED**) (Optional) Set to `true` to create a private repository. + Repositories are created as public (e.g. open source) by default. Use `visibility` instead. -* `visibility` - (Optional) Can be `public` or `private`. If your organization is associated with an enterprise account using GitHub Enterprise Cloud or GitHub Enterprise Server 2.20+, visibility can also be `internal`. The `visibility` parameter overrides the `private` parameter. +- `visibility` - (Optional) Can be `public` or `private`. If your organization is associated with an enterprise account using GitHub Enterprise Cloud or GitHub Enterprise Server 2.20+, visibility can also be `internal`. The `visibility` parameter overrides the `private` parameter. -* `has_issues` - (Optional) Set to `true` to enable the GitHub Issues features +- `has_issues` - (Optional) Set to `true` to enable the GitHub Issues features on the repository. -* `has_discussions` - (Optional) Set to `true` to enable GitHub Discussions on the repository. Defaults to `false`. +- `has_discussions` - (Optional) Set to `true` to enable GitHub Discussions on the repository. Defaults to `false`. -* `has_projects` - (Optional) Set to `true` to enable the GitHub Projects features on the repository. Per the GitHub [documentation](https://developer.github.com/v3/repos/#create) when in an organization that has disabled repository projects it will default to `false` and will otherwise default to `true`. If you specify `true` when it has been disabled it will return an error. +- `has_projects` - (Optional) Set to `true` to enable the GitHub Projects features on the repository. Per the GitHub [documentation](https://developer.github.com/v3/repos/#create) when in an organization that has disabled repository projects it will default to `false` and will otherwise default to `true`. If you specify `true` when it has been disabled it will return an error. -* `has_wiki` - (Optional) Set to `true` to enable the GitHub Wiki features on +- `has_wiki` - (Optional) Set to `true` to enable the GitHub Wiki features on the repository. -* `is_template` - (Optional) Set to `true` to tell GitHub that this is a template repository. +- `is_template` - (Optional) Set to `true` to tell GitHub that this is a template repository. -* `allow_merge_commit` - (Optional) Set to `false` to disable merge commits on the repository. +- `allow_merge_commit` - (Optional) Set to `false` to disable merge commits on the repository. -* `allow_squash_merge` - (Optional) Set to `false` to disable squash merges on the repository. +- `allow_squash_merge` - (Optional) Set to `false` to disable squash merges on the repository. -* `allow_rebase_merge` - (Optional) Set to `false` to disable rebase merges on the repository. +- `allow_rebase_merge` - (Optional) Set to `false` to disable rebase merges on the repository. -* `allow_auto_merge` - (Optional) Set to `true` to allow auto-merging pull requests on the repository. +- `allow_auto_merge` - (Optional) Set to `true` to allow auto-merging pull requests on the repository. -* `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. +- `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`. +- `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`. -* `squash_merge_commit_message` - (Optional) Can be `PR_BODY`, `COMMIT_MESSAGES`, or `BLANK` for a default squash merge commit message. Applicable only if `allow_squash_merge` is `true`. +- `squash_merge_commit_message` - (Optional) Can be `PR_BODY`, `COMMIT_MESSAGES`, or `BLANK` for a default squash merge commit message. Applicable only if `allow_squash_merge` is `true`. -* `merge_commit_title` - Can be `PR_TITLE` or `MERGE_MESSAGE` for a default merge commit title. Applicable only if `allow_merge_commit` is `true`. +- `merge_commit_title` - (Optional) Can be `PR_TITLE` or `MERGE_MESSAGE` for a default merge commit title. Applicable only if `allow_merge_commit` is `true`. -* `merge_commit_message` - Can be `PR_BODY`, `PR_TITLE`, or `BLANK` for a default merge commit message. Applicable only if `allow_merge_commit` is `true`. +- `merge_commit_message` - (Optional) Can be `PR_BODY`, `PR_TITLE`, or `BLANK` for a default merge commit message. Applicable only if `allow_merge_commit` is `true`. -* `delete_branch_on_merge` - (Optional) Automatically delete head branch after a pull request is merged. Defaults to `false`. +- `delete_branch_on_merge` - (Optional) Automatically delete head branch after a pull request is merged. Defaults to `false`. -* `web_commit_signoff_required` - (Optional) Require contributors to sign off on web-based commits. See more [here](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/managing-the-commit-signoff-policy-for-your-repository). +- `web_commit_signoff_required` - (Optional) Require contributors to sign off on web-based commits. See more in the [GitHub documentation about managing the commit signoff policy for your repository](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/managing-the-commit-signoff-policy-for-your-repository). -* `has_downloads` - (**DEPRECATED**) (Optional) Set to `true` to enable the (deprecated) downloads features on the repository. This attribute is no longer in use, but it hasn't been removed yet. It will be removed in a future version. See [this discussion](https://github.com/orgs/community/discussions/102145#discussioncomment-8351756). +- `has_downloads` - (**DEPRECATED**) (Optional) Set to `true` to enable the (deprecated) downloads features on the repository. This attribute is no longer in use, but it hasn't been removed yet. It will be removed in a future version. See [this discussion](https://github.com/orgs/community/discussions/102145#discussioncomment-8351756). -* `auto_init` - (Optional) Set to `true` to produce an initial commit in the repository. +- `auto_init` - (Optional) Set to `true` to produce an initial commit in the repository. -* `gitignore_template` - (Optional) Use the [name of the template](https://github.com/github/gitignore) without the extension. For example, "Haskell". +- `gitignore_template` - (Optional) Use the [name of the template](https://github.com/github/gitignore) without the extension. For example, "Haskell". -* `license_template` - (Optional) Use the [name of the template](https://github.com/github/choosealicense.com/tree/gh-pages/_licenses) without the extension. For example, "mit" or "mpl-2.0". +- `license_template` - (Optional) Use the [name of the template](https://github.com/github/choosealicense.com/tree/gh-pages/_licenses) without the extension. For example, "mit" or "mpl-2.0". -* `default_branch` - (Optional) (Deprecated: Use `github_branch_default` resource instead) The name of the default branch of the repository. **NOTE:** This can only be set after a repository has already been created, +- `default_branch` - (Optional) (Deprecated: Use `github_branch_default` resource instead) The name of the default branch of the repository. **NOTE:** This can only be set after a repository has already been created, and after a correct reference has been created for the target branch inside the repository. This means a user will have to omit this parameter from the initial repository creation and create the target branch inside of the repository prior to setting this attribute. -* `archived` - (Optional) Specifies if the repository should be archived. Defaults to `false`. **NOTE** Currently, the API does not support unarchiving. +- `archived` - (Optional) Specifies if the repository should be archived. Defaults to `false`. **NOTE** Currently, the API does not support unarchiving. -* `archive_on_destroy` - (Optional) Set to `true` to archive the repository instead of deleting on destroy. +- `archive_on_destroy` - (Optional) Set to `true` to archive the repository instead of deleting on destroy. -* `pages` - (Optional) The repository's GitHub Pages configuration. See [GitHub Pages Configuration](#github-pages-configuration) below for details. +- `pages` - (Optional) The repository's GitHub Pages configuration. See [GitHub Pages Configuration](#github-pages-configuration) below for details. -* `security_and_analysis` - (Optional) The repository's [security and analysis](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-security-and-analysis-settings-for-your-repository) configuration. See [Security and Analysis Configuration](#security-and-analysis-configuration) below for details. +- `security_and_analysis` - (Optional) The repository's [security and analysis](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-security-and-analysis-settings-for-your-repository) configuration. See [Security and Analysis Configuration](#security-and-analysis-configuration) below for details. -* `topics` - (Optional) The list of topics of the repository. +- `topics` - (Optional) The list of topics of the repository. ~> Note: This attribute is not compatible with the `github_repository_topics` resource. Use one of them. `github_repository_topics` is only meant to be used if the repository itself is not handled via terraform, for example if it's only read as a datasource (see [issue #1845](https://github.com/integrations/terraform-provider-github/issues/1845)). -* `template` - (Optional) Use a template repository to create this resource. See [Template Repositories](#template-repositories) below for details. +- `template` - (Optional) Use a template repository to create this resource. See [Template Repositories](#template-repositories) below for details. -* `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. +- `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` (**DEPRECATED**) (Optional) - This is ignored as the provider now handles lack of permissions automatically. +- `ignore_vulnerability_alerts_during_read` (**DEPRECATED**) (Optional) - This is ignored as the provider now handles lack of permissions automatically. -* `allow_update_branch` (Optional) - Set to `true` to always suggest updating pull request branches. +- `allow_update_branch` (Optional) - Set to `true` to always suggest updating pull request branches. ### GitHub Pages Configuration The `pages` block supports the following: -* `source` - (Optional) The source branch and directory for the rendered Pages site. See [GitHub Pages Source](#github-pages-source) below for details. +- `source` - (Optional) The source branch and directory for the rendered Pages site. See [GitHub Pages Source](#github-pages-source) below for details. -* `build_type` - (Optional) The type of GitHub Pages site to build. Can be `legacy` or `workflow`. If you use `legacy` as build type you need to set the option `source`. +- `build_type` - (Optional) The type of GitHub Pages site to build. Can be `legacy` or `workflow`. If you use `legacy` as build type you need to set the option `source`. -* `cname` - (Optional) The custom domain for the repository. This can only be set after the repository has been created. +- `cname` - (Optional) The custom domain for the repository. This can only be set after the repository has been created. #### GitHub Pages Source The `source` block supports the following: -* `branch` - (Required) The repository branch used to publish the site's source files. (i.e. `main` or `gh-pages`. +- `branch` - (Required) The repository branch used to publish the site's source files. (i.e. `main` or `gh-pages`. -* `path` - (Optional) The repository directory from which the site publishes (Default: `/`). +- `path` - (Optional) The repository directory from which the site publishes (Default: `/`). ### Security and Analysis Configuration The `security_and_analysis` block supports the following: -* `advanced_security` - (Optional) The advanced security configuration for the repository. See [Advanced Security Configuration](#advanced-security-configuration) below for details. If a repository's visibility is `public`, advanced security is always enabled and cannot be changed, so this setting cannot be supplied. +- `advanced_security` - (Optional) The advanced security configuration for the repository. See [Advanced Security Configuration](#advanced-security-configuration) below for details. If a repository's visibility is `public`, advanced security is always enabled and cannot be changed, so this setting cannot be supplied. -* `code_security` - (Optional) The code security configuration for the repository. See [Code Security](#code-security-configuration) below for details. +- `code_security` - (Optional) The code security configuration for the repository. See [Code Security](#code-security-configuration) below for details. -* `secret_scanning` - (Optional) The secret scanning configuration for the repository. See [Secret Scanning Configuration](#secret-scanning-configuration) below for details. +- `secret_scanning` - (Optional) The secret scanning configuration for the repository. See [Secret Scanning Configuration](#secret-scanning-configuration) below for details. -* `secret_scanning_push_protection` - (Optional) The secret scanning push protection configuration for the repository. See [Secret Scanning Push Protection Configuration](#secret-scanning-push-protection-configuration) below for details. +- `secret_scanning_push_protection` - (Optional) The secret scanning push protection configuration for the repository. See [Secret Scanning Push Protection Configuration](#secret-scanning-push-protection-configuration) below for details. -* `secret_scanning_ai_detection` - (Optional) The secret scanning ai detection configuration for the repository. See [Secret Scanning AI Detection Configuration](#secret-scanning-ai-detection-configuration) below for details. +- `secret_scanning_ai_detection` - (Optional) The secret scanning ai detection configuration for the repository. See [Secret Scanning AI Detection Configuration](#secret-scanning-ai-detection) below for details. -* `secret_scanning_non_provider_patterns` - (Optional) The secret scanning non-provider patterns configuration for this repository. See [Secret Scanning Non-Provider Patterns Configuration](#secret-scanning-non-provider-patterns-configuration) below for more details. +- `secret_scanning_non_provider_patterns` - (Optional) The secret scanning non-provider patterns configuration for this repository. See [Secret Scanning Non-Provider Patterns Configuration](#secret-scanning-non-provider-patterns) below for more details. #### Advanced Security Configuration The `advanced_security` block supports the following: -* `status` - (Required) Set to `enabled` to enable advanced security features on the repository. Can be `enabled` or `disabled`. +- `status` - (Required) Set to `enabled` to enable advanced security features on the repository. Can be `enabled` or `disabled`. #### Code Security Configuration -* `status` - (Required) Set to `enabled` to enable GitHub Code Security on the repository. Can be `enabled` or `disabled`. If set to `enabled`, the repository's visibility must be `public`, `security_and_analysis[0].advanced_security[0].status` must also be set to `enabled`, or your Organization must have split licensing for Advanced security. +- `status` - (Required) Set to `enabled` to enable GitHub Code Security on the repository. Can be `enabled` or `disabled`. If set to `enabled`, the repository's visibility must be `public`, `security_and_analysis[0].advanced_security[0].status` must also be set to `enabled`, or your Organization must have split licensing for Advanced security. #### Secret Scanning Configuration -* `status` - (Required) Set to `enabled` to enable secret scanning on the repository. Can be `enabled` or `disabled`. If set to `enabled`, the repository's visibility must be `public`, `security_and_analysis[0].advanced_security[0].status` must also be set to `enabled`, or your Organization must have split licensing for Advanced security. +- `status` - (Required) Set to `enabled` to enable secret scanning on the repository. Can be `enabled` or `disabled`. If set to `enabled`, the repository's visibility must be `public`, `security_and_analysis[0].advanced_security[0].status` must also be set to `enabled`, or your Organization must have split licensing for Advanced security. #### Secret Scanning Push Protection Configuration -* `status` - (Required) Set to `enabled` to enable secret scanning push protection on the repository. Can be `enabled` or `disabled`. If set to `enabled`, the repository's visibility must be `public`, `security_and_analysis[0].advanced_security[0].status` must also be set to `enabled`, or your Organization must have split licensing for Advanced security. +- `status` - (Required) Set to `enabled` to enable secret scanning push protection on the repository. Can be `enabled` or `disabled`. If set to `enabled`, the repository's visibility must be `public`, `security_and_analysis[0].advanced_security[0].status` must also be set to `enabled`, or your Organization must have split licensing for Advanced security. #### Secret Scanning AI Detection -* `status` - (Required) Set to `enabled` to enable secret scanning AI detection on the repository. Can be `enabled` or `disabled`. If set to `enabled`, the repository's visibility must be `public`, `security_and_analysis[0].advanced_security[0].status` must also be set to `enabled`, or your Organization must have split licensing for Advanced security. +- `status` - (Required) Set to `enabled` to enable secret scanning AI detection on the repository. Can be `enabled` or `disabled`. If set to `enabled`, the repository's visibility must be `public`, `security_and_analysis[0].advanced_security[0].status` must also be set to `enabled`, or your Organization must have split licensing for Advanced security. #### Secret Scanning Non-Provider Patterns -* `status` - (Required) Set to `enabled` to enable secret scanning non-provider patterns on the repository. Can be `enabled` or `disabled`. If set to `enabled`, the repository's visibility must be `public`, `security_and_analysis[0].advanced_security[0].status` must also be set to `enabled`, or your Organization must have split licensing for Advanced security. +- `status` - (Required) Set to `enabled` to enable secret scanning non-provider patterns on the repository. Can be `enabled` or `disabled`. If set to `enabled`, the repository's visibility must be `public`, `security_and_analysis[0].advanced_security[0].status` must also be set to `enabled`, or your Organization must have split licensing for Advanced security. ### Template Repositories `template` supports the following arguments: -* `owner`: The GitHub organization or user the template repository is owned by. -* `repository`: The name of the template repository. -* `include_all_branches`: Whether the new repository should include all the branches from the template repository (defaults to false, which includes only the default branch from the template). +- `owner`: The GitHub organization or user the template repository is owned by. +- `repository`: The name of the template repository. +- `include_all_branches`: Whether the new repository should include all the branches from the template repository (defaults to false, which includes only the default branch from the template). ~> **Note on `internal` visibility with templates**: When creating a repository from a template with `visibility = "internal"`, the provider uses a two-step process due to GitHub API limitations. The template creation API only supports a `private` boolean parameter. Therefore, repositories with `visibility = "internal"` are initially created as private and then immediately updated to internal visibility. This ensures internal repositories are never exposed publicly during creation. @@ -220,28 +220,33 @@ The `advanced_security` block supports the following: The following additional attributes are exported: -* `full_name` - A string of the form "orgname/reponame". +- `full_name` - A string of the form "orgname/reponame". -* `html_url` - URL to the repository on the web. +- `html_url` - URL to the repository on the web. -* `ssh_clone_url` - URL that can be provided to `git clone` to clone the repository via SSH. +- `ssh_clone_url` - URL that can be provided to `git clone` to clone the repository via SSH. -* `http_clone_url` - URL that can be provided to `git clone` to clone the repository via HTTPS. +- `http_clone_url` - URL that can be provided to `git clone` to clone the repository via HTTPS. -* `git_clone_url` - URL that can be provided to `git clone` to clone the repository anonymously via the git protocol. +- `git_clone_url` - URL that can be provided to `git clone` to clone the repository anonymously via the git protocol. -* `svn_url` - URL that can be provided to `svn checkout` to check out the repository via GitHub's Subversion protocol emulation. +- `svn_url` - URL that can be provided to `svn checkout` to check out the repository via GitHub's Subversion protocol emulation. -* `node_id` - GraphQL global node id for use with v4 API +- `node_id` - GraphQL global node id for use with v4 API -* `repo_id` - GitHub ID for the repository +- `repo_id` - GitHub ID for the repository -* `primary_language` - The primary language used in the repository. +- `primary_language` - The primary language used in the repository. -* `pages` - The block consisting of the repository's GitHub Pages configuration with the following additional attributes: -* `custom_404` - Whether the rendered GitHub Pages site has a custom 404 page. -* `html_url` - The absolute URL (including scheme) of the rendered GitHub Pages site e.g. `https://username.github.io`. -* `status` - The GitHub Pages site's build status e.g. `building` or `built`. +- `pages` - The block consisting of the repository's GitHub Pages configuration with the following additional attributes: + +- `custom_404` - Whether the rendered GitHub Pages site has a custom 404 page. + +- `html_url` - The absolute URL (including scheme) of the rendered GitHub Pages site e.g. `https://username.github.io`. + +- `status` - The GitHub Pages site's build status e.g. `building` or `built`. + +- `url` - The absolute URL (including scheme) of the rendered GitHub Pages site e.g. `https://username.github.io`. ## Import