gitlab.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887
  1. //
  2. // Copyright 2017, 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. package gitlab
  17. import (
  18. "bytes"
  19. "context"
  20. "encoding/json"
  21. "errors"
  22. "fmt"
  23. "io"
  24. "io/ioutil"
  25. "net/http"
  26. "net/url"
  27. "sort"
  28. "strconv"
  29. "strings"
  30. "time"
  31. "github.com/google/go-querystring/query"
  32. "golang.org/x/oauth2"
  33. )
  34. const (
  35. defaultBaseURL = "https://gitlab.com/"
  36. apiVersionPath = "api/v4/"
  37. userAgent = "go-gitlab"
  38. )
  39. // authType represents an authentication type within GitLab.
  40. //
  41. // GitLab API docs: https://docs.gitlab.com/ce/api/
  42. type authType int
  43. // List of available authentication types.
  44. //
  45. // GitLab API docs: https://docs.gitlab.com/ce/api/
  46. const (
  47. basicAuth authType = iota
  48. oAuthToken
  49. privateToken
  50. )
  51. // AccessLevelValue represents a permission level within GitLab.
  52. //
  53. // GitLab API docs: https://docs.gitlab.com/ce/permissions/permissions.html
  54. type AccessLevelValue int
  55. // List of available access levels
  56. //
  57. // GitLab API docs: https://docs.gitlab.com/ce/permissions/permissions.html
  58. const (
  59. NoPermissions AccessLevelValue = 0
  60. GuestPermissions AccessLevelValue = 10
  61. ReporterPermissions AccessLevelValue = 20
  62. DeveloperPermissions AccessLevelValue = 30
  63. MaintainerPermissions AccessLevelValue = 40
  64. OwnerPermissions AccessLevelValue = 50
  65. // These are deprecated and should be removed in a future version
  66. MasterPermissions AccessLevelValue = 40
  67. OwnerPermission AccessLevelValue = 50
  68. )
  69. // BuildStateValue represents a GitLab build state.
  70. type BuildStateValue string
  71. // These constants represent all valid build states.
  72. const (
  73. Pending BuildStateValue = "pending"
  74. Running BuildStateValue = "running"
  75. Success BuildStateValue = "success"
  76. Failed BuildStateValue = "failed"
  77. Canceled BuildStateValue = "canceled"
  78. Skipped BuildStateValue = "skipped"
  79. )
  80. // ISOTime represents an ISO 8601 formatted date
  81. type ISOTime time.Time
  82. // ISO 8601 date format
  83. const iso8601 = "2006-01-02"
  84. // MarshalJSON implements the json.Marshaler interface
  85. func (t ISOTime) MarshalJSON() ([]byte, error) {
  86. if y := time.Time(t).Year(); y < 0 || y >= 10000 {
  87. // ISO 8901 uses 4 digits for the years
  88. return nil, errors.New("ISOTime.MarshalJSON: year outside of range [0,9999]")
  89. }
  90. b := make([]byte, 0, len(iso8601)+2)
  91. b = append(b, '"')
  92. b = time.Time(t).AppendFormat(b, iso8601)
  93. b = append(b, '"')
  94. return b, nil
  95. }
  96. // UnmarshalJSON implements the json.Unmarshaler interface
  97. func (t *ISOTime) UnmarshalJSON(data []byte) error {
  98. // Ignore null, like in the main JSON package
  99. if string(data) == "null" {
  100. return nil
  101. }
  102. isotime, err := time.Parse(`"`+iso8601+`"`, string(data))
  103. *t = ISOTime(isotime)
  104. return err
  105. }
  106. // String implements the Stringer interface
  107. func (t ISOTime) String() string {
  108. return time.Time(t).Format(iso8601)
  109. }
  110. // NotificationLevelValue represents a notification level.
  111. type NotificationLevelValue int
  112. // String implements the fmt.Stringer interface.
  113. func (l NotificationLevelValue) String() string {
  114. return notificationLevelNames[l]
  115. }
  116. // MarshalJSON implements the json.Marshaler interface.
  117. func (l NotificationLevelValue) MarshalJSON() ([]byte, error) {
  118. return json.Marshal(l.String())
  119. }
  120. // UnmarshalJSON implements the json.Unmarshaler interface.
  121. func (l *NotificationLevelValue) UnmarshalJSON(data []byte) error {
  122. var raw interface{}
  123. if err := json.Unmarshal(data, &raw); err != nil {
  124. return err
  125. }
  126. switch raw := raw.(type) {
  127. case float64:
  128. *l = NotificationLevelValue(raw)
  129. case string:
  130. *l = notificationLevelTypes[raw]
  131. case nil:
  132. // No action needed.
  133. default:
  134. return fmt.Errorf("json: cannot unmarshal %T into Go value of type %T", raw, *l)
  135. }
  136. return nil
  137. }
  138. // List of valid notification levels.
  139. const (
  140. DisabledNotificationLevel NotificationLevelValue = iota
  141. ParticipatingNotificationLevel
  142. WatchNotificationLevel
  143. GlobalNotificationLevel
  144. MentionNotificationLevel
  145. CustomNotificationLevel
  146. )
  147. var notificationLevelNames = [...]string{
  148. "disabled",
  149. "participating",
  150. "watch",
  151. "global",
  152. "mention",
  153. "custom",
  154. }
  155. var notificationLevelTypes = map[string]NotificationLevelValue{
  156. "disabled": DisabledNotificationLevel,
  157. "participating": ParticipatingNotificationLevel,
  158. "watch": WatchNotificationLevel,
  159. "global": GlobalNotificationLevel,
  160. "mention": MentionNotificationLevel,
  161. "custom": CustomNotificationLevel,
  162. }
  163. // VisibilityValue represents a visibility level within GitLab.
  164. //
  165. // GitLab API docs: https://docs.gitlab.com/ce/api/
  166. type VisibilityValue string
  167. // List of available visibility levels
  168. //
  169. // GitLab API docs: https://docs.gitlab.com/ce/api/
  170. const (
  171. PrivateVisibility VisibilityValue = "private"
  172. InternalVisibility VisibilityValue = "internal"
  173. PublicVisibility VisibilityValue = "public"
  174. )
  175. // MergeMethodValue represents a project merge type within GitLab.
  176. //
  177. // GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#project-merge-method
  178. type MergeMethodValue string
  179. // List of available merge type
  180. //
  181. // GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#project-merge-method
  182. const (
  183. NoFastForwardMerge MergeMethodValue = "merge"
  184. FastForwardMerge MergeMethodValue = "ff"
  185. RebaseMerge MergeMethodValue = "rebase_merge"
  186. )
  187. // EventTypeValue represents actions type for contribution events
  188. type EventTypeValue string
  189. // List of available action type
  190. //
  191. // GitLab API docs: https://docs.gitlab.com/ce/api/events.html#action-types
  192. const (
  193. CreatedEventType EventTypeValue = "created"
  194. UpdatedEventType EventTypeValue = "updated"
  195. ClosedEventType EventTypeValue = "closed"
  196. ReopenedEventType EventTypeValue = "reopened"
  197. PushedEventType EventTypeValue = "pushed"
  198. CommentedEventType EventTypeValue = "commented"
  199. MergedEventType EventTypeValue = "merged"
  200. JoinedEventType EventTypeValue = "joined"
  201. LeftEventType EventTypeValue = "left"
  202. DestroyedEventType EventTypeValue = "destroyed"
  203. ExpiredEventType EventTypeValue = "expired"
  204. )
  205. // EventTargetTypeValue represents actions type value for contribution events
  206. type EventTargetTypeValue string
  207. // List of available action type
  208. //
  209. // GitLab API docs: https://docs.gitlab.com/ce/api/events.html#target-types
  210. const (
  211. IssueEventTargetType EventTargetTypeValue = "issue"
  212. MilestoneEventTargetType EventTargetTypeValue = "milestone"
  213. MergeRequestEventTargetType EventTargetTypeValue = "merge_request"
  214. NoteEventTargetType EventTargetTypeValue = "note"
  215. ProjectEventTargetType EventTargetTypeValue = "project"
  216. SnippetEventTargetType EventTargetTypeValue = "snippet"
  217. UserEventTargetType EventTargetTypeValue = "user"
  218. )
  219. // A Client manages communication with the GitLab API.
  220. type Client struct {
  221. // HTTP client used to communicate with the API.
  222. client *http.Client
  223. // Base URL for API requests. Defaults to the public GitLab API, but can be
  224. // set to a domain endpoint to use with a self hosted GitLab server. baseURL
  225. // should always be specified with a trailing slash.
  226. baseURL *url.URL
  227. // Token type used to make authenticated API calls.
  228. authType authType
  229. // Username and password used for basix authentication.
  230. username, password string
  231. // Token used to make authenticated API calls.
  232. token string
  233. // User agent used when communicating with the GitLab API.
  234. UserAgent string
  235. // Services used for talking to different parts of the GitLab API.
  236. AccessRequests *AccessRequestsService
  237. AwardEmoji *AwardEmojiService
  238. Branches *BranchesService
  239. BuildVariables *BuildVariablesService
  240. BroadcastMessage *BroadcastMessagesService
  241. CIYMLTemplate *CIYMLTemplatesService
  242. Commits *CommitsService
  243. CustomAttribute *CustomAttributesService
  244. DeployKeys *DeployKeysService
  245. Deployments *DeploymentsService
  246. Discussions *DiscussionsService
  247. Environments *EnvironmentsService
  248. Events *EventsService
  249. Features *FeaturesService
  250. GitIgnoreTemplates *GitIgnoreTemplatesService
  251. Groups *GroupsService
  252. GroupIssueBoards *GroupIssueBoardsService
  253. GroupMembers *GroupMembersService
  254. GroupMilestones *GroupMilestonesService
  255. GroupVariables *GroupVariablesService
  256. Issues *IssuesService
  257. IssueLinks *IssueLinksService
  258. Jobs *JobsService
  259. Keys *KeysService
  260. Boards *IssueBoardsService
  261. Labels *LabelsService
  262. License *LicenseService
  263. LicenseTemplates *LicenseTemplatesService
  264. MergeRequests *MergeRequestsService
  265. MergeRequestApprovals *MergeRequestApprovalsService
  266. Milestones *MilestonesService
  267. Namespaces *NamespacesService
  268. Notes *NotesService
  269. NotificationSettings *NotificationSettingsService
  270. PagesDomains *PagesDomainsService
  271. Pipelines *PipelinesService
  272. PipelineSchedules *PipelineSchedulesService
  273. PipelineTriggers *PipelineTriggersService
  274. Projects *ProjectsService
  275. ProjectMembers *ProjectMembersService
  276. ProjectBadges *ProjectBadgesService
  277. ProjectSnippets *ProjectSnippetsService
  278. ProjectVariables *ProjectVariablesService
  279. ProtectedBranches *ProtectedBranchesService
  280. Repositories *RepositoriesService
  281. RepositoryFiles *RepositoryFilesService
  282. Runners *RunnersService
  283. Search *SearchService
  284. Services *ServicesService
  285. Settings *SettingsService
  286. Sidekiq *SidekiqService
  287. Snippets *SnippetsService
  288. SystemHooks *SystemHooksService
  289. Tags *TagsService
  290. Todos *TodosService
  291. Users *UsersService
  292. Validate *ValidateService
  293. Version *VersionService
  294. Wikis *WikisService
  295. }
  296. // ListOptions specifies the optional parameters to various List methods that
  297. // support pagination.
  298. type ListOptions struct {
  299. // For paginated result sets, page of results to retrieve.
  300. Page int `url:"page,omitempty" json:"page,omitempty"`
  301. // For paginated result sets, the number of results to include per page.
  302. PerPage int `url:"per_page,omitempty" json:"per_page,omitempty"`
  303. }
  304. // NewClient returns a new GitLab API client. If a nil httpClient is
  305. // provided, http.DefaultClient will be used. To use API methods which require
  306. // authentication, provide a valid private or personal token.
  307. func NewClient(httpClient *http.Client, token string) *Client {
  308. client := newClient(httpClient)
  309. client.authType = privateToken
  310. client.token = token
  311. return client
  312. }
  313. // NewBasicAuthClient returns a new GitLab API client. If a nil httpClient is
  314. // provided, http.DefaultClient will be used. To use API methods which require
  315. // authentication, provide a valid username and password.
  316. func NewBasicAuthClient(httpClient *http.Client, endpoint, username, password string) (*Client, error) {
  317. client := newClient(httpClient)
  318. client.authType = basicAuth
  319. client.username = username
  320. client.password = password
  321. client.SetBaseURL(endpoint)
  322. err := client.requestOAuthToken(context.TODO())
  323. if err != nil {
  324. return nil, err
  325. }
  326. return client, nil
  327. }
  328. func (c *Client) requestOAuthToken(ctx context.Context) error {
  329. config := &oauth2.Config{
  330. Endpoint: oauth2.Endpoint{
  331. AuthURL: fmt.Sprintf("%s://%s/oauth/authorize", c.BaseURL().Scheme, c.BaseURL().Host),
  332. TokenURL: fmt.Sprintf("%s://%s/oauth/token", c.BaseURL().Scheme, c.BaseURL().Host),
  333. },
  334. }
  335. ctx = context.WithValue(ctx, oauth2.HTTPClient, c.client)
  336. t, err := config.PasswordCredentialsToken(ctx, c.username, c.password)
  337. if err != nil {
  338. return err
  339. }
  340. c.token = t.AccessToken
  341. return nil
  342. }
  343. // NewOAuthClient returns a new GitLab API client. If a nil httpClient is
  344. // provided, http.DefaultClient will be used. To use API methods which require
  345. // authentication, provide a valid oauth token.
  346. func NewOAuthClient(httpClient *http.Client, token string) *Client {
  347. client := newClient(httpClient)
  348. client.authType = oAuthToken
  349. client.token = token
  350. return client
  351. }
  352. func newClient(httpClient *http.Client) *Client {
  353. if httpClient == nil {
  354. httpClient = http.DefaultClient
  355. }
  356. c := &Client{client: httpClient, UserAgent: userAgent}
  357. if err := c.SetBaseURL(defaultBaseURL); err != nil {
  358. // Should never happen since defaultBaseURL is our constant.
  359. panic(err)
  360. }
  361. // Create the internal timeStats service.
  362. timeStats := &timeStatsService{client: c}
  363. // Create all the public services.
  364. c.AccessRequests = &AccessRequestsService{client: c}
  365. c.AwardEmoji = &AwardEmojiService{client: c}
  366. c.Branches = &BranchesService{client: c}
  367. c.BuildVariables = &BuildVariablesService{client: c}
  368. c.BroadcastMessage = &BroadcastMessagesService{client: c}
  369. c.CIYMLTemplate = &CIYMLTemplatesService{client: c}
  370. c.Commits = &CommitsService{client: c}
  371. c.CustomAttribute = &CustomAttributesService{client: c}
  372. c.DeployKeys = &DeployKeysService{client: c}
  373. c.Deployments = &DeploymentsService{client: c}
  374. c.Discussions = &DiscussionsService{client: c}
  375. c.Environments = &EnvironmentsService{client: c}
  376. c.Events = &EventsService{client: c}
  377. c.Features = &FeaturesService{client: c}
  378. c.GitIgnoreTemplates = &GitIgnoreTemplatesService{client: c}
  379. c.Groups = &GroupsService{client: c}
  380. c.GroupIssueBoards = &GroupIssueBoardsService{client: c}
  381. c.GroupMembers = &GroupMembersService{client: c}
  382. c.GroupMilestones = &GroupMilestonesService{client: c}
  383. c.GroupVariables = &GroupVariablesService{client: c}
  384. c.Issues = &IssuesService{client: c, timeStats: timeStats}
  385. c.IssueLinks = &IssueLinksService{client: c}
  386. c.Jobs = &JobsService{client: c}
  387. c.Keys = &KeysService{client: c}
  388. c.Boards = &IssueBoardsService{client: c}
  389. c.Labels = &LabelsService{client: c}
  390. c.License = &LicenseService{client: c}
  391. c.LicenseTemplates = &LicenseTemplatesService{client: c}
  392. c.MergeRequests = &MergeRequestsService{client: c, timeStats: timeStats}
  393. c.MergeRequestApprovals = &MergeRequestApprovalsService{client: c}
  394. c.Milestones = &MilestonesService{client: c}
  395. c.Namespaces = &NamespacesService{client: c}
  396. c.Notes = &NotesService{client: c}
  397. c.NotificationSettings = &NotificationSettingsService{client: c}
  398. c.PagesDomains = &PagesDomainsService{client: c}
  399. c.Pipelines = &PipelinesService{client: c}
  400. c.PipelineSchedules = &PipelineSchedulesService{client: c}
  401. c.PipelineTriggers = &PipelineTriggersService{client: c}
  402. c.Projects = &ProjectsService{client: c}
  403. c.ProjectMembers = &ProjectMembersService{client: c}
  404. c.ProjectBadges = &ProjectBadgesService{client: c}
  405. c.ProjectSnippets = &ProjectSnippetsService{client: c}
  406. c.ProjectVariables = &ProjectVariablesService{client: c}
  407. c.ProtectedBranches = &ProtectedBranchesService{client: c}
  408. c.Repositories = &RepositoriesService{client: c}
  409. c.RepositoryFiles = &RepositoryFilesService{client: c}
  410. c.Runners = &RunnersService{client: c}
  411. c.Services = &ServicesService{client: c}
  412. c.Search = &SearchService{client: c}
  413. c.Settings = &SettingsService{client: c}
  414. c.Sidekiq = &SidekiqService{client: c}
  415. c.Snippets = &SnippetsService{client: c}
  416. c.SystemHooks = &SystemHooksService{client: c}
  417. c.Tags = &TagsService{client: c}
  418. c.Todos = &TodosService{client: c}
  419. c.Users = &UsersService{client: c}
  420. c.Validate = &ValidateService{client: c}
  421. c.Version = &VersionService{client: c}
  422. c.Wikis = &WikisService{client: c}
  423. return c
  424. }
  425. // BaseURL return a copy of the baseURL.
  426. func (c *Client) BaseURL() *url.URL {
  427. u := *c.baseURL
  428. return &u
  429. }
  430. // SetBaseURL sets the base URL for API requests to a custom endpoint. urlStr
  431. // should always be specified with a trailing slash.
  432. func (c *Client) SetBaseURL(urlStr string) error {
  433. // Make sure the given URL end with a slash
  434. if !strings.HasSuffix(urlStr, "/") {
  435. urlStr += "/"
  436. }
  437. baseURL, err := url.Parse(urlStr)
  438. if err != nil {
  439. return err
  440. }
  441. if !strings.HasSuffix(baseURL.Path, apiVersionPath) {
  442. baseURL.Path += apiVersionPath
  443. }
  444. // Update the base URL of the client.
  445. c.baseURL = baseURL
  446. return nil
  447. }
  448. // NewRequest creates an API request. A relative URL path can be provided in
  449. // urlStr, in which case it is resolved relative to the base URL of the Client.
  450. // Relative URL paths should always be specified without a preceding slash. If
  451. // specified, the value pointed to by body is JSON encoded and included as the
  452. // request body.
  453. func (c *Client) NewRequest(method, path string, opt interface{}, options []OptionFunc) (*http.Request, error) {
  454. u := *c.baseURL
  455. unescaped, err := url.PathUnescape(path)
  456. if err != nil {
  457. return nil, err
  458. }
  459. // Set the encoded path data
  460. u.RawPath = c.baseURL.Path + path
  461. u.Path = c.baseURL.Path + unescaped
  462. if opt != nil {
  463. q, err := query.Values(opt)
  464. if err != nil {
  465. return nil, err
  466. }
  467. u.RawQuery = q.Encode()
  468. }
  469. req := &http.Request{
  470. Method: method,
  471. URL: &u,
  472. Proto: "HTTP/1.1",
  473. ProtoMajor: 1,
  474. ProtoMinor: 1,
  475. Header: make(http.Header),
  476. Host: u.Host,
  477. }
  478. for _, fn := range options {
  479. if fn == nil {
  480. continue
  481. }
  482. if err := fn(req); err != nil {
  483. return nil, err
  484. }
  485. }
  486. if method == "POST" || method == "PUT" {
  487. bodyBytes, err := json.Marshal(opt)
  488. if err != nil {
  489. return nil, err
  490. }
  491. bodyReader := bytes.NewReader(bodyBytes)
  492. u.RawQuery = ""
  493. req.Body = ioutil.NopCloser(bodyReader)
  494. req.ContentLength = int64(bodyReader.Len())
  495. req.Header.Set("Content-Type", "application/json")
  496. }
  497. req.Header.Set("Accept", "application/json")
  498. switch c.authType {
  499. case basicAuth, oAuthToken:
  500. req.Header.Set("Authorization", "Bearer "+c.token)
  501. case privateToken:
  502. req.Header.Set("PRIVATE-TOKEN", c.token)
  503. }
  504. if c.UserAgent != "" {
  505. req.Header.Set("User-Agent", c.UserAgent)
  506. }
  507. return req, nil
  508. }
  509. // Response is a GitLab API response. This wraps the standard http.Response
  510. // returned from GitLab and provides convenient access to things like
  511. // pagination links.
  512. type Response struct {
  513. *http.Response
  514. // These fields provide the page values for paginating through a set of
  515. // results. Any or all of these may be set to the zero value for
  516. // responses that are not part of a paginated set, or for which there
  517. // are no additional pages.
  518. TotalItems int
  519. TotalPages int
  520. ItemsPerPage int
  521. CurrentPage int
  522. NextPage int
  523. PreviousPage int
  524. }
  525. // newResponse creates a new Response for the provided http.Response.
  526. func newResponse(r *http.Response) *Response {
  527. response := &Response{Response: r}
  528. response.populatePageValues()
  529. return response
  530. }
  531. const (
  532. xTotal = "X-Total"
  533. xTotalPages = "X-Total-Pages"
  534. xPerPage = "X-Per-Page"
  535. xPage = "X-Page"
  536. xNextPage = "X-Next-Page"
  537. xPrevPage = "X-Prev-Page"
  538. )
  539. // populatePageValues parses the HTTP Link response headers and populates the
  540. // various pagination link values in the Response.
  541. func (r *Response) populatePageValues() {
  542. if totalItems := r.Response.Header.Get(xTotal); totalItems != "" {
  543. r.TotalItems, _ = strconv.Atoi(totalItems)
  544. }
  545. if totalPages := r.Response.Header.Get(xTotalPages); totalPages != "" {
  546. r.TotalPages, _ = strconv.Atoi(totalPages)
  547. }
  548. if itemsPerPage := r.Response.Header.Get(xPerPage); itemsPerPage != "" {
  549. r.ItemsPerPage, _ = strconv.Atoi(itemsPerPage)
  550. }
  551. if currentPage := r.Response.Header.Get(xPage); currentPage != "" {
  552. r.CurrentPage, _ = strconv.Atoi(currentPage)
  553. }
  554. if nextPage := r.Response.Header.Get(xNextPage); nextPage != "" {
  555. r.NextPage, _ = strconv.Atoi(nextPage)
  556. }
  557. if previousPage := r.Response.Header.Get(xPrevPage); previousPage != "" {
  558. r.PreviousPage, _ = strconv.Atoi(previousPage)
  559. }
  560. }
  561. // Do sends an API request and returns the API response. The API response is
  562. // JSON decoded and stored in the value pointed to by v, or returned as an
  563. // error if an API error has occurred. If v implements the io.Writer
  564. // interface, the raw response body will be written to v, without attempting to
  565. // first decode it.
  566. func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
  567. resp, err := c.client.Do(req)
  568. if err != nil {
  569. return nil, err
  570. }
  571. defer resp.Body.Close()
  572. if resp.StatusCode == http.StatusUnauthorized && c.authType == basicAuth {
  573. err = c.requestOAuthToken(req.Context())
  574. if err != nil {
  575. return nil, err
  576. }
  577. return c.Do(req, v)
  578. }
  579. response := newResponse(resp)
  580. err = CheckResponse(resp)
  581. if err != nil {
  582. // even though there was an error, we still return the response
  583. // in case the caller wants to inspect it further
  584. return response, err
  585. }
  586. if v != nil {
  587. if w, ok := v.(io.Writer); ok {
  588. _, err = io.Copy(w, resp.Body)
  589. } else {
  590. err = json.NewDecoder(resp.Body).Decode(v)
  591. }
  592. }
  593. return response, err
  594. }
  595. // Helper function to accept and format both the project ID or name as project
  596. // identifier for all API calls.
  597. func parseID(id interface{}) (string, error) {
  598. switch v := id.(type) {
  599. case int:
  600. return strconv.Itoa(v), nil
  601. case string:
  602. return v, nil
  603. default:
  604. return "", fmt.Errorf("invalid ID type %#v, the ID must be an int or a string", id)
  605. }
  606. }
  607. // An ErrorResponse reports one or more errors caused by an API request.
  608. //
  609. // GitLab API docs:
  610. // https://docs.gitlab.com/ce/api/README.html#data-validation-and-error-reporting
  611. type ErrorResponse struct {
  612. Body []byte
  613. Response *http.Response
  614. Message string
  615. }
  616. func (e *ErrorResponse) Error() string {
  617. path, _ := url.QueryUnescape(e.Response.Request.URL.Path)
  618. u := fmt.Sprintf("%s://%s%s", e.Response.Request.URL.Scheme, e.Response.Request.URL.Host, path)
  619. return fmt.Sprintf("%s %s: %d %s", e.Response.Request.Method, u, e.Response.StatusCode, e.Message)
  620. }
  621. // CheckResponse checks the API response for errors, and returns them if present.
  622. func CheckResponse(r *http.Response) error {
  623. switch r.StatusCode {
  624. case 200, 201, 202, 204, 304:
  625. return nil
  626. }
  627. errorResponse := &ErrorResponse{Response: r}
  628. data, err := ioutil.ReadAll(r.Body)
  629. if err == nil && data != nil {
  630. errorResponse.Body = data
  631. var raw interface{}
  632. if err := json.Unmarshal(data, &raw); err != nil {
  633. errorResponse.Message = "failed to parse unknown error format"
  634. } else {
  635. errorResponse.Message = parseError(raw)
  636. }
  637. }
  638. return errorResponse
  639. }
  640. // Format:
  641. // {
  642. // "message": {
  643. // "<property-name>": [
  644. // "<error-message>",
  645. // "<error-message>",
  646. // ...
  647. // ],
  648. // "<embed-entity>": {
  649. // "<property-name>": [
  650. // "<error-message>",
  651. // "<error-message>",
  652. // ...
  653. // ],
  654. // }
  655. // },
  656. // "error": "<error-message>"
  657. // }
  658. func parseError(raw interface{}) string {
  659. switch raw := raw.(type) {
  660. case string:
  661. return raw
  662. case []interface{}:
  663. var errs []string
  664. for _, v := range raw {
  665. errs = append(errs, parseError(v))
  666. }
  667. return fmt.Sprintf("[%s]", strings.Join(errs, ", "))
  668. case map[string]interface{}:
  669. var errs []string
  670. for k, v := range raw {
  671. errs = append(errs, fmt.Sprintf("{%s: %s}", k, parseError(v)))
  672. }
  673. sort.Strings(errs)
  674. return strings.Join(errs, ", ")
  675. default:
  676. return fmt.Sprintf("failed to parse unexpected error type: %T", raw)
  677. }
  678. }
  679. // OptionFunc can be passed to all API requests to make the API call as if you were
  680. // another user, provided your private token is from an administrator account.
  681. //
  682. // GitLab docs: https://docs.gitlab.com/ce/api/README.html#sudo
  683. type OptionFunc func(*http.Request) error
  684. // WithSudo takes either a username or user ID and sets the SUDO request header
  685. func WithSudo(uid interface{}) OptionFunc {
  686. return func(req *http.Request) error {
  687. user, err := parseID(uid)
  688. if err != nil {
  689. return err
  690. }
  691. req.Header.Set("SUDO", user)
  692. return nil
  693. }
  694. }
  695. // WithContext runs the request with the provided context
  696. func WithContext(ctx context.Context) OptionFunc {
  697. return func(req *http.Request) error {
  698. *req = *req.WithContext(ctx)
  699. return nil
  700. }
  701. }
  702. // Bool is a helper routine that allocates a new bool value
  703. // to store v and returns a pointer to it.
  704. func Bool(v bool) *bool {
  705. p := new(bool)
  706. *p = v
  707. return p
  708. }
  709. // Int is a helper routine that allocates a new int32 value
  710. // to store v and returns a pointer to it, but unlike Int32
  711. // its argument value is an int.
  712. func Int(v int) *int {
  713. p := new(int)
  714. *p = v
  715. return p
  716. }
  717. // String is a helper routine that allocates a new string value
  718. // to store v and returns a pointer to it.
  719. func String(v string) *string {
  720. p := new(string)
  721. *p = v
  722. return p
  723. }
  724. // AccessLevel is a helper routine that allocates a new AccessLevelValue
  725. // to store v and returns a pointer to it.
  726. func AccessLevel(v AccessLevelValue) *AccessLevelValue {
  727. p := new(AccessLevelValue)
  728. *p = v
  729. return p
  730. }
  731. // BuildState is a helper routine that allocates a new BuildStateValue
  732. // to store v and returns a pointer to it.
  733. func BuildState(v BuildStateValue) *BuildStateValue {
  734. p := new(BuildStateValue)
  735. *p = v
  736. return p
  737. }
  738. // NotificationLevel is a helper routine that allocates a new NotificationLevelValue
  739. // to store v and returns a pointer to it.
  740. func NotificationLevel(v NotificationLevelValue) *NotificationLevelValue {
  741. p := new(NotificationLevelValue)
  742. *p = v
  743. return p
  744. }
  745. // Visibility is a helper routine that allocates a new VisibilityValue
  746. // to store v and returns a pointer to it.
  747. func Visibility(v VisibilityValue) *VisibilityValue {
  748. p := new(VisibilityValue)
  749. *p = v
  750. return p
  751. }
  752. // MergeMethod is a helper routine that allocates a new MergeMethod
  753. // to sotre v and returns a pointer to it.
  754. func MergeMethod(v MergeMethodValue) *MergeMethodValue {
  755. p := new(MergeMethodValue)
  756. *p = v
  757. return p
  758. }
  759. // BoolValue is a boolean value with advanced json unmarshaling features.
  760. type BoolValue bool
  761. // UnmarshalJSON allows 1 and 0 to be considered as boolean values
  762. // Needed for https://gitlab.com/gitlab-org/gitlab-ce/issues/50122
  763. func (t *BoolValue) UnmarshalJSON(b []byte) error {
  764. switch string(b) {
  765. case `"1"`:
  766. *t = true
  767. return nil
  768. case `"0"`:
  769. *t = false
  770. return nil
  771. default:
  772. var v bool
  773. err := json.Unmarshal(b, &v)
  774. *t = BoolValue(v)
  775. return err
  776. }
  777. }