From f26ff064a2d0c154a959521d45281182f9bcafec Mon Sep 17 00:00:00 2001 From: oda251 <69433701+oda251@users.noreply.github.com> Date: Wed, 1 Apr 2026 22:54:46 +0900 Subject: [PATCH 1/2] feat: add github_repository_code_scanning_default_setup resource Add a new resource to manage code scanning default setup configuration for GitHub repositories via the REST API. Closes #2043 (partially) Co-Authored-By: Claude Opus 4.6 (1M context) --- github/provider.go | 3 +- ..._repository_code_scanning_default_setup.go | 210 ++++++++++++++++ ...sitory_code_scanning_default_setup_test.go | 237 ++++++++++++++++++ ..._code_scanning_default_setup.html.markdown | 68 +++++ 4 files changed, 517 insertions(+), 1 deletion(-) create mode 100644 github/resource_github_repository_code_scanning_default_setup.go create mode 100644 github/resource_github_repository_code_scanning_default_setup_test.go create mode 100644 website/docs/r/repository_code_scanning_default_setup.html.markdown diff --git a/github/provider.go b/github/provider.go index 2d5d6dc33a..c51a040b4d 100644 --- a/github/provider.go +++ b/github/provider.go @@ -188,8 +188,9 @@ func Provider() *schema.Provider { "github_release": resourceGithubRelease(), "github_repository": resourceGithubRepository(), "github_repository_autolink_reference": resourceGithubRepositoryAutolinkReference(), - "github_repository_dependabot_security_updates": resourceGithubRepositoryDependabotSecurityUpdates(), + "github_repository_code_scanning_default_setup": resourceGithubRepositoryCodeScanningDefaultSetup(), "github_repository_collaborator": resourceGithubRepositoryCollaborator(), + "github_repository_dependabot_security_updates": resourceGithubRepositoryDependabotSecurityUpdates(), "github_repository_collaborators": resourceGithubRepositoryCollaborators(), "github_repository_custom_property": resourceGithubRepositoryCustomProperty(), "github_repository_deploy_key": resourceGithubRepositoryDeployKey(), diff --git a/github/resource_github_repository_code_scanning_default_setup.go b/github/resource_github_repository_code_scanning_default_setup.go new file mode 100644 index 0000000000..b3a8b6a76f --- /dev/null +++ b/github/resource_github_repository_code_scanning_default_setup.go @@ -0,0 +1,210 @@ +package github + +import ( + "context" + "errors" + "fmt" + "log" + "time" + + "github.com/google/go-github/v84/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceGithubRepositoryCodeScanningDefaultSetup() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceGithubRepositoryCodeScanningDefaultSetupCreateOrUpdate, + ReadContext: resourceGithubRepositoryCodeScanningDefaultSetupRead, + UpdateContext: resourceGithubRepositoryCodeScanningDefaultSetupCreateOrUpdate, + DeleteContext: resourceGithubRepositoryCodeScanningDefaultSetupDelete, + Importer: &schema.ResourceImporter{ + StateContext: resourceGithubRepositoryCodeScanningDefaultSetupImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(5 * time.Minute), + Delete: schema.DefaultTimeout(5 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "repository": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The GitHub repository name.", + }, + "state": { + Type: schema.TypeString, + Required: true, + Description: "The desired state of code scanning default setup. Must be `configured` or `not-configured`.", + ValidateDiagFunc: validation.ToDiagFunc( + validation.StringInSlice([]string{"configured", "not-configured"}, false), + ), + }, + "query_suite": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The query suite to use. Must be `default` or `extended`.", + ValidateDiagFunc: validation.ToDiagFunc( + validation.StringInSlice([]string{"default", "extended"}, false), + ), + }, + "languages": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Description: "The languages to enable for code scanning. Supported values include `actions`, `c-cpp`, `csharp`, `go`, `java-kotlin`, `javascript-typescript`, `python`, `ruby`, `swift`.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + } +} + +func resourceGithubRepositoryCodeScanningDefaultSetupCreateOrUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*Owner).v3client + owner := meta.(*Owner).name + repoName := d.Get("repository").(string) + state := d.Get("state").(string) + + options := &github.UpdateDefaultSetupConfigurationOptions{ + State: state, + } + + if v, ok := d.GetOk("query_suite"); ok { + qs := v.(string) + options.QuerySuite = &qs + } + + if v, ok := d.GetOk("languages"); ok { + options.Languages = expandStringList(v.(*schema.Set).List()) + } + + _, _, err := client.CodeScanning.UpdateDefaultSetupConfiguration(ctx, owner, repoName, options) + if err != nil { + // 202 Accepted is expected — go-github surfaces it as AcceptedError + var acceptedErr *github.AcceptedError + if !errors.As(err, &acceptedErr) { + return diag.Errorf("error updating code scanning default setup for %s/%s: %s", owner, repoName, err) + } + } + + d.SetId(repoName) + + var timeout time.Duration + if d.IsNewResource() { + timeout = d.Timeout(schema.TimeoutCreate) + } else { + timeout = d.Timeout(schema.TimeoutUpdate) + } + + config, err := waitForCodeScanningState(ctx, client, owner, repoName, state, timeout) + if err != nil { + return diag.Errorf("error waiting for code scanning default setup state for %s/%s: %s", owner, repoName, err) + } + + return setCodeScanningDefaultSetupState(d, config) +} + +func resourceGithubRepositoryCodeScanningDefaultSetupRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*Owner).v3client + owner := meta.(*Owner).name + repoName := d.Get("repository").(string) + + if repoName == "" { + repoName = d.Id() + } + + config, _, err := client.CodeScanning.GetDefaultSetupConfiguration(ctx, owner, repoName) + if err != nil { + return diag.Errorf("error reading code scanning default setup for %s/%s: %s", owner, repoName, err) + } + + return setCodeScanningDefaultSetupState(d, config) +} + +func resourceGithubRepositoryCodeScanningDefaultSetupDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*Owner).v3client + owner := meta.(*Owner).name + repoName := d.Get("repository").(string) + + options := &github.UpdateDefaultSetupConfigurationOptions{ + State: "not-configured", + } + + _, _, err := client.CodeScanning.UpdateDefaultSetupConfiguration(ctx, owner, repoName, options) + if err != nil { + var acceptedErr *github.AcceptedError + if !errors.As(err, &acceptedErr) { + return diag.Errorf("error disabling code scanning default setup for %s/%s: %s", owner, repoName, err) + } + } + + log.Printf("[INFO] Code scanning default setup disabled for %s/%s", owner, repoName) + return nil +} + +func resourceGithubRepositoryCodeScanningDefaultSetupImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + repoName := d.Id() + if repoName == "" { + return nil, fmt.Errorf("repository name must not be empty") + } + + if err := d.Set("repository", repoName); err != nil { + return nil, err + } + + diags := resourceGithubRepositoryCodeScanningDefaultSetupRead(ctx, d, meta) + if diags.HasError() { + return nil, fmt.Errorf("error importing code scanning default setup for %s: %s", repoName, diags[0].Summary) + } + + return []*schema.ResourceData{d}, nil +} + +func setCodeScanningDefaultSetupState(d *schema.ResourceData, config *github.DefaultSetupConfiguration) diag.Diagnostics { + if err := d.Set("state", config.GetState()); err != nil { + return diag.Errorf("error setting state: %s", err) + } + if err := d.Set("query_suite", config.GetQuerySuite()); err != nil { + return diag.Errorf("error setting query_suite: %s", err) + } + if err := d.Set("languages", config.Languages); err != nil { + return diag.Errorf("error setting languages: %s", err) + } + return nil +} + +func waitForCodeScanningState(ctx context.Context, client *github.Client, owner, repo, targetState string, timeout time.Duration) (*github.DefaultSetupConfiguration, error) { + conf := &retry.StateChangeConf{ + Pending: []string{"pending"}, + Target: []string{targetState}, + Timeout: timeout, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + Refresh: func() (any, string, error) { + config, _, err := client.CodeScanning.GetDefaultSetupConfiguration(ctx, owner, repo) + if err != nil { + return nil, "", err + } + state := config.GetState() + if state == targetState { + return config, state, nil + } + return config, "pending", nil + }, + } + + result, err := conf.WaitForStateContext(ctx) + if err != nil { + return nil, err + } + + return result.(*github.DefaultSetupConfiguration), nil +} diff --git a/github/resource_github_repository_code_scanning_default_setup_test.go b/github/resource_github_repository_code_scanning_default_setup_test.go new file mode 100644 index 0000000000..9dfa99fa6e --- /dev/null +++ b/github/resource_github_repository_code_scanning_default_setup_test.go @@ -0,0 +1,237 @@ +package github + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func testAccCodeScanningDefaultSetupConfig(repoName, extraAttrs string) string { + return fmt.Sprintf(` + resource "github_repository" "test" { + name = "%s" + visibility = "public" + auto_init = true + } + + resource "github_repository_code_scanning_default_setup" "test" { + repository = github_repository.test.name + %s + } + `, repoName, extraAttrs) +} + +func TestAccGithubRepositoryCodeScanningDefaultSetup(t *testing.T) { + t.Run("configures and unconfigures code scanning default setup", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%srepo-code-scanning-%s", testResourcePrefix, randomID) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testAccCodeScanningDefaultSetupConfig(repoName, `state = "configured"`), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_repository_code_scanning_default_setup.test", "state", "configured"), + resource.TestCheckResourceAttrSet( + "github_repository_code_scanning_default_setup.test", "query_suite"), + ), + }, + { + Config: testAccCodeScanningDefaultSetupConfig(repoName, `state = "not-configured"`), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_repository_code_scanning_default_setup.test", "state", "not-configured"), + ), + }, + }, + }) + }) + + t.Run("configures with extended query suite", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%srepo-code-scanning-%s", testResourcePrefix, randomID) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testAccCodeScanningDefaultSetupConfig(repoName, ` + state = "configured" + query_suite = "extended" + `), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_repository_code_scanning_default_setup.test", "state", "configured"), + resource.TestCheckResourceAttr( + "github_repository_code_scanning_default_setup.test", "query_suite", "extended"), + ), + }, + }, + }) + }) + + t.Run("configures with explicit languages", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%srepo-code-scanning-%s", testResourcePrefix, randomID) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testAccCodeScanningDefaultSetupConfig(repoName, ` + state = "configured" + languages = ["javascript-typescript", "python"] + `), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_repository_code_scanning_default_setup.test", "state", "configured"), + resource.TestCheckResourceAttr( + "github_repository_code_scanning_default_setup.test", "languages.#", "2"), + ), + }, + }, + }) + }) + + t.Run("updates query suite without changing state", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%srepo-code-scanning-%s", testResourcePrefix, randomID) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testAccCodeScanningDefaultSetupConfig(repoName, ` + state = "configured" + query_suite = "default" + `), + Check: resource.TestCheckResourceAttr( + "github_repository_code_scanning_default_setup.test", "query_suite", "default"), + }, + { + Config: testAccCodeScanningDefaultSetupConfig(repoName, ` + state = "configured" + query_suite = "extended" + `), + Check: resource.TestCheckResourceAttr( + "github_repository_code_scanning_default_setup.test", "query_suite", "extended"), + }, + }, + }) + }) + + t.Run("is idempotent when already not-configured", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%srepo-code-scanning-%s", testResourcePrefix, randomID) + config := testAccCodeScanningDefaultSetupConfig(repoName, `state = "not-configured"`) + + check := resource.TestCheckResourceAttr( + "github_repository_code_scanning_default_setup.test", "state", "not-configured") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + {Config: config, Check: check}, + {Config: config, Check: check}, + }, + }) + }) + + t.Run("imports code scanning default setup", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%srepo-code-scanning-%s", testResourcePrefix, randomID) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testAccCodeScanningDefaultSetupConfig(repoName, `state = "configured"`), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "github_repository_code_scanning_default_setup.test", "repository"), + resource.TestCheckResourceAttr( + "github_repository_code_scanning_default_setup.test", "state", "configured"), + resource.TestCheckResourceAttrSet( + "github_repository_code_scanning_default_setup.test", "query_suite"), + ), + }, + { + ResourceName: "github_repository_code_scanning_default_setup.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + }) + + t.Run("specifies languages not present in repo without error", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%srepo-code-scanning-%s", testResourcePrefix, randomID) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testAccCodeScanningDefaultSetupConfig(repoName, ` + state = "configured" + languages = ["go", "java-kotlin"] + `), + Check: resource.TestCheckResourceAttr( + "github_repository_code_scanning_default_setup.test", "state", "configured"), + }, + }, + }) + }) + + t.Run("full lifecycle: configure, update query suite, unconfigure", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("%srepo-code-scanning-%s", testResourcePrefix, randomID) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testAccCodeScanningDefaultSetupConfig(repoName, ` + state = "configured" + query_suite = "default" + `), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_repository_code_scanning_default_setup.test", "state", "configured"), + resource.TestCheckResourceAttr( + "github_repository_code_scanning_default_setup.test", "query_suite", "default"), + ), + }, + { + Config: testAccCodeScanningDefaultSetupConfig(repoName, ` + state = "configured" + query_suite = "extended" + `), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_repository_code_scanning_default_setup.test", "state", "configured"), + resource.TestCheckResourceAttr( + "github_repository_code_scanning_default_setup.test", "query_suite", "extended"), + ), + }, + { + Config: testAccCodeScanningDefaultSetupConfig(repoName, `state = "not-configured"`), + Check: resource.TestCheckResourceAttr( + "github_repository_code_scanning_default_setup.test", "state", "not-configured"), + }, + }, + }) + }) +} diff --git a/website/docs/r/repository_code_scanning_default_setup.html.markdown b/website/docs/r/repository_code_scanning_default_setup.html.markdown new file mode 100644 index 0000000000..50c31db3ce --- /dev/null +++ b/website/docs/r/repository_code_scanning_default_setup.html.markdown @@ -0,0 +1,68 @@ +--- +layout: "github" +page_title: "GitHub: github_repository_code_scanning_default_setup" +description: |- + Manages code scanning default setup for a repository +--- + +# github_repository_code_scanning_default_setup + +This resource allows you to manage the code scanning default setup configuration for a GitHub repository. +When enabled, GitHub automatically configures CodeQL analysis for supported languages in the repository. + +See the [documentation](https://docs.github.com/en/code-security/code-scanning/enabling-code-scanning/configuring-default-setup-for-code-scanning) +for details of usage and how this will impact your repository. + +## Example Usage + +### Basic usage + +```hcl +resource "github_repository" "example" { + name = "my-repo" + visibility = "public" + auto_init = true +} + +resource "github_repository_code_scanning_default_setup" "example" { + repository = github_repository.example.name + state = "configured" +} +``` + +### With query suite and languages + +```hcl +resource "github_repository" "example" { + name = "my-repo" + visibility = "public" + auto_init = true +} + +resource "github_repository_code_scanning_default_setup" "example" { + repository = github_repository.example.name + state = "configured" + query_suite = "extended" + languages = ["javascript-typescript", "python"] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `repository` - (Required) The name of the GitHub repository. + +* `state` - (Required) The desired state of code scanning default setup. Must be `configured` or `not-configured`. See the [REST API docs](https://docs.github.com/en/rest/code-scanning/code-scanning#update-a-code-scanning-default-setup-configuration). + +* `query_suite` - (Optional) The [query suite](https://docs.github.com/en/code-security/code-scanning/managing-your-code-scanning-configuration/codeql-query-suites) to use. Must be `default` or `extended`. + +* `languages` - (Optional) The CodeQL languages to be analyzed. If not specified, default setup [automatically includes all supported languages](https://github.blog/changelog/2023-10-23-code-scanning-default-setup-automatically-includes-all-codeql-supported-languages/). Supported values: `actions`, `c-cpp`, `csharp`, `go`, `java-kotlin`, `javascript-typescript`, `python`, `ruby`, `swift`. See the [REST API docs](https://docs.github.com/en/rest/code-scanning/code-scanning#update-a-code-scanning-default-setup-configuration). + +## Import + +Code scanning default setup can be imported using the repository name: + +```sh +terraform import github_repository_code_scanning_default_setup.example my-repo +``` From f17976273dd539509af25ce5ef0e2b22f61adaaf Mon Sep 17 00:00:00 2001 From: oda251 <69433701+oda251@users.noreply.github.com> Date: Thu, 2 Apr 2026 22:47:25 +0900 Subject: [PATCH 2/2] fix: address review feedback for code_scanning_default_setup resource - Replace ForceNew with diffRepository + repository_id for rename support - Add archived repo check in Create/Update - Handle 404 in Read (remove from state) and Delete (return nil) - Use tflog instead of log.Printf - Simplify Import to only set repository (Read is called automatically) - Add nil check in waitForCodeScanningState to prevent panic - Migrate tests to ConfigStateChecks, consolidate redundant cases Co-Authored-By: Claude Opus 4.6 (1M context) --- ..._repository_code_scanning_default_setup.go | 62 ++++-- ...sitory_code_scanning_default_setup_test.go | 202 ++++++++---------- 2 files changed, 138 insertions(+), 126 deletions(-) diff --git a/github/resource_github_repository_code_scanning_default_setup.go b/github/resource_github_repository_code_scanning_default_setup.go index b3a8b6a76f..eca684daba 100644 --- a/github/resource_github_repository_code_scanning_default_setup.go +++ b/github/resource_github_repository_code_scanning_default_setup.go @@ -4,10 +4,11 @@ import ( "context" "errors" "fmt" - "log" + "net/http" "time" "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/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -30,13 +31,19 @@ func resourceGithubRepositoryCodeScanningDefaultSetup() *schema.Resource { Delete: schema.DefaultTimeout(5 * time.Minute), }, + CustomizeDiff: diffRepository, + Schema: map[string]*schema.Schema{ "repository": { Type: schema.TypeString, Required: true, - ForceNew: true, Description: "The GitHub repository name.", }, + "repository_id": { + Type: schema.TypeInt, + Computed: true, + Description: "The ID of the GitHub repository.", + }, "state": { Type: schema.TypeString, Required: true, @@ -73,6 +80,14 @@ func resourceGithubRepositoryCodeScanningDefaultSetupCreateOrUpdate(ctx context. repoName := d.Get("repository").(string) state := d.Get("state").(string) + repo, _, err := client.Repositories.Get(ctx, owner, repoName) + if err != nil { + return diag.Errorf("error reading repository %s/%s: %s", owner, repoName, err) + } + if repo.GetArchived() { + return diag.Errorf("repository %s/%s is archived", owner, repoName) + } + options := &github.UpdateDefaultSetupConfigurationOptions{ State: state, } @@ -86,7 +101,7 @@ func resourceGithubRepositoryCodeScanningDefaultSetupCreateOrUpdate(ctx context. options.Languages = expandStringList(v.(*schema.Set).List()) } - _, _, err := client.CodeScanning.UpdateDefaultSetupConfiguration(ctx, owner, repoName, options) + _, _, err = client.CodeScanning.UpdateDefaultSetupConfiguration(ctx, owner, repoName, options) if err != nil { // 202 Accepted is expected — go-github surfaces it as AcceptedError var acceptedErr *github.AcceptedError @@ -117,8 +132,21 @@ func resourceGithubRepositoryCodeScanningDefaultSetupRead(ctx context.Context, d owner := meta.(*Owner).name repoName := d.Get("repository").(string) - if repoName == "" { - repoName = d.Id() + repo, _, err := client.Repositories.Get(ctx, owner, repoName) + if err != nil { + var ghErr *github.ErrorResponse + if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusNotFound { + tflog.Info(ctx, "Repository not found, removing from state", map[string]any{ + "owner": owner, + "repository": repoName, + }) + d.SetId("") + return nil + } + return diag.Errorf("error reading repository %s/%s: %s", owner, repoName, err) + } + if err := d.Set("repository_id", int(repo.GetID())); err != nil { + return diag.Errorf("error setting repository_id: %s", err) } config, _, err := client.CodeScanning.GetDefaultSetupConfiguration(ctx, owner, repoName) @@ -141,16 +169,26 @@ func resourceGithubRepositoryCodeScanningDefaultSetupDelete(ctx context.Context, _, _, err := client.CodeScanning.UpdateDefaultSetupConfiguration(ctx, owner, repoName, options) if err != nil { var acceptedErr *github.AcceptedError - if !errors.As(err, &acceptedErr) { + var ghErr *github.ErrorResponse + switch { + case errors.As(err, &acceptedErr): + // 202 Accepted is expected + case errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusNotFound: + // repository already gone + return nil + default: return diag.Errorf("error disabling code scanning default setup for %s/%s: %s", owner, repoName, err) } } - log.Printf("[INFO] Code scanning default setup disabled for %s/%s", owner, repoName) + tflog.Info(ctx, "Code scanning default setup disabled", map[string]any{ + "owner": owner, + "repository": repoName, + }) return nil } -func resourceGithubRepositoryCodeScanningDefaultSetupImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { +func resourceGithubRepositoryCodeScanningDefaultSetupImport(_ context.Context, d *schema.ResourceData, _ any) ([]*schema.ResourceData, error) { repoName := d.Id() if repoName == "" { return nil, fmt.Errorf("repository name must not be empty") @@ -160,11 +198,6 @@ func resourceGithubRepositoryCodeScanningDefaultSetupImport(ctx context.Context, return nil, err } - diags := resourceGithubRepositoryCodeScanningDefaultSetupRead(ctx, d, meta) - if diags.HasError() { - return nil, fmt.Errorf("error importing code scanning default setup for %s: %s", repoName, diags[0].Summary) - } - return []*schema.ResourceData{d}, nil } @@ -205,6 +238,9 @@ func waitForCodeScanningState(ctx context.Context, client *github.Client, owner, if err != nil { return nil, err } + if result == nil { + return nil, fmt.Errorf("code scanning default setup returned nil result for %s/%s", owner, repo) + } return result.(*github.DefaultSetupConfiguration), nil } diff --git a/github/resource_github_repository_code_scanning_default_setup_test.go b/github/resource_github_repository_code_scanning_default_setup_test.go index 9dfa99fa6e..657d290d65 100644 --- a/github/resource_github_repository_code_scanning_default_setup_test.go +++ b/github/resource_github_repository_code_scanning_default_setup_test.go @@ -2,10 +2,14 @@ package github import ( "fmt" + "regexp" "testing" "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 testAccCodeScanningDefaultSetupConfig(repoName, extraAttrs string) string { @@ -24,35 +28,7 @@ func testAccCodeScanningDefaultSetupConfig(repoName, extraAttrs string) string { } func TestAccGithubRepositoryCodeScanningDefaultSetup(t *testing.T) { - t.Run("configures and unconfigures code scanning default setup", func(t *testing.T) { - randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - repoName := fmt.Sprintf("%srepo-code-scanning-%s", testResourcePrefix, randomID) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnauthenticated(t) }, - ProviderFactories: providerFactories, - Steps: []resource.TestStep{ - { - Config: testAccCodeScanningDefaultSetupConfig(repoName, `state = "configured"`), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository_code_scanning_default_setup.test", "state", "configured"), - resource.TestCheckResourceAttrSet( - "github_repository_code_scanning_default_setup.test", "query_suite"), - ), - }, - { - Config: testAccCodeScanningDefaultSetupConfig(repoName, `state = "not-configured"`), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository_code_scanning_default_setup.test", "state", "not-configured"), - ), - }, - }, - }) - }) - - t.Run("configures with extended query suite", func(t *testing.T) { + t.Run("configures with explicit query suite and languages", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) repoName := fmt.Sprintf("%srepo-code-scanning-%s", testResourcePrefix, randomID) @@ -64,43 +40,49 @@ func TestAccGithubRepositoryCodeScanningDefaultSetup(t *testing.T) { Config: testAccCodeScanningDefaultSetupConfig(repoName, ` state = "configured" query_suite = "extended" + languages = ["javascript-typescript", "python"] `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository_code_scanning_default_setup.test", "state", "configured"), - resource.TestCheckResourceAttr( - "github_repository_code_scanning_default_setup.test", "query_suite", "extended"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository_code_scanning_default_setup.test", + tfjsonpath.New("state"), knownvalue.StringExact("configured")), + statecheck.ExpectKnownValue("github_repository_code_scanning_default_setup.test", + tfjsonpath.New("query_suite"), knownvalue.StringExact("extended")), + statecheck.ExpectKnownValue("github_repository_code_scanning_default_setup.test", + tfjsonpath.New("languages"), knownvalue.SetSizeExact(2)), + }, }, }, }) }) - t.Run("configures with explicit languages", func(t *testing.T) { + t.Run("is idempotent when already not-configured", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) repoName := fmt.Sprintf("%srepo-code-scanning-%s", testResourcePrefix, randomID) + config := testAccCodeScanningDefaultSetupConfig(repoName, `state = "not-configured"`) resource.Test(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { - Config: testAccCodeScanningDefaultSetupConfig(repoName, ` - state = "configured" - languages = ["javascript-typescript", "python"] - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository_code_scanning_default_setup.test", "state", "configured"), - resource.TestCheckResourceAttr( - "github_repository_code_scanning_default_setup.test", "languages.#", "2"), - ), + Config: config, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository_code_scanning_default_setup.test", + tfjsonpath.New("state"), knownvalue.StringExact("not-configured")), + }, + }, + { + Config: config, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository_code_scanning_default_setup.test", + tfjsonpath.New("state"), knownvalue.StringExact("not-configured")), + }, }, }, }) }) - t.Run("updates query suite without changing state", func(t *testing.T) { + t.Run("imports code scanning default setup", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) repoName := fmt.Sprintf("%srepo-code-scanning-%s", testResourcePrefix, randomID) @@ -109,44 +91,28 @@ func TestAccGithubRepositoryCodeScanningDefaultSetup(t *testing.T) { ProviderFactories: providerFactories, Steps: []resource.TestStep{ { - Config: testAccCodeScanningDefaultSetupConfig(repoName, ` - state = "configured" - query_suite = "default" - `), - Check: resource.TestCheckResourceAttr( - "github_repository_code_scanning_default_setup.test", "query_suite", "default"), + Config: testAccCodeScanningDefaultSetupConfig(repoName, `state = "configured"`), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository_code_scanning_default_setup.test", + tfjsonpath.New("repository"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("github_repository_code_scanning_default_setup.test", + tfjsonpath.New("repository_id"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("github_repository_code_scanning_default_setup.test", + tfjsonpath.New("state"), knownvalue.StringExact("configured")), + statecheck.ExpectKnownValue("github_repository_code_scanning_default_setup.test", + tfjsonpath.New("query_suite"), knownvalue.NotNull()), + }, }, { - Config: testAccCodeScanningDefaultSetupConfig(repoName, ` - state = "configured" - query_suite = "extended" - `), - Check: resource.TestCheckResourceAttr( - "github_repository_code_scanning_default_setup.test", "query_suite", "extended"), + ResourceName: "github_repository_code_scanning_default_setup.test", + ImportState: true, + ImportStateVerify: true, }, }, }) }) - t.Run("is idempotent when already not-configured", func(t *testing.T) { - randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - repoName := fmt.Sprintf("%srepo-code-scanning-%s", testResourcePrefix, randomID) - config := testAccCodeScanningDefaultSetupConfig(repoName, `state = "not-configured"`) - - check := resource.TestCheckResourceAttr( - "github_repository_code_scanning_default_setup.test", "state", "not-configured") - - resource.Test(t, resource.TestCase{ - PreCheck: func() { skipUnauthenticated(t) }, - ProviderFactories: providerFactories, - Steps: []resource.TestStep{ - {Config: config, Check: check}, - {Config: config, Check: check}, - }, - }) - }) - - t.Run("imports code scanning default setup", func(t *testing.T) { + t.Run("specifies languages not present in repo without error", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) repoName := fmt.Sprintf("%srepo-code-scanning-%s", testResourcePrefix, randomID) @@ -155,40 +121,48 @@ func TestAccGithubRepositoryCodeScanningDefaultSetup(t *testing.T) { ProviderFactories: providerFactories, Steps: []resource.TestStep{ { - Config: testAccCodeScanningDefaultSetupConfig(repoName, `state = "configured"`), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrSet( - "github_repository_code_scanning_default_setup.test", "repository"), - resource.TestCheckResourceAttr( - "github_repository_code_scanning_default_setup.test", "state", "configured"), - resource.TestCheckResourceAttrSet( - "github_repository_code_scanning_default_setup.test", "query_suite"), - ), - }, - { - ResourceName: "github_repository_code_scanning_default_setup.test", - ImportState: true, - ImportStateVerify: true, + Config: testAccCodeScanningDefaultSetupConfig(repoName, ` + state = "configured" + languages = ["go", "java-kotlin"] + `), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository_code_scanning_default_setup.test", + tfjsonpath.New("state"), knownvalue.StringExact("configured")), + }, }, }, }) }) - t.Run("specifies languages not present in repo without error", func(t *testing.T) { + t.Run("prevents configuring on archived repository", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) repoName := fmt.Sprintf("%srepo-code-scanning-%s", testResourcePrefix, randomID) + repoConfig := ` + resource "github_repository" "test" { + name = "%s" + visibility = "public" + auto_init = true + archived = %t + } + %s + ` + codeScanningConfig := ` + resource "github_repository_code_scanning_default_setup" "test" { + repository = github_repository.test.name + state = "configured" + } + ` resource.Test(t, resource.TestCase{ PreCheck: func() { skipUnauthenticated(t) }, ProviderFactories: providerFactories, Steps: []resource.TestStep{ { - Config: testAccCodeScanningDefaultSetupConfig(repoName, ` - state = "configured" - languages = ["go", "java-kotlin"] - `), - Check: resource.TestCheckResourceAttr( - "github_repository_code_scanning_default_setup.test", "state", "configured"), + Config: fmt.Sprintf(repoConfig, repoName, false, ""), + }, + { + Config: fmt.Sprintf(repoConfig, repoName, true, codeScanningConfig), + ExpectError: regexp.MustCompile("is archived"), }, }, }) @@ -207,29 +181,31 @@ func TestAccGithubRepositoryCodeScanningDefaultSetup(t *testing.T) { state = "configured" query_suite = "default" `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository_code_scanning_default_setup.test", "state", "configured"), - resource.TestCheckResourceAttr( - "github_repository_code_scanning_default_setup.test", "query_suite", "default"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository_code_scanning_default_setup.test", + tfjsonpath.New("state"), knownvalue.StringExact("configured")), + statecheck.ExpectKnownValue("github_repository_code_scanning_default_setup.test", + tfjsonpath.New("query_suite"), knownvalue.StringExact("default")), + }, }, { Config: testAccCodeScanningDefaultSetupConfig(repoName, ` state = "configured" query_suite = "extended" `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "github_repository_code_scanning_default_setup.test", "state", "configured"), - resource.TestCheckResourceAttr( - "github_repository_code_scanning_default_setup.test", "query_suite", "extended"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository_code_scanning_default_setup.test", + tfjsonpath.New("state"), knownvalue.StringExact("configured")), + statecheck.ExpectKnownValue("github_repository_code_scanning_default_setup.test", + tfjsonpath.New("query_suite"), knownvalue.StringExact("extended")), + }, }, { Config: testAccCodeScanningDefaultSetupConfig(repoName, `state = "not-configured"`), - Check: resource.TestCheckResourceAttr( - "github_repository_code_scanning_default_setup.test", "state", "not-configured"), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository_code_scanning_default_setup.test", + tfjsonpath.New("state"), knownvalue.StringExact("not-configured")), + }, }, }, })