1// 2// Copyright 2021, Sander van Harmelen 3// 4// Licensed under the Apache License, Version 2.0 (the "License"); 5// you may not use this file except in compliance with the License. 6// You may obtain a copy of the License at 7// 8// http://www.apache.org/licenses/LICENSE-2.0 9// 10// Unless required by applicable law or agreed to in writing, software 11// distributed under the License is distributed on an "AS IS" BASIS, 12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13// See the License for the specific language governing permissions and 14// limitations under the License. 15// 16 17package gitlab 18 19import ( 20 "fmt" 21 "net/http" 22 "time" 23) 24 25// MergeRequestApprovalsService handles communication with the merge request 26// approvals related methods of the GitLab API. This includes reading/updating 27// approval settings and approve/unapproving merge requests 28// 29// GitLab API docs: https://docs.gitlab.com/ee/api/merge_request_approvals.html 30type MergeRequestApprovalsService struct { 31 client *Client 32} 33 34// MergeRequestApprovals represents GitLab merge request approvals. 35// 36// GitLab API docs: 37// https://docs.gitlab.com/ee/api/merge_request_approvals.html#merge-request-level-mr-approvals 38type MergeRequestApprovals struct { 39 ID int `json:"id"` 40 IID int `json:"iid"` 41 ProjectID int `json:"project_id"` 42 Title string `json:"title"` 43 Description string `json:"description"` 44 State string `json:"state"` 45 CreatedAt *time.Time `json:"created_at"` 46 UpdatedAt *time.Time `json:"updated_at"` 47 MergeStatus string `json:"merge_status"` 48 Approved bool `json:"approved"` 49 ApprovalsBeforeMerge int `json:"approvals_before_merge"` 50 ApprovalsRequired int `json:"approvals_required"` 51 ApprovalsLeft int `json:"approvals_left"` 52 RequirePasswordToApprove bool `json:"require_password_to_approve"` 53 ApprovedBy []*MergeRequestApproverUser `json:"approved_by"` 54 SuggestedApprovers []*BasicUser `json:"suggested_approvers"` 55 Approvers []*MergeRequestApproverUser `json:"approvers"` 56 ApproverGroups []*MergeRequestApproverGroup `json:"approver_groups"` 57 UserHasApproved bool `json:"user_has_approved"` 58 UserCanApprove bool `json:"user_can_approve"` 59 ApprovalRulesLeft []*MergeRequestApprovalRule `json:"approval_rules_left"` 60 HasApprovalRules bool `json:"has_approval_rules"` 61 MergeRequestApproversAvailable bool `json:"merge_request_approvers_available"` 62 MultipleApprovalRulesAvailable bool `json:"multiple_approval_rules_available"` 63} 64 65func (m MergeRequestApprovals) String() string { 66 return Stringify(m) 67} 68 69// MergeRequestApproverGroup represents GitLab project level merge request approver group. 70// 71// GitLab API docs: 72// https://docs.gitlab.com/ee/api/merge_request_approvals.html#project-level-mr-approvals 73type MergeRequestApproverGroup struct { 74 Group struct { 75 ID int `json:"id"` 76 Name string `json:"name"` 77 Path string `json:"path"` 78 Description string `json:"description"` 79 Visibility string `json:"visibility"` 80 AvatarURL string `json:"avatar_url"` 81 WebURL string `json:"web_url"` 82 FullName string `json:"full_name"` 83 FullPath string `json:"full_path"` 84 LFSEnabled bool `json:"lfs_enabled"` 85 RequestAccessEnabled bool `json:"request_access_enabled"` 86 } 87} 88 89// MergeRequestApprovalRule represents a GitLab merge request approval rule. 90// 91// GitLab API docs: 92// https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-merge-request-level-rules 93type MergeRequestApprovalRule struct { 94 ID int `json:"id"` 95 Name string `json:"name"` 96 RuleType string `json:"rule_type"` 97 EligibleApprovers []*BasicUser `json:"eligible_approvers"` 98 ApprovalsRequired int `json:"approvals_required"` 99 SourceRule *ProjectApprovalRule `json:"source_rule"` 100 Users []*BasicUser `json:"users"` 101 Groups []*Group `json:"groups"` 102 ContainsHiddenGroups bool `json:"contains_hidden_groups"` 103 Section string `json:"section"` 104 ApprovedBy []*BasicUser `json:"approved_by"` 105 Approved bool `json:"approved"` 106} 107 108// MergeRequestApprovalState represents a GitLab merge request approval state. 109// 110// GitLab API docs: 111// https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-the-approval-state-of-merge-requests 112type MergeRequestApprovalState struct { 113 ApprovalRulesOverwritten bool `json:"approval_rules_overwritten"` 114 Rules []*MergeRequestApprovalRule `json:"rules"` 115} 116 117// String is a stringify for MergeRequestApprovalRule 118func (s MergeRequestApprovalRule) String() string { 119 return Stringify(s) 120} 121 122// MergeRequestApproverUser represents GitLab project level merge request approver user. 123// 124// GitLab API docs: 125// https://docs.gitlab.com/ee/api/merge_request_approvals.html#project-level-mr-approvals 126type MergeRequestApproverUser struct { 127 User *BasicUser 128} 129 130// ApproveMergeRequestOptions represents the available ApproveMergeRequest() options. 131// 132// GitLab API docs: 133// https://docs.gitlab.com/ee/api/merge_request_approvals.html#approve-merge-request 134type ApproveMergeRequestOptions struct { 135 SHA *string `url:"sha,omitempty" json:"sha,omitempty"` 136} 137 138// ApproveMergeRequest approves a merge request on GitLab. If a non-empty sha 139// is provided then it must match the sha at the HEAD of the MR. 140// 141// GitLab API docs: 142// https://docs.gitlab.com/ee/api/merge_request_approvals.html#approve-merge-request 143func (s *MergeRequestApprovalsService) ApproveMergeRequest(pid interface{}, mr int, opt *ApproveMergeRequestOptions, options ...RequestOptionFunc) (*MergeRequestApprovals, *Response, error) { 144 project, err := parseID(pid) 145 if err != nil { 146 return nil, nil, err 147 } 148 u := fmt.Sprintf("projects/%s/merge_requests/%d/approve", PathEscape(project), mr) 149 150 req, err := s.client.NewRequest(http.MethodPost, u, opt, options) 151 if err != nil { 152 return nil, nil, err 153 } 154 155 m := new(MergeRequestApprovals) 156 resp, err := s.client.Do(req, m) 157 if err != nil { 158 return nil, resp, err 159 } 160 161 return m, resp, err 162} 163 164// UnapproveMergeRequest unapproves a previously approved merge request on GitLab. 165// 166// GitLab API docs: 167// https://docs.gitlab.com/ee/api/merge_request_approvals.html#unapprove-merge-request 168func (s *MergeRequestApprovalsService) UnapproveMergeRequest(pid interface{}, mr int, options ...RequestOptionFunc) (*Response, error) { 169 project, err := parseID(pid) 170 if err != nil { 171 return nil, err 172 } 173 u := fmt.Sprintf("projects/%s/merge_requests/%d/unapprove", PathEscape(project), mr) 174 175 req, err := s.client.NewRequest(http.MethodPost, u, nil, options) 176 if err != nil { 177 return nil, err 178 } 179 180 return s.client.Do(req, nil) 181} 182 183// ChangeMergeRequestApprovalConfigurationOptions represents the available 184// ChangeMergeRequestApprovalConfiguration() options. 185// 186// GitLab API docs: 187// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-approval-configuration 188type ChangeMergeRequestApprovalConfigurationOptions struct { 189 ApprovalsRequired *int `url:"approvals_required,omitempty" json:"approvals_required,omitempty"` 190} 191 192// GetConfiguration shows information about single merge request approvals 193// 194// GitLab API docs: 195// https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-configuration-1 196func (s *MergeRequestApprovalsService) GetConfiguration(pid interface{}, mr int, options ...RequestOptionFunc) (*MergeRequestApprovals, *Response, error) { 197 project, err := parseID(pid) 198 if err != nil { 199 return nil, nil, err 200 } 201 u := fmt.Sprintf("projects/%s/merge_requests/%d/approvals", PathEscape(project), mr) 202 203 req, err := s.client.NewRequest(http.MethodGet, u, nil, options) 204 if err != nil { 205 return nil, nil, err 206 } 207 208 m := new(MergeRequestApprovals) 209 resp, err := s.client.Do(req, m) 210 if err != nil { 211 return nil, resp, err 212 } 213 214 return m, resp, err 215} 216 217// ChangeApprovalConfiguration updates the approval configuration of a merge request. 218// 219// GitLab API docs: 220// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-approval-configuration 221func (s *MergeRequestApprovalsService) ChangeApprovalConfiguration(pid interface{}, mergeRequest int, opt *ChangeMergeRequestApprovalConfigurationOptions, options ...RequestOptionFunc) (*MergeRequest, *Response, error) { 222 project, err := parseID(pid) 223 if err != nil { 224 return nil, nil, err 225 } 226 u := fmt.Sprintf("projects/%s/merge_requests/%d/approvals", PathEscape(project), mergeRequest) 227 228 req, err := s.client.NewRequest(http.MethodPost, u, opt, options) 229 if err != nil { 230 return nil, nil, err 231 } 232 233 m := new(MergeRequest) 234 resp, err := s.client.Do(req, m) 235 if err != nil { 236 return nil, resp, err 237 } 238 239 return m, resp, err 240} 241 242// ChangeMergeRequestAllowedApproversOptions represents the available 243// ChangeMergeRequestAllowedApprovers() options. 244// 245// GitLab API docs: 246// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-allowed-approvers-for-merge-request 247type ChangeMergeRequestAllowedApproversOptions struct { 248 ApproverIDs []int `url:"approver_ids" json:"approver_ids"` 249 ApproverGroupIDs []int `url:"approver_group_ids" json:"approver_group_ids"` 250} 251 252// ChangeAllowedApprovers updates the approvers for a merge request. 253// 254// GitLab API docs: 255// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-allowed-approvers-for-merge-request 256func (s *MergeRequestApprovalsService) ChangeAllowedApprovers(pid interface{}, mergeRequest int, opt *ChangeMergeRequestAllowedApproversOptions, options ...RequestOptionFunc) (*MergeRequest, *Response, error) { 257 project, err := parseID(pid) 258 if err != nil { 259 return nil, nil, err 260 } 261 u := fmt.Sprintf("projects/%s/merge_requests/%d/approvers", PathEscape(project), mergeRequest) 262 263 req, err := s.client.NewRequest(http.MethodPut, u, opt, options) 264 if err != nil { 265 return nil, nil, err 266 } 267 268 m := new(MergeRequest) 269 resp, err := s.client.Do(req, m) 270 if err != nil { 271 return nil, resp, err 272 } 273 274 return m, resp, err 275} 276 277// GetApprovalRules requests information about a merge request’s approval rules 278// 279// GitLab API docs: 280// https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-merge-request-level-rules 281func (s *MergeRequestApprovalsService) GetApprovalRules(pid interface{}, mergeRequest int, options ...RequestOptionFunc) ([]*MergeRequestApprovalRule, *Response, error) { 282 project, err := parseID(pid) 283 if err != nil { 284 return nil, nil, err 285 } 286 u := fmt.Sprintf("projects/%s/merge_requests/%d/approval_rules", PathEscape(project), mergeRequest) 287 288 req, err := s.client.NewRequest(http.MethodGet, u, nil, options) 289 if err != nil { 290 return nil, nil, err 291 } 292 293 var par []*MergeRequestApprovalRule 294 resp, err := s.client.Do(req, &par) 295 if err != nil { 296 return nil, resp, err 297 } 298 299 return par, resp, err 300} 301 302// GetApprovalState requests information about a merge request’s approval state 303// 304// GitLab API docs: 305// https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-the-approval-state-of-merge-requests 306func (s *MergeRequestApprovalsService) GetApprovalState(pid interface{}, mergeRequest int, options ...RequestOptionFunc) (*MergeRequestApprovalState, *Response, error) { 307 project, err := parseID(pid) 308 if err != nil { 309 return nil, nil, err 310 } 311 u := fmt.Sprintf("projects/%s/merge_requests/%d/approval_state", PathEscape(project), mergeRequest) 312 313 req, err := s.client.NewRequest(http.MethodGet, u, nil, options) 314 if err != nil { 315 return nil, nil, err 316 } 317 318 var pas *MergeRequestApprovalState 319 resp, err := s.client.Do(req, &pas) 320 if err != nil { 321 return nil, resp, err 322 } 323 324 return pas, resp, err 325} 326 327// CreateMergeRequestApprovalRuleOptions represents the available CreateApprovalRule() 328// options. 329// 330// GitLab API docs: 331// https://docs.gitlab.com/ee/api/merge_request_approvals.html#create-merge-request-level-rule 332type CreateMergeRequestApprovalRuleOptions struct { 333 Name *string `url:"name,omitempty" json:"name,omitempty"` 334 ApprovalsRequired *int `url:"approvals_required,omitempty" json:"approvals_required,omitempty"` 335 ApprovalProjectRuleID *int `url:"approval_project_rule_id,omitempty" json:"approval_project_rule_id,omitempty"` 336 UserIDs *[]int `url:"user_ids,omitempty" json:"user_ids,omitempty"` 337 GroupIDs *[]int `url:"group_ids,omitempty" json:"group_ids,omitempty"` 338} 339 340// CreateApprovalRule creates a new MR level approval rule. 341// 342// GitLab API docs: 343// https://docs.gitlab.com/ee/api/merge_request_approvals.html#create-merge-request-level-rule 344func (s *MergeRequestApprovalsService) CreateApprovalRule(pid interface{}, mergeRequest int, opt *CreateMergeRequestApprovalRuleOptions, options ...RequestOptionFunc) (*MergeRequestApprovalRule, *Response, error) { 345 project, err := parseID(pid) 346 if err != nil { 347 return nil, nil, err 348 } 349 u := fmt.Sprintf("projects/%s/merge_requests/%d/approval_rules", PathEscape(project), mergeRequest) 350 351 req, err := s.client.NewRequest(http.MethodPost, u, opt, options) 352 if err != nil { 353 return nil, nil, err 354 } 355 356 par := new(MergeRequestApprovalRule) 357 resp, err := s.client.Do(req, &par) 358 if err != nil { 359 return nil, resp, err 360 } 361 362 return par, resp, err 363} 364 365// UpdateMergeRequestApprovalRuleOptions represents the available UpdateApprovalRule() 366// options. 367// 368// GitLab API docs: 369// https://docs.gitlab.com/ee/api/merge_request_approvals.html#update-merge-request-level-rule 370type UpdateMergeRequestApprovalRuleOptions struct { 371 Name *string `url:"name,omitempty" json:"name,omitempty"` 372 ApprovalsRequired *int `url:"approvals_required,omitempty" json:"approvals_required,omitempty"` 373 UserIDs *[]int `url:"user_ids,omitempty" json:"user_ids,omitempty"` 374 GroupIDs *[]int `url:"group_ids,omitempty" json:"group_ids,omitempty"` 375} 376 377// UpdateApprovalRule updates an existing approval rule with new options. 378// 379// GitLab API docs: 380// https://docs.gitlab.com/ee/api/merge_request_approvals.html#update-merge-request-level-rule 381func (s *MergeRequestApprovalsService) UpdateApprovalRule(pid interface{}, mergeRequest int, approvalRule int, opt *UpdateMergeRequestApprovalRuleOptions, options ...RequestOptionFunc) (*MergeRequestApprovalRule, *Response, error) { 382 project, err := parseID(pid) 383 if err != nil { 384 return nil, nil, err 385 } 386 u := fmt.Sprintf("projects/%s/merge_requests/%d/approval_rules/%d", PathEscape(project), mergeRequest, approvalRule) 387 388 req, err := s.client.NewRequest(http.MethodPut, u, opt, options) 389 if err != nil { 390 return nil, nil, err 391 } 392 393 par := new(MergeRequestApprovalRule) 394 resp, err := s.client.Do(req, &par) 395 if err != nil { 396 return nil, resp, err 397 } 398 399 return par, resp, err 400} 401 402// DeleteApprovalRule deletes a mr level approval rule. 403// 404// GitLab API docs: 405// https://docs.gitlab.com/ee/api/merge_request_approvals.html#delete-merge-request-level-rule 406func (s *MergeRequestApprovalsService) DeleteApprovalRule(pid interface{}, mergeRequest int, approvalRule int, options ...RequestOptionFunc) (*Response, error) { 407 project, err := parseID(pid) 408 if err != nil { 409 return nil, err 410 } 411 u := fmt.Sprintf("projects/%s/merge_requests/%d/approval_rules/%d", PathEscape(project), mergeRequest, approvalRule) 412 413 req, err := s.client.NewRequest(http.MethodDelete, u, nil, options) 414 if err != nil { 415 return nil, err 416 } 417 418 return s.client.Do(req, nil) 419} 420