mirror of
https://github.com/ysoftdevs/terraform-provider-bitbucketserver.git
synced 2026-03-17 23:13:47 +01:00
419 lines
10 KiB
Go
419 lines
10 KiB
Go
package bitbucket
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
bitbucketTypes "github.com/xvlcwk-terraform/terraform-provider-bitbucketserver/bitbucket/util/types"
|
|
"io/ioutil"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
|
)
|
|
|
|
type Reviewer struct {
|
|
ID int `json:"id,omitempty"`
|
|
}
|
|
|
|
type MatcherType struct {
|
|
ID string `json:"id,omitempty"`
|
|
}
|
|
|
|
type Matcher struct {
|
|
ID string `json:"id,omitempty"`
|
|
Type MatcherType `json:"type,omitempty"`
|
|
}
|
|
|
|
type RefMatcher struct {
|
|
ID string `json:"id,omitempty"`
|
|
Type struct {
|
|
ID string `json:"id,omitempty"`
|
|
} `json:"type,omitempty"`
|
|
}
|
|
|
|
type DefaultReviewersConditionPayload struct {
|
|
SourceMatcher Matcher `json:"sourceMatcher,omitempty"`
|
|
TargetMatcher Matcher `json:"targetMatcher,omitempty"`
|
|
Reviewers []Reviewer `json:"reviewers,omitempty"`
|
|
RequiredApprovals string `json:"requiredApprovals,omitempty"`
|
|
}
|
|
|
|
type DefaultReviewersConditionResp struct {
|
|
ID int `json:"id,omitempty"`
|
|
RequiredApprovals int `json:"requiredApprovals,omitempty"`
|
|
Reviewers []Reviewer `json:"reviewers,omitempty"`
|
|
SourceRefMatcher RefMatcher `json:"sourceRefMatcher,omitempty"`
|
|
TargetRefMatcher RefMatcher `json:"targetRefMatcher,omitempty"`
|
|
}
|
|
|
|
var matcherDesc = `id can be either "any" to match all branches, "refs/heads/master" to match certain branch, "pattern" to match multiple branches or "development" to match branching model. type_id must be one of: "ANY_REF", "BRANCH", "PATTERN", "MODEL_BRANCH".`
|
|
|
|
var validMatcherTypeIDs = []string{
|
|
"ANY_REF", "BRANCH", "PATTERN", "MODEL_BRANCH",
|
|
}
|
|
|
|
func resourceDefaultReviewersCondition() *schema.Resource {
|
|
return &schema.Resource{
|
|
Create: resourceDefaultReviewersConditionCreate,
|
|
Read: resourceDefaultReviewersConditionRead,
|
|
Exists: resourceDefaultReviewersConditionExists,
|
|
Delete: resourceDefaultReviewersConditionDelete,
|
|
Importer: &schema.ResourceImporter{
|
|
State: schema.ImportStatePassthrough,
|
|
},
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
"project_key": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
"repository_slug": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
"source_matcher": {
|
|
Type: schema.TypeMap,
|
|
Elem: &schema.Schema{
|
|
Type: schema.TypeString,
|
|
},
|
|
Required: true,
|
|
ForceNew: true,
|
|
Description: matcherDesc,
|
|
},
|
|
"target_matcher": {
|
|
Type: schema.TypeMap,
|
|
Elem: &schema.Schema{
|
|
Type: schema.TypeString,
|
|
},
|
|
Required: true,
|
|
ForceNew: true,
|
|
Description: matcherDesc,
|
|
},
|
|
"reviewers": {
|
|
Type: schema.TypeSet,
|
|
Elem: &schema.Schema{Type: schema.TypeInt},
|
|
Required: true,
|
|
Set: schema.HashInt,
|
|
ForceNew: true,
|
|
MinItems: 1,
|
|
Description: "IDs of users to become default reviewers when you create a pull request.",
|
|
},
|
|
"required_approvals": {
|
|
Type: schema.TypeInt,
|
|
Required: true,
|
|
ForceNew: true,
|
|
Description: "The number of default reviewers that must approve a pull request.",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func refMatcherToMatcher(refMatcher RefMatcher) Matcher {
|
|
convertID := func(id string) string {
|
|
if id == "ANY_REF_MATCHER_ID" {
|
|
return "any"
|
|
}
|
|
|
|
return id
|
|
}
|
|
|
|
return Matcher{
|
|
ID: convertID(refMatcher.ID),
|
|
Type: MatcherType{
|
|
ID: refMatcher.Type.ID,
|
|
},
|
|
}
|
|
}
|
|
|
|
func expandReviewers(set *schema.Set) []Reviewer {
|
|
list := set.List()
|
|
|
|
rs := make([]Reviewer, 0, len(list))
|
|
|
|
for _, v := range list {
|
|
rs = append(rs, Reviewer{ID: v.(int)})
|
|
}
|
|
|
|
return rs
|
|
}
|
|
|
|
func collapseReviewers(reviewers []Reviewer) *schema.Set {
|
|
reviewerIDs := make([]interface{}, 0)
|
|
|
|
for _, r := range reviewers {
|
|
reviewerIDs = append(reviewerIDs, r.ID)
|
|
}
|
|
|
|
return schema.NewSet(schema.HashInt, reviewerIDs)
|
|
}
|
|
|
|
func expandMatcher(matcherMap map[string]interface{}) Matcher {
|
|
return Matcher{
|
|
ID: matcherMap["id"].(string),
|
|
Type: MatcherType{
|
|
ID: matcherMap["type_id"].(string),
|
|
},
|
|
}
|
|
}
|
|
|
|
func collapseMatcher(matcher Matcher) map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"id": matcher.ID,
|
|
"type_id": matcher.Type.ID,
|
|
}
|
|
}
|
|
|
|
func parseResourceID(id string) (string, string, string, error) {
|
|
parts := strings.Split(id, ":")
|
|
|
|
errMsg := fmt.Errorf("invalid format of ID (%s), expected condition_id:project_key or condition_id:project_key:repository_slug", id)
|
|
|
|
if len(parts) != 2 && len(parts) != 3 {
|
|
return "", "", "", errMsg
|
|
}
|
|
|
|
if len(parts) == 2 {
|
|
if parts[0] == "" || parts[1] == "" {
|
|
return "", "", "", errMsg
|
|
}
|
|
|
|
return parts[0], parts[1], "", nil
|
|
}
|
|
|
|
if parts[0] == "" || parts[1] == "" || parts[2] == "" {
|
|
return "", "", "", errMsg
|
|
}
|
|
|
|
return parts[0], parts[1], parts[2], nil
|
|
}
|
|
|
|
func createResourceID(conditionID int, projectKey string, repositorySlug string) string {
|
|
if repositorySlug == "" {
|
|
return fmt.Sprintf("%v:%s", conditionID, projectKey)
|
|
}
|
|
|
|
return fmt.Sprintf("%v:%s:%s", conditionID, projectKey, repositorySlug)
|
|
}
|
|
|
|
func getCreateConditionURI(projectKey string, repositorySlug string) string {
|
|
if repositorySlug == "" {
|
|
return fmt.Sprintf("/rest/default-reviewers/1.0/projects/%s/condition",
|
|
url.PathEscape(projectKey),
|
|
)
|
|
}
|
|
|
|
return fmt.Sprintf("/rest/default-reviewers/1.0/projects/%s/repos/%s/condition",
|
|
url.PathEscape(projectKey),
|
|
url.PathEscape(repositorySlug),
|
|
)
|
|
}
|
|
|
|
func getReadConditionURI(projectKey string, repositorySlug string) string {
|
|
if repositorySlug == "" {
|
|
return fmt.Sprintf("/rest/default-reviewers/1.0/projects/%s/conditions",
|
|
url.PathEscape(projectKey),
|
|
)
|
|
}
|
|
|
|
return fmt.Sprintf("/rest/default-reviewers/1.0/projects/%s/repos/%s/conditions",
|
|
url.PathEscape(projectKey),
|
|
url.PathEscape(repositorySlug),
|
|
)
|
|
}
|
|
|
|
func getDeleteConditionURI(conditionID string, projectKey string, repositorySlug string) string {
|
|
if repositorySlug == "" {
|
|
return fmt.Sprintf("/rest/default-reviewers/1.0/projects/%s/condition/%s",
|
|
url.PathEscape(projectKey),
|
|
url.PathEscape(conditionID),
|
|
)
|
|
}
|
|
|
|
return fmt.Sprintf("/rest/default-reviewers/1.0/projects/%s/repos/%s/condition/%s",
|
|
url.PathEscape(projectKey),
|
|
url.PathEscape(repositorySlug),
|
|
url.PathEscape(conditionID),
|
|
)
|
|
}
|
|
|
|
func contains(s []string, e string) bool {
|
|
for _, a := range s {
|
|
if a == e {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func selectConditionByID(conditions []DefaultReviewersConditionResp, conditionID string) *DefaultReviewersConditionResp {
|
|
for _, c := range conditions {
|
|
cID := strconv.Itoa(c.ID)
|
|
|
|
if cID == conditionID {
|
|
return &c
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func resourceDefaultReviewersConditionCreate(d *schema.ResourceData, m interface{}) error {
|
|
projectKey := d.Get("project_key").(string)
|
|
repositorySlug := d.Get("repository_slug").(string)
|
|
|
|
sourceMatcher := expandMatcher(d.Get("source_matcher").(map[string]interface{}))
|
|
targetMatcher := expandMatcher(d.Get("target_matcher").(map[string]interface{}))
|
|
reviewers := expandReviewers(d.Get("reviewers").(*schema.Set))
|
|
requiredApprovals := d.Get("required_approvals").(int)
|
|
|
|
if !contains(validMatcherTypeIDs, sourceMatcher.Type.ID) {
|
|
return fmt.Errorf("source_matcher.type_id %s must be one of %v", sourceMatcher.Type.ID, validMatcherTypeIDs)
|
|
}
|
|
|
|
if !contains(validMatcherTypeIDs, targetMatcher.Type.ID) {
|
|
return fmt.Errorf("target_matcher.type_id %s must be one of %v", targetMatcher.Type.ID, validMatcherTypeIDs)
|
|
}
|
|
|
|
if requiredApprovals > len(reviewers) {
|
|
return fmt.Errorf("required_approvals %d cannot be more than length of reviewers %d", requiredApprovals, len(reviewers))
|
|
}
|
|
|
|
bytedata, err := json.Marshal(&DefaultReviewersConditionPayload{
|
|
SourceMatcher: sourceMatcher,
|
|
TargetMatcher: targetMatcher,
|
|
Reviewers: reviewers,
|
|
RequiredApprovals: strconv.Itoa(requiredApprovals),
|
|
})
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
client := m.(*bitbucketTypes.BitbucketServerProvider).BitbucketClient
|
|
|
|
resp, err := client.Post(getCreateConditionURI(projectKey, repositorySlug), bytes.NewBuffer(bytedata))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var newCondition DefaultReviewersConditionResp
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = json.Unmarshal(body, &newCondition)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.SetId(createResourceID(newCondition.ID, projectKey, repositorySlug))
|
|
|
|
return resourceDefaultReviewersConditionRead(d, m)
|
|
}
|
|
|
|
func resourceDefaultReviewersConditionRead(d *schema.ResourceData, m interface{}) error {
|
|
conditionID, projectKey, repositorySlug, err := parseResourceID(d.Id())
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
client := m.(*bitbucketTypes.BitbucketServerProvider).BitbucketClient
|
|
|
|
resp, err := client.Get(getReadConditionURI(projectKey, repositorySlug))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if resp.StatusCode == 200 {
|
|
var conditions []DefaultReviewersConditionResp
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = json.Unmarshal(body, &conditions)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
condition := selectConditionByID(conditions, conditionID)
|
|
|
|
if condition != nil {
|
|
d.Set("project_key", projectKey)
|
|
d.Set("repository_slug", repositorySlug)
|
|
d.Set("source_matcher", collapseMatcher(refMatcherToMatcher(condition.SourceRefMatcher)))
|
|
d.Set("target_matcher", collapseMatcher(refMatcherToMatcher(condition.TargetRefMatcher)))
|
|
d.Set("reviewers", collapseReviewers(condition.Reviewers))
|
|
d.Set("required_approvals", condition.RequiredApprovals)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func resourceDefaultReviewersConditionExists(d *schema.ResourceData, m interface{}) (bool, error) {
|
|
conditionID, projectKey, repositorySlug, err := parseResourceID(d.Id())
|
|
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
client := m.(*bitbucketTypes.BitbucketServerProvider).BitbucketClient
|
|
|
|
resp, err := client.Get(getReadConditionURI(projectKey, repositorySlug))
|
|
|
|
if resp != nil && resp.StatusCode == 404 {
|
|
return false, nil
|
|
}
|
|
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
var conditions []DefaultReviewersConditionResp
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
err = json.Unmarshal(body, &conditions)
|
|
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
condition := selectConditionByID(conditions, conditionID)
|
|
|
|
return condition != nil, nil
|
|
}
|
|
|
|
func resourceDefaultReviewersConditionDelete(d *schema.ResourceData, m interface{}) error {
|
|
conditionID, projectKey, repositorySlug, err := parseResourceID(d.Id())
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
client := m.(*bitbucketTypes.BitbucketServerProvider).BitbucketClient
|
|
|
|
_, err = client.Delete(getDeleteConditionURI(conditionID, projectKey, repositorySlug))
|
|
|
|
return err
|
|
}
|