Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion github/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
210 changes: 210 additions & 0 deletions github/resource_github_repository_code_scanning_default_setup.go
Original file line number Diff line number Diff line change
@@ -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.",
},
Comment on lines +37 to +41
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove ForceNew and add the diffRepo structure used in other similar resources. This allows support for renaming repositories

"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()
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How could this situation arise?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Removed the dead code.


config, _, err := client.CodeScanning.GetDefaultSetupConfiguration(ctx, owner, repoName)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: does this return err or success if the repo has been archived?
If success, then we need to also check if the repo is archived and return an error

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API returns 403 on archived repos, but with a misleading message ("Code scanning is not enabled"). Added an explicit archived check in Create/Update to provide a clear error message, along with a test case. Read does not check (consistent with other resources).

# configured before archiving
$ gh api repos/oda251/actrun-mcp/code-scanning/default-setup
{"state":"configured","languages":[],"query_suite":"default",...}

# after archiving
$ gh api repos/oda251/actrun-mcp/code-scanning/default-setup
403: "Code scanning is not enabled for this repository."

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)
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also not error if the repo is gone.

}

log.Printf("[INFO] Code scanning default setup disabled for %s/%s", owner, repoName)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use tflog

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)
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't ever call any of the CRUD functions. Read will be automatically called after import, you only need to set any important fields for Read to work


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
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this panic if the wait returns nil?

}
Loading