forgejo-terraform-provider/gitea/resource_gitea_repository_branch_protection.go
Jörg Markert a07bd291f5 feat: add branch_protection resource (#72)
added terraform tests for the resource

Reviewed-on: https://gitea.com/gitea/terraform-provider-gitea/pulls/72
Co-authored-by: Jörg Markert <joerg.markert@gmail.com>
Co-committed-by: Jörg Markert <joerg.markert@gmail.com>
2024-09-11 17:32:48 +00:00

495 lines
18 KiB
Go

package gitea
import (
"log"
"code.gitea.io/sdk/gitea"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const (
repoBPUsername string = "username"
repoBPName string = "name"
repoBPRuleName string = "rule_name"
repoBPProtectedFilePatterns string = "protected_file_patterns"
repoBPUnprotectedFilePatterns string = "unprotected_file_patterns"
repoBPEnablePush string = "enable_push"
repoBPEnablePushWhitelist string = "enable_push_whitelist"
repoBPPushWhitelistUsers string = "push_whitelist_users"
repoBPPushWhitelistTeams string = "push_whitelist_teams"
repoBPPushWhitelistDeployKeys string = "push_whitelist_deploy_keys"
repoBPRequireSignedCommits string = "require_signed_commits"
repoBPRequiredApprovals string = "required_approvals"
repoBPEnableApprovalWhitelist string = "enable_approval_whitelist"
repoBPApprovalWhitelistUsers string = "approval_whitelist_users"
repoBPApprovalWhitelistTeams string = "approval_whitelist_teams"
repoBPDismissStaleApprovals string = "dismiss_stale_approvals"
// not implemented in go-gitea-sdk
// repoBPIgnoreStaleApprovals string = "ignore_stale_approvals"
repoBPEnableStatusCheck string = "enable_status_check"
repoBPStatusCheckPatterns string = "status_check_patterns"
repoBPEnableMergeWhitelist string = "enable_merge_whitelist"
repoBPMergeWhitelistUsers string = "merge_whitelist_users"
repoBPMergeWhitelistTeams string = "merge_whitelist_teams"
repoBPBlockMergeOnRejectedReviews string = "block_merge_on_rejected_reviews"
repoBPBlockMergeOnOfficialReviewRequests string = "block_merge_on_official_review_requests"
repoBPBlockMergeOnOutdatedBranch string = "block_merge_on_outdated_branch"
repoBPUpdatedAt string = "updated_at"
repoBPCreatedAt string = "created_at"
)
func resourceRepositoryBranchProtectionRead(d *schema.ResourceData, meta interface{}) (err error) {
client := meta.(*gitea.Client)
user := d.Get(repoBPUsername).(string)
repo := d.Get(repoBPName).(string)
rule_name := d.Get(repoBPRuleName).(string)
bp, resp, err := client.GetBranchProtection(user, repo, rule_name)
if err != nil {
if resp.StatusCode == 404 {
d.SetId("")
return
} else {
return err
}
}
err = setRepositoryBranchProtectionData(bp, user, repo, d)
return err
}
func generateWhitelist(d *schema.ResourceData, listname string) (enabled bool, users []string, teams []string) {
u := d.Get(listname + "_users")
users = make([]string, 0)
if u != nil {
for _, element := range u.([]interface{}) {
users = append(users, element.(string))
}
}
t := d.Get(listname + "_teams")
teams = make([]string, 0)
if u != nil {
for _, element := range t.([]interface{}) {
teams = append(teams, element.(string))
}
}
if c := len(users) + len(teams); c > 0 {
enabled = true
}
if listname == "push_whitelist" && d.Get(repoBPPushWhitelistDeployKeys).(bool) {
enabled = true
}
log.Println("enabled?:", enabled, listname)
return enabled, users, teams
}
func resourceRepositoryBranchProtectionCreate(d *schema.ResourceData, meta interface{}) (err error) {
client := meta.(*gitea.Client)
user := d.Get(repoBPUsername).(string)
repo := d.Get(repoBPName).(string)
enablePushWhitelist, pushWhitelistUsernames, pushWhitelistTeams := generateWhitelist(d, "push_whitelist")
enableMergeWhitelist, mergeWhitelistUsernames, mergeWhitelistTeams := generateWhitelist(d, "merge_whitelist")
enableApprovalsWhitelist, approvalsWhitelistUsernames, approvalsWhitelistTeams := generateWhitelist(d, "approval_whitelist")
statusCheckContexts := make([]string, 0)
for _, element := range d.Get(repoBPStatusCheckPatterns).([]interface{}) {
statusCheckContexts = append(statusCheckContexts, element.(string))
}
log.Println("create_ulist:", pushWhitelistUsernames)
enableStatusCheck := false
if len(statusCheckContexts) > 0 {
enableStatusCheck = true
}
bpOption := gitea.CreateBranchProtectionOption{
// BranchName is deprecated in gitea, but still required in go-gitea-sdk, therefore using RuleName
BranchName: d.Get(repoBPRuleName).(string),
RuleName: d.Get(repoBPRuleName).(string),
EnablePush: d.Get(repoBPEnablePush).(bool),
EnablePushWhitelist: enablePushWhitelist,
PushWhitelistUsernames: pushWhitelistUsernames,
PushWhitelistTeams: pushWhitelistTeams,
PushWhitelistDeployKeys: d.Get(repoBPPushWhitelistDeployKeys).(bool),
EnableMergeWhitelist: enableMergeWhitelist,
MergeWhitelistUsernames: mergeWhitelistUsernames,
MergeWhitelistTeams: mergeWhitelistTeams,
EnableStatusCheck: enableStatusCheck,
StatusCheckContexts: statusCheckContexts,
RequiredApprovals: int64(d.Get(repoBPRequiredApprovals).(int)),
EnableApprovalsWhitelist: enableApprovalsWhitelist,
ApprovalsWhitelistUsernames: approvalsWhitelistUsernames,
ApprovalsWhitelistTeams: approvalsWhitelistTeams,
BlockOnRejectedReviews: d.Get(repoBPBlockMergeOnRejectedReviews).(bool),
BlockOnOfficialReviewRequests: d.Get(repoBPBlockMergeOnOfficialReviewRequests).(bool),
BlockOnOutdatedBranch: d.Get(repoBPBlockMergeOnOutdatedBranch).(bool),
DismissStaleApprovals: d.Get(repoBPDismissStaleApprovals).(bool),
// IgnoreStaleApprovals: d.Get(repoBPIgnoreStaleApprovals).(bool),
RequireSignedCommits: d.Get(repoBPRequireSignedCommits).(bool),
ProtectedFilePatterns: d.Get(repoBPProtectedFilePatterns).(string),
UnprotectedFilePatterns: d.Get(repoBPUnprotectedFilePatterns).(string),
}
bp, _, err := client.CreateBranchProtection(user, repo, bpOption)
if err != nil {
return err
}
err = setRepositoryBranchProtectionData(bp, user, repo, d)
return err
}
func resourceRepositoryBranchProtectionUpdate(d *schema.ResourceData, meta interface{}) (err error) {
client := meta.(*gitea.Client)
user := d.Get(repoBPUsername).(string)
repo := d.Get(repoBPName).(string)
rule_name := d.Id()
enablePushWhitelist, pushWhitelistUsernames, pushWhitelistTeams := generateWhitelist(d, "push_whitelist")
enableMergeWhitelist, mergeWhitelistUsernames, mergeWhitelistTeams := generateWhitelist(d, "merge_whitelist")
enableApprovalsWhitelist, approvalsWhitelistUsernames, approvalsWhitelistTeams := generateWhitelist(d, "approval_whitelist")
statusCheckContexts := make([]string, 0)
for _, element := range d.Get(repoBPStatusCheckPatterns).([]interface{}) {
statusCheckContexts = append(statusCheckContexts, element.(string))
}
enablePush := false
if enablePushWhitelist == true || d.Get(repoBPEnablePush).(bool) == true {
enablePush = true
}
pushWhitelistDeployKeys := d.Get(repoBPPushWhitelistDeployKeys).(bool)
enableStatusCheck := false
if len(statusCheckContexts) > 0 {
enableStatusCheck = true
}
requiredApprovals := int64(d.Get(repoBPRequiredApprovals).(int))
blockOnRejectedReviews := d.Get(repoBPBlockMergeOnRejectedReviews).(bool)
blockOnOfficialReviewRequests := d.Get(repoBPBlockMergeOnOfficialReviewRequests).(bool)
blockOnOutdatedBranch := d.Get(repoBPBlockMergeOnOutdatedBranch).(bool)
dismissStaleApprovals := d.Get(repoBPDismissStaleApprovals).(bool)
// ignoreStaleApprovals := d.Get(repoBPIgnoreStaleApprovals).(bool)
requireSignedCommits := d.Get(repoBPRequireSignedCommits).(bool)
protectedFilePatterns := d.Get(repoBPProtectedFilePatterns).(string)
unprotectedFilePatterns := d.Get(repoBPUnprotectedFilePatterns).(string)
bpOption := gitea.EditBranchProtectionOption{
EnablePush: &enablePush,
EnablePushWhitelist: &enablePushWhitelist,
PushWhitelistUsernames: pushWhitelistUsernames,
PushWhitelistTeams: pushWhitelistTeams,
PushWhitelistDeployKeys: &pushWhitelistDeployKeys,
EnableMergeWhitelist: &enableMergeWhitelist,
MergeWhitelistUsernames: mergeWhitelistUsernames,
MergeWhitelistTeams: mergeWhitelistTeams,
EnableStatusCheck: &enableStatusCheck,
StatusCheckContexts: statusCheckContexts,
RequiredApprovals: &requiredApprovals,
EnableApprovalsWhitelist: &enableApprovalsWhitelist,
ApprovalsWhitelistUsernames: approvalsWhitelistUsernames,
ApprovalsWhitelistTeams: approvalsWhitelistTeams,
BlockOnRejectedReviews: &blockOnRejectedReviews,
BlockOnOfficialReviewRequests: &blockOnOfficialReviewRequests,
BlockOnOutdatedBranch: &blockOnOutdatedBranch,
DismissStaleApprovals: &dismissStaleApprovals,
// IgnoreStaleApprovals: &ignoreStaleApprovals,
RequireSignedCommits: &requireSignedCommits,
ProtectedFilePatterns: &protectedFilePatterns,
UnprotectedFilePatterns: &unprotectedFilePatterns,
}
bp, _, err := client.EditBranchProtection(user, repo, rule_name, bpOption)
if err != nil {
return err
}
err = setRepositoryBranchProtectionData(bp, user, repo, d)
return err
}
func resourceRepositoryBranchProtectionDelete(d *schema.ResourceData, meta interface{}) (err error) {
client := meta.(*gitea.Client)
user := d.Get(repoBPUsername).(string)
repo := d.Get(repoBPName).(string)
rule_name := d.Id()
_, err = client.DeleteBranchProtection(user, repo, rule_name)
if err != nil {
return err
}
return err
}
func setRepositoryBranchProtectionData(bp *gitea.BranchProtection, user string, repo string, d *schema.ResourceData) (err error) {
d.SetId(bp.RuleName)
d.Set(repoBPUsername, user)
d.Set(repoBPName, repo)
d.Set(repoBPProtectedFilePatterns, bp.ProtectedFilePatterns)
d.Set(repoBPUnprotectedFilePatterns, bp.UnprotectedFilePatterns)
d.Set(repoBPEnablePush, bp.EnablePush)
d.Set(repoBPEnablePushWhitelist, bp.EnablePushWhitelist)
d.Set(repoBPPushWhitelistUsers, bp.PushWhitelistUsernames)
d.Set(repoBPPushWhitelistTeams, bp.PushWhitelistTeams)
d.Set(repoBPPushWhitelistDeployKeys, bp.PushWhitelistDeployKeys)
d.Set(repoBPRequireSignedCommits, bp.RequireSignedCommits)
d.Set(repoBPRequiredApprovals, bp.RequiredApprovals)
d.Set(repoBPEnableApprovalWhitelist, bp.EnableApprovalsWhitelist)
d.Set(repoBPApprovalWhitelistUsers, bp.ApprovalsWhitelistUsernames)
d.Set(repoBPApprovalWhitelistTeams, bp.ApprovalsWhitelistTeams)
d.Set(repoBPDismissStaleApprovals, bp.DismissStaleApprovals)
// d.Set(repoBPIgnoreStaleApprovals, bp.IgnoreStaleApprovals)
d.Set(repoBPEnableStatusCheck, bp.EnableStatusCheck)
d.Set(repoBPStatusCheckPatterns, bp.StatusCheckContexts)
d.Set(repoBPEnableMergeWhitelist, bp.EnableMergeWhitelist)
d.Set(repoBPMergeWhitelistUsers, bp.MergeWhitelistUsernames)
d.Set(repoBPMergeWhitelistTeams, bp.MergeWhitelistTeams)
d.Set(repoBPBlockMergeOnRejectedReviews, bp.BlockOnRejectedReviews)
d.Set(repoBPBlockMergeOnOfficialReviewRequests, bp.BlockOnOfficialReviewRequests)
d.Set(repoBPBlockMergeOnOutdatedBranch, bp.BlockOnOutdatedBranch)
d.Set(repoBPUpdatedAt, bp.Updated)
d.Set(repoBPCreatedAt, bp.Created)
return err
}
func resourceGiteaRepositoryBranchProtection() *schema.Resource {
return &schema.Resource{
Read: resourceRepositoryBranchProtectionRead,
Create: resourceRepositoryBranchProtectionCreate,
Update: resourceRepositoryBranchProtectionUpdate,
Delete: resourceRepositoryBranchProtectionDelete,
// TODO: importer ?
Schema: map[string]*schema.Schema{
"username": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "User name or organization name",
},
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Repository name",
},
"rule_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Protected Branch Name Pattern",
},
"protected_file_patterns": {
Type: schema.TypeString,
Optional: true,
ForceNew: false,
Default: "",
Description: "Protected file patterns (separated using semicolon ';')",
},
"unprotected_file_patterns": {
Type: schema.TypeString,
Optional: true,
ForceNew: false,
Default: "",
Description: "Unprotected file patterns (separated using semicolon ';')",
},
"enable_push": {
Type: schema.TypeBool,
Optional: true,
ForceNew: false,
Default: false,
Description: `Anyone with write access will be allowed to push to this branch
(but not force push), add a whitelist users or teams to limit
access.`,
},
"enable_push_whitelist": {
Type: schema.TypeBool,
Computed: true,
Description: "True if a push whitelist is used.",
},
"push_whitelist_users": {
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
RequiredWith: []string{"enable_push"},
Optional: true,
ForceNew: false,
Description: "Allowlisted users for pushing. Requires enable_push to be set to true.",
},
"push_whitelist_teams": {
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
RequiredWith: []string{"enable_push"},
Optional: true,
ForceNew: false,
Description: "Allowlisted teams for pushing. Requires enable_push to be set to true.",
},
"push_whitelist_deploy_keys": {
Type: schema.TypeBool,
RequiredWith: []string{"enable_push"},
Optional: true,
ForceNew: false,
Default: false,
Description: "Allow deploy keys with write access to push. Requires enable_push to be set to true.",
},
"require_signed_commits": {
Type: schema.TypeBool,
Optional: true,
ForceNew: false,
Default: false,
Description: "Reject pushes to this branch if they are unsigned or unverifiable.",
},
"required_approvals": {
Type: schema.TypeInt,
Optional: true,
ForceNew: false,
Default: 0,
Description: "Allow only to merge pull request with enough positive reviews.",
},
"enable_approval_whitelist": {
Type: schema.TypeBool,
Computed: true,
Description: "True if a approval whitelist is used.",
},
"approval_whitelist_users": {
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Optional: true,
ForceNew: false,
Description: `Only reviews from allowlisted users will count to the required
approvals. Without approval allowlist, reviews from anyone with
write access count to the required approvals.`,
},
"approval_whitelist_teams": {
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Optional: true,
ForceNew: false,
Description: `Only reviews from allowlisted teams will count to the required
approvals. Without approval allowlist, reviews from anyone with
write access count to the required approvals.`,
},
"dismiss_stale_approvals": {
Type: schema.TypeBool,
Optional: true,
ForceNew: false,
Default: false,
Description: `When new commits that change the content of the pull request
are pushed to the branch, old approvals will be dismissed.`,
},
//
// not implemented in go-gitea-sdk
//
// "ignore_stale_approvals": {
// Type: schema.TypeBool,
// Optional true,
// ForceNew: false,
// Default: false,
// Description: `Do not count approvals that were made on older commits (stale
// reviews) towards how many approvals the PR has. Irrelevant if
// stale reviews are already dismissed.`,
// },
"enable_status_check": {
Type: schema.TypeBool,
Computed: true,
Description: `Require status checks to pass before merging. When enabled,
commits must first be pushed to another branch, then merged
or pushed directly to a branch that matches this rule after
status checks have passed. If no contexts are matched, the
last commit must be successful regardless of context`,
},
"status_check_patterns": {
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Optional: true,
ForceNew: false,
Description: `Enter patterns to specify which status checks must pass before
branches can be merged into a branch that matches this rule.
Each line specifies a pattern. Patterns cannot be empty.`,
},
"enable_merge_whitelist": {
Type: schema.TypeBool,
Computed: true,
Description: "True if a merge whitelist is used.",
},
"merge_whitelist_users": {
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Optional: true,
ForceNew: false,
Description: "Allow only allowlisted users to merge pull requests into this branch.",
},
"merge_whitelist_teams": {
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Optional: true,
ForceNew: false,
Description: "Allow only allowlisted teams to merge pull requests into this branch.",
},
"block_merge_on_rejected_reviews": {
Type: schema.TypeBool,
Optional: true,
ForceNew: false,
Default: false,
Description: `Merging will not be possible when changes are
requested by official reviewers, even if there are enough
approvals.`,
},
"block_merge_on_official_review_requests": {
Type: schema.TypeBool,
Optional: true,
ForceNew: false,
Default: false,
Description: `Merging will not be possible when it has official
review requests, even if there are enough approvals.`,
},
"block_merge_on_outdated_branch": {
Type: schema.TypeBool,
Optional: true,
ForceNew: false,
Default: false,
Description: "Merging will not be possible when head branch is behind base branch.",
},
"updated_at": {
Type: schema.TypeString,
Computed: true,
Description: "Webhook creation timestamp",
},
"created_at": {
Type: schema.TypeString,
Computed: true,
Description: "Webhook creation timestamp",
},
},
Description: "This resource allows you to create and manage branch protections for repositories.",
}
}