Skip to content

Commit f26ff06

Browse files
oda251claude
andcommitted
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) <noreply@anthropic.com>
1 parent 94f71cf commit f26ff06

File tree

4 files changed

+517
-1
lines changed

4 files changed

+517
-1
lines changed

github/provider.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,9 @@ func Provider() *schema.Provider {
188188
"github_release": resourceGithubRelease(),
189189
"github_repository": resourceGithubRepository(),
190190
"github_repository_autolink_reference": resourceGithubRepositoryAutolinkReference(),
191-
"github_repository_dependabot_security_updates": resourceGithubRepositoryDependabotSecurityUpdates(),
191+
"github_repository_code_scanning_default_setup": resourceGithubRepositoryCodeScanningDefaultSetup(),
192192
"github_repository_collaborator": resourceGithubRepositoryCollaborator(),
193+
"github_repository_dependabot_security_updates": resourceGithubRepositoryDependabotSecurityUpdates(),
193194
"github_repository_collaborators": resourceGithubRepositoryCollaborators(),
194195
"github_repository_custom_property": resourceGithubRepositoryCustomProperty(),
195196
"github_repository_deploy_key": resourceGithubRepositoryDeployKey(),
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"log"
8+
"time"
9+
10+
"github.com/google/go-github/v84/github"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
12+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
13+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
14+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
15+
)
16+
17+
func resourceGithubRepositoryCodeScanningDefaultSetup() *schema.Resource {
18+
return &schema.Resource{
19+
CreateContext: resourceGithubRepositoryCodeScanningDefaultSetupCreateOrUpdate,
20+
ReadContext: resourceGithubRepositoryCodeScanningDefaultSetupRead,
21+
UpdateContext: resourceGithubRepositoryCodeScanningDefaultSetupCreateOrUpdate,
22+
DeleteContext: resourceGithubRepositoryCodeScanningDefaultSetupDelete,
23+
Importer: &schema.ResourceImporter{
24+
StateContext: resourceGithubRepositoryCodeScanningDefaultSetupImport,
25+
},
26+
27+
Timeouts: &schema.ResourceTimeout{
28+
Create: schema.DefaultTimeout(5 * time.Minute),
29+
Update: schema.DefaultTimeout(5 * time.Minute),
30+
Delete: schema.DefaultTimeout(5 * time.Minute),
31+
},
32+
33+
Schema: map[string]*schema.Schema{
34+
"repository": {
35+
Type: schema.TypeString,
36+
Required: true,
37+
ForceNew: true,
38+
Description: "The GitHub repository name.",
39+
},
40+
"state": {
41+
Type: schema.TypeString,
42+
Required: true,
43+
Description: "The desired state of code scanning default setup. Must be `configured` or `not-configured`.",
44+
ValidateDiagFunc: validation.ToDiagFunc(
45+
validation.StringInSlice([]string{"configured", "not-configured"}, false),
46+
),
47+
},
48+
"query_suite": {
49+
Type: schema.TypeString,
50+
Optional: true,
51+
Computed: true,
52+
Description: "The query suite to use. Must be `default` or `extended`.",
53+
ValidateDiagFunc: validation.ToDiagFunc(
54+
validation.StringInSlice([]string{"default", "extended"}, false),
55+
),
56+
},
57+
"languages": {
58+
Type: schema.TypeSet,
59+
Optional: true,
60+
Computed: true,
61+
Description: "The languages to enable for code scanning. Supported values include `actions`, `c-cpp`, `csharp`, `go`, `java-kotlin`, `javascript-typescript`, `python`, `ruby`, `swift`.",
62+
Elem: &schema.Schema{
63+
Type: schema.TypeString,
64+
},
65+
},
66+
},
67+
}
68+
}
69+
70+
func resourceGithubRepositoryCodeScanningDefaultSetupCreateOrUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
71+
client := meta.(*Owner).v3client
72+
owner := meta.(*Owner).name
73+
repoName := d.Get("repository").(string)
74+
state := d.Get("state").(string)
75+
76+
options := &github.UpdateDefaultSetupConfigurationOptions{
77+
State: state,
78+
}
79+
80+
if v, ok := d.GetOk("query_suite"); ok {
81+
qs := v.(string)
82+
options.QuerySuite = &qs
83+
}
84+
85+
if v, ok := d.GetOk("languages"); ok {
86+
options.Languages = expandStringList(v.(*schema.Set).List())
87+
}
88+
89+
_, _, err := client.CodeScanning.UpdateDefaultSetupConfiguration(ctx, owner, repoName, options)
90+
if err != nil {
91+
// 202 Accepted is expected — go-github surfaces it as AcceptedError
92+
var acceptedErr *github.AcceptedError
93+
if !errors.As(err, &acceptedErr) {
94+
return diag.Errorf("error updating code scanning default setup for %s/%s: %s", owner, repoName, err)
95+
}
96+
}
97+
98+
d.SetId(repoName)
99+
100+
var timeout time.Duration
101+
if d.IsNewResource() {
102+
timeout = d.Timeout(schema.TimeoutCreate)
103+
} else {
104+
timeout = d.Timeout(schema.TimeoutUpdate)
105+
}
106+
107+
config, err := waitForCodeScanningState(ctx, client, owner, repoName, state, timeout)
108+
if err != nil {
109+
return diag.Errorf("error waiting for code scanning default setup state for %s/%s: %s", owner, repoName, err)
110+
}
111+
112+
return setCodeScanningDefaultSetupState(d, config)
113+
}
114+
115+
func resourceGithubRepositoryCodeScanningDefaultSetupRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
116+
client := meta.(*Owner).v3client
117+
owner := meta.(*Owner).name
118+
repoName := d.Get("repository").(string)
119+
120+
if repoName == "" {
121+
repoName = d.Id()
122+
}
123+
124+
config, _, err := client.CodeScanning.GetDefaultSetupConfiguration(ctx, owner, repoName)
125+
if err != nil {
126+
return diag.Errorf("error reading code scanning default setup for %s/%s: %s", owner, repoName, err)
127+
}
128+
129+
return setCodeScanningDefaultSetupState(d, config)
130+
}
131+
132+
func resourceGithubRepositoryCodeScanningDefaultSetupDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
133+
client := meta.(*Owner).v3client
134+
owner := meta.(*Owner).name
135+
repoName := d.Get("repository").(string)
136+
137+
options := &github.UpdateDefaultSetupConfigurationOptions{
138+
State: "not-configured",
139+
}
140+
141+
_, _, err := client.CodeScanning.UpdateDefaultSetupConfiguration(ctx, owner, repoName, options)
142+
if err != nil {
143+
var acceptedErr *github.AcceptedError
144+
if !errors.As(err, &acceptedErr) {
145+
return diag.Errorf("error disabling code scanning default setup for %s/%s: %s", owner, repoName, err)
146+
}
147+
}
148+
149+
log.Printf("[INFO] Code scanning default setup disabled for %s/%s", owner, repoName)
150+
return nil
151+
}
152+
153+
func resourceGithubRepositoryCodeScanningDefaultSetupImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) {
154+
repoName := d.Id()
155+
if repoName == "" {
156+
return nil, fmt.Errorf("repository name must not be empty")
157+
}
158+
159+
if err := d.Set("repository", repoName); err != nil {
160+
return nil, err
161+
}
162+
163+
diags := resourceGithubRepositoryCodeScanningDefaultSetupRead(ctx, d, meta)
164+
if diags.HasError() {
165+
return nil, fmt.Errorf("error importing code scanning default setup for %s: %s", repoName, diags[0].Summary)
166+
}
167+
168+
return []*schema.ResourceData{d}, nil
169+
}
170+
171+
func setCodeScanningDefaultSetupState(d *schema.ResourceData, config *github.DefaultSetupConfiguration) diag.Diagnostics {
172+
if err := d.Set("state", config.GetState()); err != nil {
173+
return diag.Errorf("error setting state: %s", err)
174+
}
175+
if err := d.Set("query_suite", config.GetQuerySuite()); err != nil {
176+
return diag.Errorf("error setting query_suite: %s", err)
177+
}
178+
if err := d.Set("languages", config.Languages); err != nil {
179+
return diag.Errorf("error setting languages: %s", err)
180+
}
181+
return nil
182+
}
183+
184+
func waitForCodeScanningState(ctx context.Context, client *github.Client, owner, repo, targetState string, timeout time.Duration) (*github.DefaultSetupConfiguration, error) {
185+
conf := &retry.StateChangeConf{
186+
Pending: []string{"pending"},
187+
Target: []string{targetState},
188+
Timeout: timeout,
189+
Delay: 1 * time.Second,
190+
MinTimeout: 1 * time.Second,
191+
Refresh: func() (any, string, error) {
192+
config, _, err := client.CodeScanning.GetDefaultSetupConfiguration(ctx, owner, repo)
193+
if err != nil {
194+
return nil, "", err
195+
}
196+
state := config.GetState()
197+
if state == targetState {
198+
return config, state, nil
199+
}
200+
return config, "pending", nil
201+
},
202+
}
203+
204+
result, err := conf.WaitForStateContext(ctx)
205+
if err != nil {
206+
return nil, err
207+
}
208+
209+
return result.(*github.DefaultSetupConfiguration), nil
210+
}

0 commit comments

Comments
 (0)