diff options
Diffstat (limited to 'vendor/github.com/xanzy/go-gitlab/gitlab.go')
-rw-r--r-- | vendor/github.com/xanzy/go-gitlab/gitlab.go | 453 |
1 files changed, 345 insertions, 108 deletions
diff --git a/vendor/github.com/xanzy/go-gitlab/gitlab.go b/vendor/github.com/xanzy/go-gitlab/gitlab.go index 80bd1ed..6019d87 100644 --- a/vendor/github.com/xanzy/go-gitlab/gitlab.go +++ b/vendor/github.com/xanzy/go-gitlab/gitlab.go @@ -20,6 +20,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -28,27 +29,30 @@ import ( "sort" "strconv" "strings" + "time" "github.com/google/go-querystring/query" + "golang.org/x/oauth2" ) const ( - libraryVersion = "0.2.0" - defaultBaseURL = "https://gitlab.com/api/v4/" - userAgent = "go-gitlab/" + libraryVersion + defaultBaseURL = "https://gitlab.com/" + apiVersionPath = "api/v4/" + userAgent = "go-gitlab" ) -// tokenType represents a token type within GitLab. +// authType represents an authentication type within GitLab. // // GitLab API docs: https://docs.gitlab.com/ce/api/ -type tokenType int +type authType int -// List of available token type +// List of available authentication types. // // GitLab API docs: https://docs.gitlab.com/ce/api/ const ( - privateToken tokenType = iota + basicAuth authType = iota oAuthToken + privateToken ) // AccessLevelValue represents a permission level within GitLab. @@ -60,6 +64,7 @@ type AccessLevelValue int // // GitLab API docs: https://docs.gitlab.com/ce/permissions/permissions.html const ( + NoPermissions AccessLevelValue = 0 GuestPermissions AccessLevelValue = 10 ReporterPermissions AccessLevelValue = 20 DeveloperPermissions AccessLevelValue = 30 @@ -67,6 +72,58 @@ const ( OwnerPermission AccessLevelValue = 50 ) +// BuildStateValue represents a GitLab build state. +type BuildStateValue string + +// These constants represent all valid build states. +const ( + Pending BuildStateValue = "pending" + Running BuildStateValue = "running" + Success BuildStateValue = "success" + Failed BuildStateValue = "failed" + Canceled BuildStateValue = "canceled" + Skipped BuildStateValue = "skipped" +) + +// ISOTime represents an ISO 8601 formatted date +type ISOTime time.Time + +// ISO 8601 date format +const iso8601 = "2006-01-02" + +// MarshalJSON implements the json.Marshaler interface +func (t ISOTime) MarshalJSON() ([]byte, error) { + if y := time.Time(t).Year(); y < 0 || y >= 10000 { + // ISO 8901 uses 4 digits for the years + 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 +} + +// UnmarshalJSON implements the json.Unmarshaler interface +func (t *ISOTime) UnmarshalJSON(data []byte) error { + // Ignore null, like in the main JSON package + if string(data) == "null" { + return nil + } + + isotime, err := time.Parse(`"`+iso8601+`"`, string(data)) + *t = ISOTime(isotime) + + return err +} + +// String implements the Stringer interface +func (t ISOTime) String() string { + return time.Time(t).Format(iso8601) +} + // NotificationLevelValue represents a notification level. type NotificationLevelValue int @@ -92,6 +149,8 @@ func (l *NotificationLevelValue) UnmarshalJSON(data []byte) error { *l = NotificationLevelValue(raw) case string: *l = notificationLevelTypes[raw] + case nil: + // No action needed. default: return fmt.Errorf("json: cannot unmarshal %T into Go value of type %T", raw, *l) } @@ -127,6 +186,19 @@ var notificationLevelTypes = map[string]NotificationLevelValue{ "custom": CustomNotificationLevel, } +// OrderByValue represent in which order to sort the item +type OrderByValue string + +// These constants represent all valid order by values. +const ( + OrderByCreatedAt OrderByValue = "created_at" + OrderByID OrderByValue = "id" + OrderByIID OrderByValue = "iid" + OrderByRef OrderByValue = "ref" + OrderByStatus OrderByValue = "status" + OrderByUserID OrderByValue = "user_id" +) + // VisibilityValue represents a visibility level within GitLab. // // GitLab API docs: https://docs.gitlab.com/ce/api/ @@ -141,56 +213,128 @@ const ( PublicVisibility VisibilityValue = "public" ) +// MergeMethodValue represents a project merge type within GitLab. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#project-merge-method +type MergeMethodValue string + +// List of available merge type +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#project-merge-method +const ( + NoFastForwardMerge MergeMethodValue = "merge" + FastForwardMerge MergeMethodValue = "ff" + RebaseMerge MergeMethodValue = "rebase_merge" +) + +// EventTypeValue represents actions type for contribution events +type EventTypeValue string + +// List of available action type +// +// GitLab API docs: https://docs.gitlab.com/ce/api/events.html#action-types +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" +) + +// EventTargetTypeValue represents actions type value for contribution events +type EventTargetTypeValue string + +// List of available action type +// +// GitLab API docs: https://docs.gitlab.com/ce/api/events.html#target-types +const ( + IssueEventTargetType EventTargetTypeValue = "issue" + MilestoneEventTargetType EventTargetTypeValue = "milestone" + MergeRequestEventTargetType EventTargetTypeValue = "merge_request" + NoteEventTargetType EventTargetTypeValue = "note" + ProjectEventTargetType EventTargetTypeValue = "project" + SnippetEventTargetType EventTargetTypeValue = "snippet" + UserEventTargetType EventTargetTypeValue = "user" +) + // A Client manages communication with the GitLab API. type Client struct { // HTTP client used to communicate with the API. client *http.Client // Base URL for API requests. Defaults to the public GitLab API, but can be - // set to a domain endpoint to use with aself hosted GitLab server. baseURL + // set to a domain endpoint to use with a self hosted GitLab server. baseURL // should always be specified with a trailing slash. baseURL *url.URL - // token type used to make authenticated API calls. - tokenType tokenType + // Token type used to make authenticated API calls. + authType authType - // token used to make authenticated API calls. + // Username and password used for basix authentication. + username, password string + + // Token used to make authenticated API calls. token string // User agent used when communicating with the GitLab API. UserAgent string // Services used for talking to different parts of the GitLab API. - Branches *BranchesService - BuildVariables *BuildVariablesService - Commits *CommitsService - DeployKeys *DeployKeysService - Features *FeaturesService - Groups *GroupsService - Issues *IssuesService - Jobs *JobsService - Labels *LabelsService - MergeRequests *MergeRequestsService - Milestones *MilestonesService - Namespaces *NamespacesService - Notes *NotesService - NotificationSettings *NotificationSettingsService - Projects *ProjectsService - ProjectMembers *ProjectMembersService - ProjectSnippets *ProjectSnippetsService - Pipelines *PipelinesService - PipelineTriggers *PipelineTriggersService - Repositories *RepositoriesService - RepositoryFiles *RepositoryFilesService - Services *ServicesService - Session *SessionService - Settings *SettingsService - SystemHooks *SystemHooksService - Tags *TagsService - Todos *TodosService - Users *UsersService - Version *VersionService - Wikis *WikisService + AwardEmoji *AwardEmojiService + Branches *BranchesService + BuildVariables *BuildVariablesService + BroadcastMessage *BroadcastMessagesService + Commits *CommitsService + DeployKeys *DeployKeysService + Deployments *DeploymentsService + Environments *EnvironmentsService + Events *EventsService + Features *FeaturesService + GitIgnoreTemplates *GitIgnoreTemplatesService + Groups *GroupsService + GroupMembers *GroupMembersService + GroupMilestones *GroupMilestonesService + Issues *IssuesService + IssueLinks *IssueLinksService + Jobs *JobsService + Boards *IssueBoardsService + Labels *LabelsService + MergeRequests *MergeRequestsService + MergeRequestApprovals *MergeRequestApprovalsService + Milestones *MilestonesService + Namespaces *NamespacesService + Notes *NotesService + NotificationSettings *NotificationSettingsService + PagesDomains *PagesDomainsService + Pipelines *PipelinesService + PipelineSchedules *PipelineSchedulesService + PipelineTriggers *PipelineTriggersService + Projects *ProjectsService + ProjectMembers *ProjectMembersService + ProjectSnippets *ProjectSnippetsService + ProtectedBranches *ProtectedBranchesService + Repositories *RepositoriesService + RepositoryFiles *RepositoryFilesService + Runners *RunnersService + Search *SearchService + Services *ServicesService + Session *SessionService + Settings *SettingsService + Sidekiq *SidekiqService + Snippets *SnippetsService + SystemHooks *SystemHooksService + Tags *TagsService + Todos *TodosService + Users *UsersService + Validate *ValidateService + Version *VersionService + Wikis *WikisService } // ListOptions specifies the optional parameters to various List methods that @@ -205,24 +349,64 @@ type ListOptions struct { // NewClient returns a new GitLab API client. If a nil httpClient is // provided, http.DefaultClient will be used. To use API methods which require -// authentication, provide a valid private token. +// authentication, provide a valid private or personal token. func NewClient(httpClient *http.Client, token string) *Client { - return newClient(httpClient, privateToken, token) + client := newClient(httpClient) + client.authType = privateToken + client.token = token + return client +} + +// NewBasicAuthClient returns a new GitLab API client. If a nil httpClient is +// provided, http.DefaultClient will be used. To use API methods which require +// authentication, provide a valid username and password. +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 } // NewOAuthClient returns a new GitLab API client. If a nil httpClient is // provided, http.DefaultClient will be used. To use API methods which require // authentication, provide a valid oauth token. func NewOAuthClient(httpClient *http.Client, token string) *Client { - return newClient(httpClient, oAuthToken, token) + client := newClient(httpClient) + client.authType = oAuthToken + client.token = token + return client } -func newClient(httpClient *http.Client, tokenType tokenType, token string) *Client { +func newClient(httpClient *http.Client) *Client { if httpClient == nil { httpClient = http.DefaultClient } - c := &Client{client: httpClient, tokenType: tokenType, token: token, UserAgent: userAgent} + c := &Client{client: httpClient, UserAgent: userAgent} if err := c.SetBaseURL(defaultBaseURL); err != nil { // Should never happen since defaultBaseURL is our constant. panic(err) @@ -232,34 +416,53 @@ func newClient(httpClient *http.Client, tokenType tokenType, token string) *Clie timeStats := &timeStatsService{client: c} // Create all the public services. + c.AwardEmoji = &AwardEmojiService{client: c} c.Branches = &BranchesService{client: c} c.BuildVariables = &BuildVariablesService{client: c} + c.BroadcastMessage = &BroadcastMessagesService{client: c} c.Commits = &CommitsService{client: c} c.DeployKeys = &DeployKeysService{client: c} + c.Deployments = &DeploymentsService{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.GroupMembers = &GroupMembersService{client: c} + c.GroupMilestones = &GroupMilestonesService{client: c} c.Issues = &IssuesService{client: c, timeStats: timeStats} + c.IssueLinks = &IssueLinksService{client: c} c.Jobs = &JobsService{client: c} + c.Boards = &IssueBoardsService{client: c} c.Labels = &LabelsService{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.ProjectSnippets = &ProjectSnippetsService{client: c} - c.Pipelines = &PipelinesService{client: c} - c.PipelineTriggers = &PipelineTriggersService{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.Session = &SessionService{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} @@ -280,9 +483,19 @@ func (c *Client) SetBaseURL(urlStr string) error { urlStr += "/" } - var err error - c.baseURL, err = url.Parse(urlStr) - return err + baseURL, err := url.Parse(urlStr) + if err != nil { + return err + } + + if !strings.HasSuffix(baseURL.Path, apiVersionPath) { + baseURL.Path += apiVersionPath + } + + // Update the base URL of the client. + c.baseURL = baseURL + + return nil } // NewRequest creates an API request. A relative URL path can be provided in @@ -314,6 +527,10 @@ func (c *Client) NewRequest(method, path string, opt interface{}, options []Opti } for _, fn := range options { + if fn == nil { + continue + } + if err := fn(req); err != nil { return nil, err } @@ -334,11 +551,11 @@ func (c *Client) NewRequest(method, path string, opt interface{}, options []Opti req.Header.Set("Accept", "application/json") - switch c.tokenType { + switch c.authType { + case basicAuth, oAuthToken: + req.Header.Set("Authorization", "Bearer "+c.token) case privateToken: req.Header.Set("PRIVATE-TOKEN", c.token) - case oAuthToken: - req.Header.Set("Authorization", "Bearer "+c.token) } if c.UserAgent != "" { @@ -358,11 +575,12 @@ type Response struct { // results. Any or all of these may be set to the zero value for // responses that are not part of a paginated set, or for which there // are no additional pages. - - NextPage int - PrevPage int - FirstPage int - LastPage int + TotalItems int + TotalPages int + ItemsPerPage int + CurrentPage int + NextPage int + PreviousPage int } // newResponse creates a new Response for the provided http.Response. @@ -372,47 +590,35 @@ func newResponse(r *http.Response) *Response { 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" +) + // populatePageValues parses the HTTP Link response headers and populates the // various pagination link values in the Response. func (r *Response) populatePageValues() { - if links, ok := r.Response.Header["Link"]; ok && len(links) > 0 { - for _, link := range strings.Split(links[0], ",") { - segments := strings.Split(strings.TrimSpace(link), ";") - - // link must at least have href and rel - if len(segments) < 2 { - continue - } - - // ensure href is properly formatted - if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") { - continue - } - - // try to pull out page parameter - url, err := url.Parse(segments[0][1 : len(segments[0])-1]) - if err != nil { - continue - } - page := url.Query().Get("page") - if page == "" { - continue - } - - for _, segment := range segments[1:] { - switch strings.TrimSpace(segment) { - case `rel="next"`: - r.NextPage, _ = strconv.Atoi(page) - case `rel="prev"`: - r.PrevPage, _ = strconv.Atoi(page) - case `rel="first"`: - r.FirstPage, _ = strconv.Atoi(page) - case `rel="last"`: - r.LastPage, _ = strconv.Atoi(page) - } - - } - } + 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) } } @@ -428,6 +634,14 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { } 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) @@ -466,6 +680,7 @@ func parseID(id interface{}) (string, error) { // GitLab API docs: // https://docs.gitlab.com/ce/api/README.html#data-validation-and-error-reporting type ErrorResponse struct { + Body []byte Response *http.Response Message string } @@ -486,12 +701,14 @@ func CheckResponse(r *http.Response) error { 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) } - - errorResponse.Message = parseError(raw) } return errorResponse @@ -549,16 +766,12 @@ 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 { - switch uid := uid.(type) { - case int: - req.Header.Set("SUDO", strconv.Itoa(uid)) - return nil - case string: - req.Header.Set("SUDO", uid) - return nil - default: - return fmt.Errorf("uid must be either a username or user ID") + user, err := parseID(uid) + if err != nil { + return err } + req.Header.Set("SUDO", user) + return nil } } @@ -603,6 +816,14 @@ func AccessLevel(v AccessLevelValue) *AccessLevelValue { return p } +// BuildState is a helper routine that allocates a new BuildStateValue +// to store v and returns a pointer to it. +func BuildState(v BuildStateValue) *BuildStateValue { + p := new(BuildStateValue) + *p = v + return p +} + // NotificationLevel is a helper routine that allocates a new NotificationLevelValue // to store v and returns a pointer to it. func NotificationLevel(v NotificationLevelValue) *NotificationLevelValue { @@ -611,6 +832,14 @@ func NotificationLevel(v NotificationLevelValue) *NotificationLevelValue { return p } +// OrderBy is a helper routine that allocates a new OrderByValue +// to store v and returns a pointer to it. +func OrderBy(v OrderByValue) *OrderByValue { + p := new(OrderByValue) + *p = v + return p +} + // Visibility is a helper routine that allocates a new VisibilityValue // to store v and returns a pointer to it. func Visibility(v VisibilityValue) *VisibilityValue { @@ -618,3 +847,11 @@ func Visibility(v VisibilityValue) *VisibilityValue { *p = v return p } + +// MergeMethod is a helper routine that allocates a new MergeMethod +// to sotre v and returns a pointer to it. +func MergeMethod(v MergeMethodValue) *MergeMethodValue { + p := new(MergeMethodValue) + *p = v + return p +} |