Skip to content

Commit 405dc41

Browse files
author
Jeremy Udit
authored
Add github_branch_protection_v3 Resource (#642)
* Add `branch_protection_v3` Resource - add new resource to `website/github.erb` - add new resource to `website/docs/r/<resource>.html.markdown` - add new resource to `github/provider.go` - add tests for resource in `github/resource_<resource>_test.go` - implement new resource in `github/resource_<resource>.go` * fixup! gofmt fixes
1 parent 39ef30e commit 405dc41

7 files changed

+1031
-1
lines changed

github/provider.go

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ func Provider() terraform.ResourceProvider {
4545
"github_actions_secret": resourceGithubActionsSecret(),
4646
"github_branch": resourceGithubBranch(),
4747
"github_branch_protection": resourceGithubBranchProtection(),
48+
"github_branch_protection_v3": resourceGithubBranchProtectionV3(),
4849
"github_issue_label": resourceGithubIssueLabel(),
4950
"github_membership": resourceGithubMembership(),
5051
"github_organization_block": resourceOrganizationBlock(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"net/http"
8+
9+
"github.com/google/go-github/v32/github"
10+
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
11+
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
12+
)
13+
14+
func resourceGithubBranchProtectionV3() *schema.Resource {
15+
return &schema.Resource{
16+
Create: resourceGithubBranchProtectionV3Create,
17+
Read: resourceGithubBranchProtectionV3Read,
18+
Update: resourceGithubBranchProtectionV3Update,
19+
Delete: resourceGithubBranchProtectionV3Delete,
20+
Importer: &schema.ResourceImporter{
21+
State: schema.ImportStatePassthrough,
22+
},
23+
24+
Schema: map[string]*schema.Schema{
25+
"repository": {
26+
Type: schema.TypeString,
27+
Required: true,
28+
ForceNew: true,
29+
},
30+
"branch": {
31+
Type: schema.TypeString,
32+
Required: true,
33+
ForceNew: true,
34+
},
35+
"required_status_checks": {
36+
Type: schema.TypeList,
37+
Optional: true,
38+
MaxItems: 1,
39+
Elem: &schema.Resource{
40+
Schema: map[string]*schema.Schema{
41+
"include_admins": {
42+
Type: schema.TypeBool,
43+
Optional: true,
44+
Default: false,
45+
Deprecated: "Use enforce_admins instead",
46+
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
47+
return true
48+
},
49+
},
50+
"strict": {
51+
Type: schema.TypeBool,
52+
Optional: true,
53+
Default: false,
54+
},
55+
"contexts": {
56+
Type: schema.TypeSet,
57+
Optional: true,
58+
Elem: &schema.Schema{
59+
Type: schema.TypeString,
60+
},
61+
},
62+
},
63+
},
64+
},
65+
"required_pull_request_reviews": {
66+
Type: schema.TypeList,
67+
Optional: true,
68+
MaxItems: 1,
69+
Elem: &schema.Resource{
70+
Schema: map[string]*schema.Schema{
71+
// FIXME: Remove this deprecated field
72+
"include_admins": {
73+
Type: schema.TypeBool,
74+
Optional: true,
75+
Default: false,
76+
Deprecated: "Use enforce_admins instead",
77+
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
78+
return true
79+
},
80+
},
81+
"dismiss_stale_reviews": {
82+
Type: schema.TypeBool,
83+
Optional: true,
84+
Default: false,
85+
},
86+
"dismissal_users": {
87+
Type: schema.TypeSet,
88+
Optional: true,
89+
Elem: &schema.Schema{Type: schema.TypeString},
90+
},
91+
"dismissal_teams": {
92+
Type: schema.TypeSet,
93+
Optional: true,
94+
Elem: &schema.Schema{Type: schema.TypeString},
95+
},
96+
"require_code_owner_reviews": {
97+
Type: schema.TypeBool,
98+
Optional: true,
99+
},
100+
"required_approving_review_count": {
101+
Type: schema.TypeInt,
102+
Optional: true,
103+
Default: 1,
104+
ValidateFunc: validation.IntBetween(1, 6),
105+
},
106+
},
107+
},
108+
},
109+
"restrictions": {
110+
Type: schema.TypeList,
111+
Optional: true,
112+
MaxItems: 1,
113+
Elem: &schema.Resource{
114+
Schema: map[string]*schema.Schema{
115+
"users": {
116+
Type: schema.TypeSet,
117+
Optional: true,
118+
Elem: &schema.Schema{Type: schema.TypeString},
119+
},
120+
"teams": {
121+
Type: schema.TypeSet,
122+
Optional: true,
123+
Elem: &schema.Schema{Type: schema.TypeString},
124+
},
125+
"apps": {
126+
Type: schema.TypeSet,
127+
Optional: true,
128+
Elem: &schema.Schema{Type: schema.TypeString},
129+
},
130+
},
131+
},
132+
},
133+
"enforce_admins": {
134+
Type: schema.TypeBool,
135+
Optional: true,
136+
Default: false,
137+
},
138+
"require_signed_commits": {
139+
Type: schema.TypeBool,
140+
Optional: true,
141+
Default: false,
142+
},
143+
"etag": {
144+
Type: schema.TypeString,
145+
Computed: true,
146+
},
147+
},
148+
}
149+
}
150+
151+
func resourceGithubBranchProtectionV3Create(d *schema.ResourceData, meta interface{}) error {
152+
err := checkOrganization(meta)
153+
if err != nil {
154+
return err
155+
}
156+
157+
client := meta.(*Owner).v3client
158+
159+
orgName := meta.(*Owner).name
160+
repoName := d.Get("repository").(string)
161+
branch := d.Get("branch").(string)
162+
163+
protectionRequest, err := buildProtectionRequest(d)
164+
if err != nil {
165+
return err
166+
}
167+
ctx := context.Background()
168+
169+
log.Printf("[DEBUG] Creating branch protection: %s/%s (%s)",
170+
orgName, repoName, branch)
171+
protection, _, err := client.Repositories.UpdateBranchProtection(ctx,
172+
orgName,
173+
repoName,
174+
branch,
175+
protectionRequest,
176+
)
177+
if err != nil {
178+
return err
179+
}
180+
181+
if err := checkBranchRestrictionsUsers(protection.GetRestrictions(), protectionRequest.GetRestrictions()); err != nil {
182+
return err
183+
}
184+
185+
d.SetId(buildTwoPartID(repoName, branch))
186+
187+
if err = requireSignedCommitsUpdate(d, meta); err != nil {
188+
return err
189+
}
190+
191+
return resourceGithubBranchProtectionV3Read(d, meta)
192+
}
193+
194+
func resourceGithubBranchProtectionV3Read(d *schema.ResourceData, meta interface{}) error {
195+
err := checkOrganization(meta)
196+
if err != nil {
197+
return err
198+
}
199+
200+
client := meta.(*Owner).v3client
201+
202+
repoName, branch, err := parseTwoPartID(d.Id(), "repository", "branch")
203+
if err != nil {
204+
return err
205+
}
206+
orgName := meta.(*Owner).name
207+
208+
ctx := context.WithValue(context.Background(), ctxId, d.Id())
209+
if !d.IsNewResource() {
210+
ctx = context.WithValue(ctx, ctxEtag, d.Get("etag").(string))
211+
}
212+
213+
log.Printf("[DEBUG] Reading branch protection: %s/%s (%s)",
214+
orgName, repoName, branch)
215+
githubProtection, resp, err := client.Repositories.GetBranchProtection(ctx,
216+
orgName, repoName, branch)
217+
if err != nil {
218+
if ghErr, ok := err.(*github.ErrorResponse); ok {
219+
if ghErr.Response.StatusCode == http.StatusNotModified {
220+
if err := requireSignedCommitsRead(d, meta); err != nil {
221+
return fmt.Errorf("Error setting signed commit restriction: %v", err)
222+
}
223+
return nil
224+
}
225+
if ghErr.Response.StatusCode == http.StatusNotFound {
226+
log.Printf("[WARN] Removing branch protection %s/%s (%s) from state because it no longer exists in GitHub",
227+
orgName, repoName, branch)
228+
d.SetId("")
229+
return nil
230+
}
231+
}
232+
233+
return err
234+
}
235+
236+
d.Set("etag", resp.Header.Get("ETag"))
237+
d.Set("repository", repoName)
238+
d.Set("branch", branch)
239+
d.Set("enforce_admins", githubProtection.GetEnforceAdmins().Enabled)
240+
241+
if err := flattenAndSetRequiredStatusChecks(d, githubProtection); err != nil {
242+
return fmt.Errorf("Error setting required_status_checks: %v", err)
243+
}
244+
245+
if err := flattenAndSetRequiredPullRequestReviews(d, githubProtection); err != nil {
246+
return fmt.Errorf("Error setting required_pull_request_reviews: %v", err)
247+
}
248+
249+
if err := flattenAndSetRestrictions(d, githubProtection); err != nil {
250+
return fmt.Errorf("Error setting restrictions: %v", err)
251+
}
252+
253+
if err := requireSignedCommitsRead(d, meta); err != nil {
254+
return fmt.Errorf("Error setting signed commit restriction: %v", err)
255+
}
256+
257+
return nil
258+
}
259+
260+
func resourceGithubBranchProtectionV3Update(d *schema.ResourceData, meta interface{}) error {
261+
err := checkOrganization(meta)
262+
if err != nil {
263+
return err
264+
}
265+
266+
client := meta.(*Owner).v3client
267+
repoName, branch, err := parseTwoPartID(d.Id(), "repository", "branch")
268+
if err != nil {
269+
return err
270+
}
271+
272+
protectionRequest, err := buildProtectionRequest(d)
273+
if err != nil {
274+
return err
275+
}
276+
277+
orgName := meta.(*Owner).name
278+
ctx := context.WithValue(context.Background(), ctxId, d.Id())
279+
280+
log.Printf("[DEBUG] Updating branch protection: %s/%s (%s)",
281+
orgName, repoName, branch)
282+
protection, _, err := client.Repositories.UpdateBranchProtection(ctx,
283+
orgName,
284+
repoName,
285+
branch,
286+
protectionRequest,
287+
)
288+
if err != nil {
289+
return err
290+
}
291+
292+
if err := checkBranchRestrictionsUsers(protection.GetRestrictions(), protectionRequest.GetRestrictions()); err != nil {
293+
return err
294+
}
295+
296+
if protectionRequest.RequiredPullRequestReviews == nil {
297+
_, err = client.Repositories.RemovePullRequestReviewEnforcement(ctx,
298+
orgName,
299+
repoName,
300+
branch,
301+
)
302+
if err != nil {
303+
return err
304+
}
305+
}
306+
307+
d.SetId(buildTwoPartID(repoName, branch))
308+
309+
if err = requireSignedCommitsUpdate(d, meta); err != nil {
310+
return err
311+
}
312+
313+
return resourceGithubBranchProtectionV3Read(d, meta)
314+
}
315+
316+
func resourceGithubBranchProtectionV3Delete(d *schema.ResourceData, meta interface{}) error {
317+
err := checkOrganization(meta)
318+
if err != nil {
319+
return err
320+
}
321+
322+
client := meta.(*Owner).v3client
323+
repoName, branch, err := parseTwoPartID(d.Id(), "repository", "branch")
324+
if err != nil {
325+
return err
326+
}
327+
328+
orgName := meta.(*Owner).name
329+
ctx := context.WithValue(context.Background(), ctxId, d.Id())
330+
331+
log.Printf("[DEBUG] Deleting branch protection: %s/%s (%s)", orgName, repoName, branch)
332+
_, err = client.Repositories.RemoveBranchProtection(ctx,
333+
orgName, repoName, branch)
334+
return err
335+
}

0 commit comments

Comments
 (0)