123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887 |
- package gitlab
- import (
- "bytes"
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "net/url"
- "sort"
- "strconv"
- "strings"
- "time"
- "github.com/google/go-querystring/query"
- "golang.org/x/oauth2"
- )
- const (
- defaultBaseURL = "https://gitlab.com/"
- apiVersionPath = "api/v4/"
- userAgent = "go-gitlab"
- )
- type authType int
- const (
- basicAuth authType = iota
- oAuthToken
- privateToken
- )
- type AccessLevelValue int
- const (
- NoPermissions AccessLevelValue = 0
- GuestPermissions AccessLevelValue = 10
- ReporterPermissions AccessLevelValue = 20
- DeveloperPermissions AccessLevelValue = 30
- MaintainerPermissions AccessLevelValue = 40
- OwnerPermissions AccessLevelValue = 50
-
- MasterPermissions AccessLevelValue = 40
- OwnerPermission AccessLevelValue = 50
- )
- type BuildStateValue string
- const (
- Pending BuildStateValue = "pending"
- Running BuildStateValue = "running"
- Success BuildStateValue = "success"
- Failed BuildStateValue = "failed"
- Canceled BuildStateValue = "canceled"
- Skipped BuildStateValue = "skipped"
- )
- type ISOTime time.Time
- const iso8601 = "2006-01-02"
- func (t ISOTime) MarshalJSON() ([]byte, error) {
- if y := time.Time(t).Year(); y < 0 || y >= 10000 {
-
- return nil, errors.New("ISOTime.MarshalJSON: year outside of range [0,9999]")
- }
- b := make([]byte, 0, len(iso8601)+2)
- b = append(b, '"')
- b = time.Time(t).AppendFormat(b, iso8601)
- b = append(b, '"')
- return b, nil
- }
- func (t *ISOTime) UnmarshalJSON(data []byte) error {
-
- if string(data) == "null" {
- return nil
- }
- isotime, err := time.Parse(`"`+iso8601+`"`, string(data))
- *t = ISOTime(isotime)
- return err
- }
- func (t ISOTime) String() string {
- return time.Time(t).Format(iso8601)
- }
- type NotificationLevelValue int
- func (l NotificationLevelValue) String() string {
- return notificationLevelNames[l]
- }
- func (l NotificationLevelValue) MarshalJSON() ([]byte, error) {
- return json.Marshal(l.String())
- }
- func (l *NotificationLevelValue) UnmarshalJSON(data []byte) error {
- var raw interface{}
- if err := json.Unmarshal(data, &raw); err != nil {
- return err
- }
- switch raw := raw.(type) {
- case float64:
- *l = NotificationLevelValue(raw)
- case string:
- *l = notificationLevelTypes[raw]
- case nil:
-
- default:
- return fmt.Errorf("json: cannot unmarshal %T into Go value of type %T", raw, *l)
- }
- return nil
- }
- const (
- DisabledNotificationLevel NotificationLevelValue = iota
- ParticipatingNotificationLevel
- WatchNotificationLevel
- GlobalNotificationLevel
- MentionNotificationLevel
- CustomNotificationLevel
- )
- var notificationLevelNames = [...]string{
- "disabled",
- "participating",
- "watch",
- "global",
- "mention",
- "custom",
- }
- var notificationLevelTypes = map[string]NotificationLevelValue{
- "disabled": DisabledNotificationLevel,
- "participating": ParticipatingNotificationLevel,
- "watch": WatchNotificationLevel,
- "global": GlobalNotificationLevel,
- "mention": MentionNotificationLevel,
- "custom": CustomNotificationLevel,
- }
- type VisibilityValue string
- const (
- PrivateVisibility VisibilityValue = "private"
- InternalVisibility VisibilityValue = "internal"
- PublicVisibility VisibilityValue = "public"
- )
- type MergeMethodValue string
- const (
- NoFastForwardMerge MergeMethodValue = "merge"
- FastForwardMerge MergeMethodValue = "ff"
- RebaseMerge MergeMethodValue = "rebase_merge"
- )
- type EventTypeValue string
- const (
- CreatedEventType EventTypeValue = "created"
- UpdatedEventType EventTypeValue = "updated"
- ClosedEventType EventTypeValue = "closed"
- ReopenedEventType EventTypeValue = "reopened"
- PushedEventType EventTypeValue = "pushed"
- CommentedEventType EventTypeValue = "commented"
- MergedEventType EventTypeValue = "merged"
- JoinedEventType EventTypeValue = "joined"
- LeftEventType EventTypeValue = "left"
- DestroyedEventType EventTypeValue = "destroyed"
- ExpiredEventType EventTypeValue = "expired"
- )
- type EventTargetTypeValue string
- const (
- IssueEventTargetType EventTargetTypeValue = "issue"
- MilestoneEventTargetType EventTargetTypeValue = "milestone"
- MergeRequestEventTargetType EventTargetTypeValue = "merge_request"
- NoteEventTargetType EventTargetTypeValue = "note"
- ProjectEventTargetType EventTargetTypeValue = "project"
- SnippetEventTargetType EventTargetTypeValue = "snippet"
- UserEventTargetType EventTargetTypeValue = "user"
- )
- type Client struct {
-
- client *http.Client
-
-
-
- baseURL *url.URL
-
- authType authType
-
- username, password string
-
- token string
-
- UserAgent string
-
- AccessRequests *AccessRequestsService
- AwardEmoji *AwardEmojiService
- Branches *BranchesService
- BuildVariables *BuildVariablesService
- BroadcastMessage *BroadcastMessagesService
- CIYMLTemplate *CIYMLTemplatesService
- Commits *CommitsService
- CustomAttribute *CustomAttributesService
- DeployKeys *DeployKeysService
- Deployments *DeploymentsService
- Discussions *DiscussionsService
- Environments *EnvironmentsService
- Events *EventsService
- Features *FeaturesService
- GitIgnoreTemplates *GitIgnoreTemplatesService
- Groups *GroupsService
- GroupIssueBoards *GroupIssueBoardsService
- GroupMembers *GroupMembersService
- GroupMilestones *GroupMilestonesService
- GroupVariables *GroupVariablesService
- Issues *IssuesService
- IssueLinks *IssueLinksService
- Jobs *JobsService
- Keys *KeysService
- Boards *IssueBoardsService
- Labels *LabelsService
- License *LicenseService
- LicenseTemplates *LicenseTemplatesService
- MergeRequests *MergeRequestsService
- MergeRequestApprovals *MergeRequestApprovalsService
- Milestones *MilestonesService
- Namespaces *NamespacesService
- Notes *NotesService
- NotificationSettings *NotificationSettingsService
- PagesDomains *PagesDomainsService
- Pipelines *PipelinesService
- PipelineSchedules *PipelineSchedulesService
- PipelineTriggers *PipelineTriggersService
- Projects *ProjectsService
- ProjectMembers *ProjectMembersService
- ProjectBadges *ProjectBadgesService
- ProjectSnippets *ProjectSnippetsService
- ProjectVariables *ProjectVariablesService
- ProtectedBranches *ProtectedBranchesService
- Repositories *RepositoriesService
- RepositoryFiles *RepositoryFilesService
- Runners *RunnersService
- Search *SearchService
- Services *ServicesService
- Settings *SettingsService
- Sidekiq *SidekiqService
- Snippets *SnippetsService
- SystemHooks *SystemHooksService
- Tags *TagsService
- Todos *TodosService
- Users *UsersService
- Validate *ValidateService
- Version *VersionService
- Wikis *WikisService
- }
- type ListOptions struct {
-
- Page int `url:"page,omitempty" json:"page,omitempty"`
-
- PerPage int `url:"per_page,omitempty" json:"per_page,omitempty"`
- }
- func NewClient(httpClient *http.Client, token string) *Client {
- client := newClient(httpClient)
- client.authType = privateToken
- client.token = token
- return client
- }
- func NewBasicAuthClient(httpClient *http.Client, endpoint, username, password string) (*Client, error) {
- client := newClient(httpClient)
- client.authType = basicAuth
- client.username = username
- client.password = password
- client.SetBaseURL(endpoint)
- err := client.requestOAuthToken(context.TODO())
- if err != nil {
- return nil, err
- }
- return client, nil
- }
- func (c *Client) requestOAuthToken(ctx context.Context) error {
- config := &oauth2.Config{
- Endpoint: oauth2.Endpoint{
- AuthURL: fmt.Sprintf("%s://%s/oauth/authorize", c.BaseURL().Scheme, c.BaseURL().Host),
- TokenURL: fmt.Sprintf("%s://%s/oauth/token", c.BaseURL().Scheme, c.BaseURL().Host),
- },
- }
- ctx = context.WithValue(ctx, oauth2.HTTPClient, c.client)
- t, err := config.PasswordCredentialsToken(ctx, c.username, c.password)
- if err != nil {
- return err
- }
- c.token = t.AccessToken
- return nil
- }
- func NewOAuthClient(httpClient *http.Client, token string) *Client {
- client := newClient(httpClient)
- client.authType = oAuthToken
- client.token = token
- return client
- }
- func newClient(httpClient *http.Client) *Client {
- if httpClient == nil {
- httpClient = http.DefaultClient
- }
- c := &Client{client: httpClient, UserAgent: userAgent}
- if err := c.SetBaseURL(defaultBaseURL); err != nil {
-
- panic(err)
- }
-
- timeStats := &timeStatsService{client: c}
-
- c.AccessRequests = &AccessRequestsService{client: c}
- c.AwardEmoji = &AwardEmojiService{client: c}
- c.Branches = &BranchesService{client: c}
- c.BuildVariables = &BuildVariablesService{client: c}
- c.BroadcastMessage = &BroadcastMessagesService{client: c}
- c.CIYMLTemplate = &CIYMLTemplatesService{client: c}
- c.Commits = &CommitsService{client: c}
- c.CustomAttribute = &CustomAttributesService{client: c}
- c.DeployKeys = &DeployKeysService{client: c}
- c.Deployments = &DeploymentsService{client: c}
- c.Discussions = &DiscussionsService{client: c}
- c.Environments = &EnvironmentsService{client: c}
- c.Events = &EventsService{client: c}
- c.Features = &FeaturesService{client: c}
- c.GitIgnoreTemplates = &GitIgnoreTemplatesService{client: c}
- c.Groups = &GroupsService{client: c}
- c.GroupIssueBoards = &GroupIssueBoardsService{client: c}
- c.GroupMembers = &GroupMembersService{client: c}
- c.GroupMilestones = &GroupMilestonesService{client: c}
- c.GroupVariables = &GroupVariablesService{client: c}
- c.Issues = &IssuesService{client: c, timeStats: timeStats}
- c.IssueLinks = &IssueLinksService{client: c}
- c.Jobs = &JobsService{client: c}
- c.Keys = &KeysService{client: c}
- c.Boards = &IssueBoardsService{client: c}
- c.Labels = &LabelsService{client: c}
- c.License = &LicenseService{client: c}
- c.LicenseTemplates = &LicenseTemplatesService{client: c}
- c.MergeRequests = &MergeRequestsService{client: c, timeStats: timeStats}
- c.MergeRequestApprovals = &MergeRequestApprovalsService{client: c}
- c.Milestones = &MilestonesService{client: c}
- c.Namespaces = &NamespacesService{client: c}
- c.Notes = &NotesService{client: c}
- c.NotificationSettings = &NotificationSettingsService{client: c}
- c.PagesDomains = &PagesDomainsService{client: c}
- c.Pipelines = &PipelinesService{client: c}
- c.PipelineSchedules = &PipelineSchedulesService{client: c}
- c.PipelineTriggers = &PipelineTriggersService{client: c}
- c.Projects = &ProjectsService{client: c}
- c.ProjectMembers = &ProjectMembersService{client: c}
- c.ProjectBadges = &ProjectBadgesService{client: c}
- c.ProjectSnippets = &ProjectSnippetsService{client: c}
- c.ProjectVariables = &ProjectVariablesService{client: c}
- c.ProtectedBranches = &ProtectedBranchesService{client: c}
- c.Repositories = &RepositoriesService{client: c}
- c.RepositoryFiles = &RepositoryFilesService{client: c}
- c.Runners = &RunnersService{client: c}
- c.Services = &ServicesService{client: c}
- c.Search = &SearchService{client: c}
- c.Settings = &SettingsService{client: c}
- c.Sidekiq = &SidekiqService{client: c}
- c.Snippets = &SnippetsService{client: c}
- c.SystemHooks = &SystemHooksService{client: c}
- c.Tags = &TagsService{client: c}
- c.Todos = &TodosService{client: c}
- c.Users = &UsersService{client: c}
- c.Validate = &ValidateService{client: c}
- c.Version = &VersionService{client: c}
- c.Wikis = &WikisService{client: c}
- return c
- }
- func (c *Client) BaseURL() *url.URL {
- u := *c.baseURL
- return &u
- }
- func (c *Client) SetBaseURL(urlStr string) error {
-
- if !strings.HasSuffix(urlStr, "/") {
- urlStr += "/"
- }
- baseURL, err := url.Parse(urlStr)
- if err != nil {
- return err
- }
- if !strings.HasSuffix(baseURL.Path, apiVersionPath) {
- baseURL.Path += apiVersionPath
- }
-
- c.baseURL = baseURL
- return nil
- }
- func (c *Client) NewRequest(method, path string, opt interface{}, options []OptionFunc) (*http.Request, error) {
- u := *c.baseURL
- unescaped, err := url.PathUnescape(path)
- if err != nil {
- return nil, err
- }
-
- u.RawPath = c.baseURL.Path + path
- u.Path = c.baseURL.Path + unescaped
- if opt != nil {
- q, err := query.Values(opt)
- if err != nil {
- return nil, err
- }
- u.RawQuery = q.Encode()
- }
- req := &http.Request{
- Method: method,
- URL: &u,
- Proto: "HTTP/1.1",
- ProtoMajor: 1,
- ProtoMinor: 1,
- Header: make(http.Header),
- Host: u.Host,
- }
- for _, fn := range options {
- if fn == nil {
- continue
- }
- if err := fn(req); err != nil {
- return nil, err
- }
- }
- if method == "POST" || method == "PUT" {
- bodyBytes, err := json.Marshal(opt)
- if err != nil {
- return nil, err
- }
- bodyReader := bytes.NewReader(bodyBytes)
- u.RawQuery = ""
- req.Body = ioutil.NopCloser(bodyReader)
- req.ContentLength = int64(bodyReader.Len())
- req.Header.Set("Content-Type", "application/json")
- }
- req.Header.Set("Accept", "application/json")
- switch c.authType {
- case basicAuth, oAuthToken:
- req.Header.Set("Authorization", "Bearer "+c.token)
- case privateToken:
- req.Header.Set("PRIVATE-TOKEN", c.token)
- }
- if c.UserAgent != "" {
- req.Header.Set("User-Agent", c.UserAgent)
- }
- return req, nil
- }
- type Response struct {
- *http.Response
-
-
-
-
- TotalItems int
- TotalPages int
- ItemsPerPage int
- CurrentPage int
- NextPage int
- PreviousPage int
- }
- func newResponse(r *http.Response) *Response {
- response := &Response{Response: r}
- response.populatePageValues()
- return response
- }
- const (
- xTotal = "X-Total"
- xTotalPages = "X-Total-Pages"
- xPerPage = "X-Per-Page"
- xPage = "X-Page"
- xNextPage = "X-Next-Page"
- xPrevPage = "X-Prev-Page"
- )
- func (r *Response) populatePageValues() {
- if totalItems := r.Response.Header.Get(xTotal); totalItems != "" {
- r.TotalItems, _ = strconv.Atoi(totalItems)
- }
- if totalPages := r.Response.Header.Get(xTotalPages); totalPages != "" {
- r.TotalPages, _ = strconv.Atoi(totalPages)
- }
- if itemsPerPage := r.Response.Header.Get(xPerPage); itemsPerPage != "" {
- r.ItemsPerPage, _ = strconv.Atoi(itemsPerPage)
- }
- if currentPage := r.Response.Header.Get(xPage); currentPage != "" {
- r.CurrentPage, _ = strconv.Atoi(currentPage)
- }
- if nextPage := r.Response.Header.Get(xNextPage); nextPage != "" {
- r.NextPage, _ = strconv.Atoi(nextPage)
- }
- if previousPage := r.Response.Header.Get(xPrevPage); previousPage != "" {
- r.PreviousPage, _ = strconv.Atoi(previousPage)
- }
- }
- func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
- resp, err := c.client.Do(req)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- if resp.StatusCode == http.StatusUnauthorized && c.authType == basicAuth {
- err = c.requestOAuthToken(req.Context())
- if err != nil {
- return nil, err
- }
- return c.Do(req, v)
- }
- response := newResponse(resp)
- err = CheckResponse(resp)
- if err != nil {
-
-
- return response, err
- }
- if v != nil {
- if w, ok := v.(io.Writer); ok {
- _, err = io.Copy(w, resp.Body)
- } else {
- err = json.NewDecoder(resp.Body).Decode(v)
- }
- }
- return response, err
- }
- func parseID(id interface{}) (string, error) {
- switch v := id.(type) {
- case int:
- return strconv.Itoa(v), nil
- case string:
- return v, nil
- default:
- return "", fmt.Errorf("invalid ID type %#v, the ID must be an int or a string", id)
- }
- }
- type ErrorResponse struct {
- Body []byte
- Response *http.Response
- Message string
- }
- func (e *ErrorResponse) Error() string {
- path, _ := url.QueryUnescape(e.Response.Request.URL.Path)
- u := fmt.Sprintf("%s://%s%s", e.Response.Request.URL.Scheme, e.Response.Request.URL.Host, path)
- return fmt.Sprintf("%s %s: %d %s", e.Response.Request.Method, u, e.Response.StatusCode, e.Message)
- }
- func CheckResponse(r *http.Response) error {
- switch r.StatusCode {
- case 200, 201, 202, 204, 304:
- return nil
- }
- errorResponse := &ErrorResponse{Response: r}
- data, err := ioutil.ReadAll(r.Body)
- if err == nil && data != nil {
- errorResponse.Body = data
- var raw interface{}
- if err := json.Unmarshal(data, &raw); err != nil {
- errorResponse.Message = "failed to parse unknown error format"
- } else {
- errorResponse.Message = parseError(raw)
- }
- }
- return errorResponse
- }
- func parseError(raw interface{}) string {
- switch raw := raw.(type) {
- case string:
- return raw
- case []interface{}:
- var errs []string
- for _, v := range raw {
- errs = append(errs, parseError(v))
- }
- return fmt.Sprintf("[%s]", strings.Join(errs, ", "))
- case map[string]interface{}:
- var errs []string
- for k, v := range raw {
- errs = append(errs, fmt.Sprintf("{%s: %s}", k, parseError(v)))
- }
- sort.Strings(errs)
- return strings.Join(errs, ", ")
- default:
- return fmt.Sprintf("failed to parse unexpected error type: %T", raw)
- }
- }
- type OptionFunc func(*http.Request) error
- // WithSudo takes either a username or user ID and sets the SUDO request header
- func WithSudo(uid interface{}) OptionFunc {
- return func(req *http.Request) error {
- user, err := parseID(uid)
- if err != nil {
- return err
- }
- req.Header.Set("SUDO", user)
- return nil
- }
- }
- func WithContext(ctx context.Context) OptionFunc {
- return func(req *http.Request) error {
- *req = *req.WithContext(ctx)
- return nil
- }
- }
- func Bool(v bool) *bool {
- p := new(bool)
- *p = v
- return p
- }
- func Int(v int) *int {
- p := new(int)
- *p = v
- return p
- }
- func String(v string) *string {
- p := new(string)
- *p = v
- return p
- }
- func AccessLevel(v AccessLevelValue) *AccessLevelValue {
- p := new(AccessLevelValue)
- *p = v
- return p
- }
- func BuildState(v BuildStateValue) *BuildStateValue {
- p := new(BuildStateValue)
- *p = v
- return p
- }
- func NotificationLevel(v NotificationLevelValue) *NotificationLevelValue {
- p := new(NotificationLevelValue)
- *p = v
- return p
- }
- func Visibility(v VisibilityValue) *VisibilityValue {
- p := new(VisibilityValue)
- *p = v
- return p
- }
- func MergeMethod(v MergeMethodValue) *MergeMethodValue {
- p := new(MergeMethodValue)
- *p = v
- return p
- }
- type BoolValue bool
- func (t *BoolValue) UnmarshalJSON(b []byte) error {
- switch string(b) {
- case `"1"`:
- *t = true
- return nil
- case `"0"`:
- *t = false
- return nil
- default:
- var v bool
- err := json.Unmarshal(b, &v)
- *t = BoolValue(v)
- return err
- }
- }
|