From b4970e54d4872d57244f63f35cc4cd00d63bfcfa Mon Sep 17 00:00:00 2001 From: Mark Old Date: Fri, 8 Jun 2018 13:17:37 -0700 Subject: [PATCH 01/23] Add rudimentary gitlab support --- Gopkg.lock | 8 +- Gopkg.toml | 4 + gitlab/display.go | 75 ++ gitlab/gitlab_repo.go | 113 ++ gitlab/widget.go | 162 +++ vendor/github.com/xanzy/go-gitlab/.gitignore | 24 + vendor/github.com/xanzy/go-gitlab/.travis.yml | 27 + .../github.com/xanzy/go-gitlab/CHANGELOG.md | 27 + vendor/github.com/xanzy/go-gitlab/LICENSE | 202 ++++ vendor/github.com/xanzy/go-gitlab/README.md | 171 +++ .../xanzy/go-gitlab/award_emojis.go | 460 ++++++++ vendor/github.com/xanzy/go-gitlab/boards.go | 261 +++++ vendor/github.com/xanzy/go-gitlab/branches.go | 238 +++++ .../xanzy/go-gitlab/broadcast_messages.go | 172 +++ .../xanzy/go-gitlab/build_variables.go | 173 +++ vendor/github.com/xanzy/go-gitlab/commits.go | 448 ++++++++ .../github.com/xanzy/go-gitlab/deploy_keys.go | 201 ++++ .../github.com/xanzy/go-gitlab/deployments.go | 121 +++ .../xanzy/go-gitlab/environments.go | 166 +++ .../github.com/xanzy/go-gitlab/event_types.go | 649 ++++++++++++ vendor/github.com/xanzy/go-gitlab/events.go | 147 +++ .../xanzy/go-gitlab/feature_flags.go | 79 ++ .../xanzy/go-gitlab/gitignore_templates.go | 84 ++ vendor/github.com/xanzy/go-gitlab/gitlab.go | 854 +++++++++++++++ .../xanzy/go-gitlab/group_members.go | 195 ++++ .../xanzy/go-gitlab/group_milestones.go | 250 +++++ vendor/github.com/xanzy/go-gitlab/groups.go | 307 ++++++ .../github.com/xanzy/go-gitlab/issue_links.go | 128 +++ vendor/github.com/xanzy/go-gitlab/issues.go | 403 +++++++ vendor/github.com/xanzy/go-gitlab/jobs.go | 350 ++++++ vendor/github.com/xanzy/go-gitlab/labels.go | 228 ++++ .../go-gitlab/merge_request_approvals.go | 125 +++ .../xanzy/go-gitlab/merge_requests.go | 743 +++++++++++++ .../github.com/xanzy/go-gitlab/milestones.go | 267 +++++ .../github.com/xanzy/go-gitlab/namespaces.go | 122 +++ vendor/github.com/xanzy/go-gitlab/notes.go | 490 +++++++++ .../xanzy/go-gitlab/notifications.go | 214 ++++ .../xanzy/go-gitlab/pages_domains.go | 175 +++ .../xanzy/go-gitlab/pipeline_schedules.go | 341 ++++++ .../xanzy/go-gitlab/pipeline_triggers.go | 232 ++++ .../github.com/xanzy/go-gitlab/pipelines.go | 220 ++++ .../xanzy/go-gitlab/project_members.go | 179 ++++ .../xanzy/go-gitlab/project_snippets.go | 207 ++++ vendor/github.com/xanzy/go-gitlab/projects.go | 997 ++++++++++++++++++ .../xanzy/go-gitlab/protected_branches.go | 165 +++ .../xanzy/go-gitlab/repositories.go | 260 +++++ .../xanzy/go-gitlab/repository_files.go | 235 +++++ vendor/github.com/xanzy/go-gitlab/runners.go | 326 ++++++ vendor/github.com/xanzy/go-gitlab/search.go | 326 ++++++ vendor/github.com/xanzy/go-gitlab/services.go | 515 +++++++++ vendor/github.com/xanzy/go-gitlab/session.go | 78 ++ vendor/github.com/xanzy/go-gitlab/settings.go | 265 +++++ .../xanzy/go-gitlab/sidekiq_metrics.go | 154 +++ vendor/github.com/xanzy/go-gitlab/snippets.go | 230 ++++ vendor/github.com/xanzy/go-gitlab/strings.go | 94 ++ .../xanzy/go-gitlab/system_hooks.go | 143 +++ vendor/github.com/xanzy/go-gitlab/tags.go | 232 ++++ .../github.com/xanzy/go-gitlab/time_stats.go | 163 +++ vendor/github.com/xanzy/go-gitlab/todos.go | 175 +++ vendor/github.com/xanzy/go-gitlab/users.go | 767 ++++++++++++++ vendor/github.com/xanzy/go-gitlab/validate.go | 40 + vendor/github.com/xanzy/go-gitlab/version.go | 56 + vendor/github.com/xanzy/go-gitlab/wikis.go | 204 ++++ wtf.go | 4 + 64 files changed, 15470 insertions(+), 1 deletion(-) create mode 100644 gitlab/display.go create mode 100644 gitlab/gitlab_repo.go create mode 100644 gitlab/widget.go create mode 100644 vendor/github.com/xanzy/go-gitlab/.gitignore create mode 100644 vendor/github.com/xanzy/go-gitlab/.travis.yml create mode 100644 vendor/github.com/xanzy/go-gitlab/CHANGELOG.md create mode 100644 vendor/github.com/xanzy/go-gitlab/LICENSE create mode 100644 vendor/github.com/xanzy/go-gitlab/README.md create mode 100644 vendor/github.com/xanzy/go-gitlab/award_emojis.go create mode 100644 vendor/github.com/xanzy/go-gitlab/boards.go create mode 100644 vendor/github.com/xanzy/go-gitlab/branches.go create mode 100644 vendor/github.com/xanzy/go-gitlab/broadcast_messages.go create mode 100644 vendor/github.com/xanzy/go-gitlab/build_variables.go create mode 100644 vendor/github.com/xanzy/go-gitlab/commits.go create mode 100644 vendor/github.com/xanzy/go-gitlab/deploy_keys.go create mode 100644 vendor/github.com/xanzy/go-gitlab/deployments.go create mode 100644 vendor/github.com/xanzy/go-gitlab/environments.go create mode 100644 vendor/github.com/xanzy/go-gitlab/event_types.go create mode 100644 vendor/github.com/xanzy/go-gitlab/events.go create mode 100644 vendor/github.com/xanzy/go-gitlab/feature_flags.go create mode 100644 vendor/github.com/xanzy/go-gitlab/gitignore_templates.go create mode 100644 vendor/github.com/xanzy/go-gitlab/gitlab.go create mode 100644 vendor/github.com/xanzy/go-gitlab/group_members.go create mode 100644 vendor/github.com/xanzy/go-gitlab/group_milestones.go create mode 100644 vendor/github.com/xanzy/go-gitlab/groups.go create mode 100644 vendor/github.com/xanzy/go-gitlab/issue_links.go create mode 100644 vendor/github.com/xanzy/go-gitlab/issues.go create mode 100644 vendor/github.com/xanzy/go-gitlab/jobs.go create mode 100644 vendor/github.com/xanzy/go-gitlab/labels.go create mode 100644 vendor/github.com/xanzy/go-gitlab/merge_request_approvals.go create mode 100644 vendor/github.com/xanzy/go-gitlab/merge_requests.go create mode 100644 vendor/github.com/xanzy/go-gitlab/milestones.go create mode 100644 vendor/github.com/xanzy/go-gitlab/namespaces.go create mode 100644 vendor/github.com/xanzy/go-gitlab/notes.go create mode 100644 vendor/github.com/xanzy/go-gitlab/notifications.go create mode 100644 vendor/github.com/xanzy/go-gitlab/pages_domains.go create mode 100644 vendor/github.com/xanzy/go-gitlab/pipeline_schedules.go create mode 100644 vendor/github.com/xanzy/go-gitlab/pipeline_triggers.go create mode 100644 vendor/github.com/xanzy/go-gitlab/pipelines.go create mode 100644 vendor/github.com/xanzy/go-gitlab/project_members.go create mode 100644 vendor/github.com/xanzy/go-gitlab/project_snippets.go create mode 100644 vendor/github.com/xanzy/go-gitlab/projects.go create mode 100644 vendor/github.com/xanzy/go-gitlab/protected_branches.go create mode 100644 vendor/github.com/xanzy/go-gitlab/repositories.go create mode 100644 vendor/github.com/xanzy/go-gitlab/repository_files.go create mode 100644 vendor/github.com/xanzy/go-gitlab/runners.go create mode 100644 vendor/github.com/xanzy/go-gitlab/search.go create mode 100644 vendor/github.com/xanzy/go-gitlab/services.go create mode 100644 vendor/github.com/xanzy/go-gitlab/session.go create mode 100644 vendor/github.com/xanzy/go-gitlab/settings.go create mode 100644 vendor/github.com/xanzy/go-gitlab/sidekiq_metrics.go create mode 100644 vendor/github.com/xanzy/go-gitlab/snippets.go create mode 100644 vendor/github.com/xanzy/go-gitlab/strings.go create mode 100644 vendor/github.com/xanzy/go-gitlab/system_hooks.go create mode 100644 vendor/github.com/xanzy/go-gitlab/tags.go create mode 100644 vendor/github.com/xanzy/go-gitlab/time_stats.go create mode 100644 vendor/github.com/xanzy/go-gitlab/todos.go create mode 100644 vendor/github.com/xanzy/go-gitlab/users.go create mode 100644 vendor/github.com/xanzy/go-gitlab/validate.go create mode 100644 vendor/github.com/xanzy/go-gitlab/version.go create mode 100644 vendor/github.com/xanzy/go-gitlab/wikis.go diff --git a/Gopkg.lock b/Gopkg.lock index ef289b39..d59fa66b 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -88,6 +88,12 @@ packages = ["."] revision = "71ecf1f4299c6d72b16d20da72405b7e85ac8720" +[[projects]] + branch = "master" + name = "github.com/xanzy/go-gitlab" + packages = ["."] + revision = "73e9df58a3194b1256edae4d2e819603f06d428c" + [[projects]] branch = "master" name = "github.com/yfronto/newrelic" @@ -164,6 +170,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "b0e632406212933c5f4d7652db7aa06e63f4ed94d38ceb823e5c51b865c20be0" + inputs-digest = "fd122eb7175ffd7db7cb197de13e0b850231ec9269a63a8ca9d117a8aa50d7e5" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index c18c9146..e64c4c7b 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -41,6 +41,10 @@ name = "github.com/google/go-github" branch = "master" +[[constraint]] + name = "github.com/xanzy/go-gitlab" + branch = "master" + [[constraint]] name = "github.com/jessevdk/go-flags" version = "1.4.0" diff --git a/gitlab/display.go b/gitlab/display.go new file mode 100644 index 00000000..8968701b --- /dev/null +++ b/gitlab/display.go @@ -0,0 +1,75 @@ +package gitlab + +import ( + "fmt" + + "github.com/senorprogrammer/wtf/wtf" +) + +func (widget *Widget) display() { + + project := widget.currentGitlabProject() + if project == nil { + fmt.Fprintf(widget.View, "%s", " Gitlab project data is unavailable (1)") + return + } + + widget.View.SetTitle(fmt.Sprintf("%s- %s", widget.Name, widget.title(project))) + + str := wtf.SigilStr(len(widget.GitlabProjects), widget.Idx, widget.View) + "\n" + str = str + " [red]Stats[white]\n" + str = str + widget.displayStats(project) + str = str + "\n" + str = str + " [red]Open Approval Requests[white]\n" + str = str + widget.displayMyApprovalRequests(project, Config.UString("wtf.mods.gitlab.username")) + str = str + "\n" + str = str + " [red]My Merge Requests[white]\n" + str = str + widget.displayMyMergeRequests(project, Config.UString("wtf.mods.gitlab.username")) + + widget.View.SetText(str) +} + +func (widget *Widget) displayMyMergeRequests(project *GitlabProject, username string) string { + mrs := project.myMergeRequests(username) + + if len(mrs) == 0 { + return " [grey]none[white]\n" + } + + str := "" + for _, mr := range mrs { + str = str + fmt.Sprintf(" [green]%4d[white] %s\n", mr.IID, mr.Title) + } + + return str +} + +func (widget *Widget) displayMyApprovalRequests(project *GitlabProject, username string) string { + mrs := project.myApprovalRequests(username) + + if len(mrs) == 0 { + return " [grey]none[white]\n" + } + + str := "" + for _, mr := range mrs { + str = str + fmt.Sprintf(" [green]%4d[white] %s\n", mr.IID, mr.Title) + } + + return str +} + +func (widget *Widget) displayStats(project *GitlabProject) string { + str := fmt.Sprintf( + " MRs: %d Issues: %d Stars: %d\n", + project.MergeRequestCount(), + project.IssueCount(), + project.StarCount(), + ) + + return str +} + +func (widget *Widget) title(project *GitlabProject) string { + return fmt.Sprintf("[green]%s [white]", project.Path) +} diff --git a/gitlab/gitlab_repo.go b/gitlab/gitlab_repo.go new file mode 100644 index 00000000..1af7e00c --- /dev/null +++ b/gitlab/gitlab_repo.go @@ -0,0 +1,113 @@ +package gitlab + +import ( + glb "github.com/xanzy/go-gitlab" +) + +type GitlabProject struct { + gitlab *glb.Client + Path string + + MergeRequests []*glb.MergeRequest + RemoteProject *glb.Project +} + +func NewGitlabProject(name string, namespace string, gitlab *glb.Client) *GitlabProject { + path := namespace + "/" + name + project := GitlabProject{ + gitlab: gitlab, + Path: path, + } + + return &project +} + +// Refresh reloads the gitlab data via the Gitlab API +func (project *GitlabProject) Refresh() { + project.MergeRequests, _ = project.loadMergeRequests() + project.RemoteProject, _ = project.loadRemoteProject() +} + +/* -------------------- Counts -------------------- */ + +func (project *GitlabProject) IssueCount() int { + if project.RemoteProject == nil { + return 0 + } + + return project.RemoteProject.OpenIssuesCount +} + +func (project *GitlabProject) MergeRequestCount() int { + return len(project.MergeRequests) +} + +func (project *GitlabProject) StarCount() int { + if project.RemoteProject == nil { + return 0 + } + + return project.RemoteProject.StarCount +} + +/* -------------------- Unexported Functions -------------------- */ + +// myMergeRequests returns a list of merge requests created by username on this project +func (project *GitlabProject) myMergeRequests(username string) []*glb.MergeRequest { + mrs := []*glb.MergeRequest{} + + for _, mr := range project.MergeRequests { + user := mr.Author + + if user.Username == username { + mrs = append(mrs, mr) + } + } + + return mrs +} + +// myApprovalRequests returns a list of merge requests for which username has been +// requested to approve +func (project *GitlabProject) myApprovalRequests(username string) []*glb.MergeRequest { + mrs := []*glb.MergeRequest{} + + for _, mr := range project.MergeRequests { + approvers, _, err := project.gitlab.MergeRequests.GetMergeRequestApprovals(project.Path, mr.IID) + if err != nil { + continue + } + for _, approver := range approvers.Approvers { + if approver.User.Username == username { + mrs = append(mrs, mr) + } + } + } + + return mrs +} + +func (project *GitlabProject) loadMergeRequests() ([]*glb.MergeRequest, error) { + state := "opened" + opts := glb.ListProjectMergeRequestsOptions{ + State: &state, + } + + mrs, _, err := project.gitlab.MergeRequests.ListProjectMergeRequests(project.Path, &opts) + + if err != nil { + return nil, err + } + + return mrs, nil +} + +func (project *GitlabProject) loadRemoteProject() (*glb.Project, error) { + projectsitory, _, err := project.gitlab.Projects.GetProject(project.Path) + + if err != nil { + return nil, err + } + + return projectsitory, nil +} diff --git a/gitlab/widget.go b/gitlab/widget.go new file mode 100644 index 00000000..9dd19ba9 --- /dev/null +++ b/gitlab/widget.go @@ -0,0 +1,162 @@ +package gitlab + +import ( + "os" + + "github.com/gdamore/tcell" + "github.com/olebedev/config" + "github.com/rivo/tview" + "github.com/senorprogrammer/wtf/wtf" + glb "github.com/xanzy/go-gitlab" +) + +// Config is a pointer to the global config object +var Config *config.Config + +const HelpText = ` + Keyboard commands for Gitlab: + + /: Show/hide this help window + h: Previous project + l: Next project + r: Refresh the data + + arrow left: Previous project + arrow right: Next project +` + +type Widget struct { + wtf.TextWidget + + app *tview.Application + pages *tview.Pages + + gitlab *glb.Client + + GitlabProjects []*GitlabProject + Idx int +} + +func NewWidget(app *tview.Application, pages *tview.Pages) *Widget { + apiKey := os.Getenv("WTF_GITLAB_TOKEN") + baseURL := Config.UString("wtf.mods.gitlab.domain") + gitlab := glb.NewClient(nil, apiKey) + if baseURL != "" { + gitlab.SetBaseURL(baseURL) + } + + widget := Widget{ + TextWidget: wtf.NewTextWidget(" Gitlab ", "gitlab", true), + + app: app, + pages: pages, + + gitlab: gitlab, + + Idx: 0, + } + + widget.GitlabProjects = widget.buildProjectCollection(Config.UMap("wtf.mods.gitlab.projects")) + + widget.View.SetInputCapture(widget.keyboardIntercept) + + return &widget +} + +/* -------------------- Exported Functions -------------------- */ + +func (widget *Widget) Refresh() { + if widget.Disabled() { + return + } + + for _, project := range widget.GitlabProjects { + project.Refresh() + } + + widget.UpdateRefreshedAt() + widget.display() +} + +func (widget *Widget) Next() { + widget.Idx = widget.Idx + 1 + if widget.Idx == len(widget.GitlabProjects) { + widget.Idx = 0 + } + + widget.display() +} + +func (widget *Widget) Prev() { + widget.Idx = widget.Idx - 1 + if widget.Idx < 0 { + widget.Idx = len(widget.GitlabProjects) - 1 + } + + widget.display() +} + +/* -------------------- Unexported Functions -------------------- */ + +func (widget *Widget) buildProjectCollection(projectData map[string]interface{}) []*GitlabProject { + gitlabProjects := []*GitlabProject{} + + for name, namespace := range projectData { + project := NewGitlabProject(name, namespace.(string), widget.gitlab) + gitlabProjects = append(gitlabProjects, project) + } + + return gitlabProjects +} + +func (widget *Widget) currentGitlabProject() *GitlabProject { + if len(widget.GitlabProjects) == 0 { + return nil + } + + if widget.Idx < 0 || widget.Idx >= len(widget.GitlabProjects) { + return nil + } + + return widget.GitlabProjects[widget.Idx] +} + +func (widget *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey { + switch string(event.Rune()) { + case "/": + widget.showHelp() + return nil + case "h": + widget.Prev() + return nil + case "l": + widget.Next() + return nil + case "r": + widget.Refresh() + return nil + } + + switch event.Key() { + case tcell.KeyLeft: + widget.Prev() + return nil + case tcell.KeyRight: + widget.Next() + return nil + default: + return event + } +} + +func (widget *Widget) showHelp() { + closeFunc := func() { + widget.pages.RemovePage("help") + widget.app.SetFocus(widget.View) + } + + modal := wtf.NewBillboardModal(HelpText, closeFunc) + + widget.pages.AddPage("help", modal, false, true) + widget.app.SetFocus(modal) +} diff --git a/vendor/github.com/xanzy/go-gitlab/.gitignore b/vendor/github.com/xanzy/go-gitlab/.gitignore new file mode 100644 index 00000000..daf913b1 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/xanzy/go-gitlab/.travis.yml b/vendor/github.com/xanzy/go-gitlab/.travis.yml new file mode 100644 index 00000000..26203291 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/.travis.yml @@ -0,0 +1,27 @@ +language: go + +go: + - 1.8.x + - 1.9.x + - 1.10.x + - master + +stages: + - lint + - test + +jobs: + include: + - stage: lint + script: + - go get github.com/golang/lint/golint + - golint -set_exit_status + - go vet -v + - stage: test + script: + - go test -v + +matrix: + allow_failures: + - go: master + fast_finish: true diff --git a/vendor/github.com/xanzy/go-gitlab/CHANGELOG.md b/vendor/github.com/xanzy/go-gitlab/CHANGELOG.md new file mode 100644 index 00000000..29e93fff --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/CHANGELOG.md @@ -0,0 +1,27 @@ +go-github CHANGELOG +=================== + +0.6.0 +----- +- Add support for the V4 Gitlab API. This means the older V3 API is no longer fully supported + with this version. If you still need that version, please use the `f-api-v3` branch. + +0.4.0 +----- +- Add support to use [`sudo`](https://docs.gitlab.com/ce/api/README.html#sudo) for all API calls. +- Add support for the Notification Settings API. +- Add support for the Time Tracking API. +- Make sure that the error response correctly outputs any returned errors. +- And a reasonable number of smaller enhanchements and bugfixes. + +0.3.0 +----- +- Moved the tags related API calls to their own service, following the Gitlab API structure. + +0.2.0 +----- +- Convert all Option structs to use pointers for their fields. + +0.1.0 +----- +- Initial release. diff --git a/vendor/github.com/xanzy/go-gitlab/LICENSE b/vendor/github.com/xanzy/go-gitlab/LICENSE new file mode 100644 index 00000000..e06d2081 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/vendor/github.com/xanzy/go-gitlab/README.md b/vendor/github.com/xanzy/go-gitlab/README.md new file mode 100644 index 00000000..dc793162 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/README.md @@ -0,0 +1,171 @@ +# go-gitlab + +A GitLab API client enabling Go programs to interact with GitLab in a simple and uniform way + +[![Build Status](https://travis-ci.org/xanzy/go-gitlab.svg?branch=master)](https://travis-ci.org/xanzy/go-gitlab) +[![GitHub license](https://img.shields.io/github/license/xanzy/go-gitlab.svg)](https://github.com/xanzy/go-gitlab/blob/master/LICENSE) +[![Sourcegraph](https://sourcegraph.com/github.com/xanzy/go-gitlab/-/badge.svg)](https://sourcegraph.com/github.com/xanzy/go-gitlab?badge) +[![GoDoc](https://godoc.org/github.com/xanzy/go-gitlab?status.svg)](https://godoc.org/github.com/xanzy/go-gitlab) +[![Go Report Card](https://goreportcard.com/badge/github.com/xanzy/go-gitlab)](https://goreportcard.com/report/github.com/xanzy/go-gitlab) +[![GitHub issues](https://img.shields.io/github/issues/xanzy/go-gitlab.svg)](https://github.com/xanzy/go-gitlab/issues) + +## NOTE + +Release v0.6.0 (released on 25-08-2017) no longer supports the older V3 Gitlab API. If +you need V3 support, please use the `f-api-v3` branch. This release contains some backwards +incompatible changes that were needed to fully support the V4 Gitlab API. + +## Coverage + +This API client package covers most of the existing Gitlab API calls and is updated regularly +to add new and/or missing endpoints. Currently the following services are supported: + +- [x] Award Emojis +- [x] Branches +- [x] Broadcast Messages +- [ ] Project-level Variables +- [ ] Group-level Variables +- [x] Commits +- [ ] Custom Attributes +- [x] Deployments +- [x] Deploy Keys +- [x] Environments +- [ ] Epics +- [ ] Epic Issues +- [x] Events +- [x] Feature flags +- [ ] Geo Nodes +- [x] Gitignores templates +- [ ] GitLab CI Config templates +- [x] Groups +- [ ] Group Access Requests +- [x] Group Members +- [x] Issues +- [x] Issue Boards +- [x] Jobs +- [ ] Keys +- [x] Labels +- [ ] License +- [x] Merge Requests +- [x] Merge Request Approvals +- [x] Project Milestones +- [ ] Group Milestones +- [x] Namespaces +- [x] Notes (comments) +- [ ] Discussions (threaded comments) +- [x] Notification settings +- [ ] Open source license templates +- [x] Pages Domains +- [x] Pipelines +- [x] Pipeline Triggers +- [x] Pipeline Schedules +- [x] Projects (including setting Webhooks) +- [ ] Project Access Requests +- [ ] Project badges +- [ ] Project import/export +- [x] Project Members +- [x] Project Snippets +- [x] Protected Branches +- [x] Repositories +- [x] Repository Files +- [x] Runners +- [ ] Search +- [x] Services +- [x] Settings +- [x] Sidekiq metrics +- [x] Session +- [x] System Hooks +- [x] Tags +- [x] Todos +- [x] Users +- [x] Validate CI configuration +- [x] Version +- [x] Wikis + +## Usage + +```go +import "github.com/xanzy/go-gitlab" +``` + +Construct a new GitLab client, then use the various services on the client to +access different parts of the GitLab API. For example, to list all +users: + +```go +git := gitlab.NewClient(nil, "yourtokengoeshere") +//git.SetBaseURL("https://git.mydomain.com/api/v3") +users, _, err := git.Users.ListUsers() +``` + +Some API methods have optional parameters that can be passed. For example, +to list all projects for user "svanharmelen": + +```go +git := gitlab.NewClient(nil) +opt := &ListProjectsOptions{Search: gitlab.String("svanharmelen")} +projects, _, err := git.Projects.ListProjects(opt) +``` + +### Examples + +The [examples](https://github.com/xanzy/go-gitlab/tree/master/examples) directory +contains a couple for clear examples, of which one is partially listed here as well: + +```go +package main + +import ( + "log" + + "github.com/xanzy/go-gitlab" +) + +func main() { + git := gitlab.NewClient(nil, "yourtokengoeshere") + + // Create new project + p := &gitlab.CreateProjectOptions{ + Name: gitlab.String("My Project"), + Description: gitlab.String("Just a test project to play with"), + MergeRequestsEnabled: gitlab.Bool(true), + SnippetsEnabled: gitlab.Bool(true), + Visibility: gitlab.Visibility(gitlab.PublicVisibility), + } + project, _, err := git.Projects.CreateProject(p) + if err != nil { + log.Fatal(err) + } + + // Add a new snippet + s := &gitlab.CreateProjectSnippetOptions{ + Title: gitlab.String("Dummy Snippet"), + FileName: gitlab.String("snippet.go"), + Code: gitlab.String("package main...."), + Visibility: gitlab.Visibility(gitlab.PublicVisibility), + } + _, _, err = git.ProjectSnippets.CreateSnippet(project.ID, s) + if err != nil { + log.Fatal(err) + } +} + +``` + +For complete usage of go-gitlab, see the full [package docs](https://godoc.org/github.com/xanzy/go-gitlab). + +## ToDo + +- The biggest thing this package still needs is tests :disappointed: + +## Issues + +- If you have an issue: report it on the [issue tracker](https://github.com/xanzy/go-gitlab/issues) + +## Author + +Sander van Harmelen () + +## License + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/vendor/github.com/xanzy/go-gitlab/award_emojis.go b/vendor/github.com/xanzy/go-gitlab/award_emojis.go new file mode 100644 index 00000000..b12a662f --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/award_emojis.go @@ -0,0 +1,460 @@ +// +// Copyright 2017, Arkbriar +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" + "time" +) + +// AwardEmojiService handles communication with the emoji awards related methods +// of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/award_emoji.html +type AwardEmojiService struct { + client *Client +} + +// AwardEmoji represents a GitLab Award Emoji. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/award_emoji.html +type AwardEmoji struct { + ID int `json:"id"` + Name string `json:"name"` + User struct { + Name string `json:"name"` + Username string `json:"username"` + ID int `json:"id"` + State string `json:"state"` + AvatarURL string `json:"avatar_url"` + WebURL string `json:"web_url"` + } `json:"user"` + CreatedAt *time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at"` + AwardableID int `json:"awardable_id"` + AwardableType string `json:"awardable_type"` +} + +const ( + awardMergeRequest = "merge_requests" + awardIssue = "issues" + awardSnippets = "snippets" +) + +// ListEmojiAwardsOptions represents the available options for listing emoji +// for each resources +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html +type ListEmojiAwardsOptions ListOptions + +// ListMergeRequestAwardEmoji gets a list of all award emoji on the merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#list-an-awardable-39-s-award-emoji +func (s *AwardEmojiService) ListMergeRequestAwardEmoji(pid interface{}, mergeRequestIID int, opt *ListEmojiAwardsOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { + return s.listAwardEmoji(pid, awardMergeRequest, mergeRequestIID, opt, options...) +} + +// ListIssueAwardEmoji gets a list of all award emoji on the issue. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#list-an-awardable-39-s-award-emoji +func (s *AwardEmojiService) ListIssueAwardEmoji(pid interface{}, issueIID int, opt *ListEmojiAwardsOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { + return s.listAwardEmoji(pid, awardIssue, issueIID, opt, options...) +} + +// ListSnippetAwardEmoji gets a list of all award emoji on the snippet. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#list-an-awardable-39-s-award-emoji +func (s *AwardEmojiService) ListSnippetAwardEmoji(pid interface{}, snippetID int, opt *ListEmojiAwardsOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { + return s.listAwardEmoji(pid, awardSnippets, snippetID, opt, options...) +} + +func (s *AwardEmojiService) listAwardEmoji(pid interface{}, resource string, resourceID int, opt *ListEmojiAwardsOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/%s/%d/award_emoji", + url.QueryEscape(project), + resource, + resourceID, + ) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var as []*AwardEmoji + resp, err := s.client.Do(req, &as) + if err != nil { + return nil, resp, err + } + + return as, resp, err +} + +// GetMergeRequestAwardEmoji get an award emoji from merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#list-an-awardable-39-s-award-emoji +func (s *AwardEmojiService) GetMergeRequestAwardEmoji(pid interface{}, mergeRequestIID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { + return s.getAwardEmoji(pid, awardMergeRequest, mergeRequestIID, awardID, options...) +} + +// GetIssueAwardEmoji get an award emoji from issue. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#list-an-awardable-39-s-award-emoji +func (s *AwardEmojiService) GetIssueAwardEmoji(pid interface{}, issueIID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { + return s.getAwardEmoji(pid, awardIssue, issueIID, awardID, options...) +} + +// GetSnippetAwardEmoji get an award emoji from snippet. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#list-an-awardable-39-s-award-emoji +func (s *AwardEmojiService) GetSnippetAwardEmoji(pid interface{}, snippetID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { + return s.getAwardEmoji(pid, awardSnippets, snippetID, awardID, options...) +} + +func (s *AwardEmojiService) getAwardEmoji(pid interface{}, resource string, resourceID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/%s/%d/award_emoji/%d", + url.QueryEscape(project), + resource, + resourceID, + awardID, + ) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + a := new(AwardEmoji) + resp, err := s.client.Do(req, &a) + if err != nil { + return nil, resp, err + } + + return a, resp, err +} + +// CreateMergeRequestAwardEmoji get an award emoji from merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji +func (s *AwardEmojiService) CreateMergeRequestAwardEmoji(pid interface{}, mergeRequestIID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { + return s.createAwardEmoji(pid, awardMergeRequest, mergeRequestIID, awardID, options...) +} + +// CreateIssueAwardEmoji get an award emoji from issue. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji +func (s *AwardEmojiService) CreateIssueAwardEmoji(pid interface{}, issueIID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { + return s.createAwardEmoji(pid, awardIssue, issueIID, awardID, options...) +} + +// CreateSnippetAwardEmoji get an award emoji from snippet. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji +func (s *AwardEmojiService) CreateSnippetAwardEmoji(pid interface{}, snippetID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { + return s.createAwardEmoji(pid, awardSnippets, snippetID, awardID, options...) +} + +func (s *AwardEmojiService) createAwardEmoji(pid interface{}, resource string, resourceID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/%s/%d/award_emoji/%d", + url.QueryEscape(project), + resource, + resourceID, + awardID, + ) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + a := new(AwardEmoji) + resp, err := s.client.Do(req, &a) + if err != nil { + return nil, resp, err + } + + return a, resp, err +} + +// DeleteIssueAwardEmoji delete award emoji on an issue. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji-on-a-note +func (s *AwardEmojiService) DeleteIssueAwardEmoji(pid interface{}, issueIID, awardID int, options ...OptionFunc) (*Response, error) { + return s.deleteAwardEmoji(pid, awardMergeRequest, issueIID, awardID, options...) +} + +// DeleteMergeRequestAwardEmoji delete award emoji on a merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji-on-a-note +func (s *AwardEmojiService) DeleteMergeRequestAwardEmoji(pid interface{}, mergeRequestIID, awardID int, options ...OptionFunc) (*Response, error) { + return s.deleteAwardEmoji(pid, awardMergeRequest, mergeRequestIID, awardID, options...) +} + +// DeleteSnippetAwardEmoji delete award emoji on a snippet. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji-on-a-note +func (s *AwardEmojiService) DeleteSnippetAwardEmoji(pid interface{}, snippetID, awardID int, options ...OptionFunc) (*Response, error) { + return s.deleteAwardEmoji(pid, awardMergeRequest, snippetID, awardID, options...) +} + +// DeleteAwardEmoji Delete an award emoji on the specified resource. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#delete-an-award-emoji +func (s *AwardEmojiService) deleteAwardEmoji(pid interface{}, resource string, resourceID, awardID int, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/%s/%d/award_emoji/%d", url.QueryEscape(project), resource, + resourceID, awardID) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// ListIssuesAwardEmojiOnNote gets a list of all award emoji on a note from the +// issue. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes +func (s *AwardEmojiService) ListIssuesAwardEmojiOnNote(pid interface{}, issueID, noteID int, opt *ListEmojiAwardsOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { + return s.listAwardEmojiOnNote(pid, awardIssue, issueID, noteID, opt, options...) +} + +// ListMergeRequestAwardEmojiOnNote gets a list of all award emoji on a note +// from the merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes +func (s *AwardEmojiService) ListMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID, noteID int, opt *ListEmojiAwardsOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { + return s.listAwardEmojiOnNote(pid, awardMergeRequest, mergeRequestIID, noteID, opt, options...) +} + +// ListSnippetAwardEmojiOnNote gets a list of all award emoji on a note from the +// snippet. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes +func (s *AwardEmojiService) ListSnippetAwardEmojiOnNote(pid interface{}, snippetIID, noteID int, opt *ListEmojiAwardsOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { + return s.listAwardEmojiOnNote(pid, awardSnippets, snippetIID, noteID, opt, options...) +} + +func (s *AwardEmojiService) listAwardEmojiOnNote(pid interface{}, resources string, ressourceID, noteID int, opt *ListEmojiAwardsOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/%s/%d/notes/%d/award_emoji", url.QueryEscape(project), resources, + ressourceID, noteID) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var as []*AwardEmoji + resp, err := s.client.Do(req, &as) + if err != nil { + return nil, resp, err + } + + return as, resp, err +} + +// GetIssuesAwardEmojiOnNote gets an award emoji on a note from an issue. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes +func (s *AwardEmojiService) GetIssuesAwardEmojiOnNote(pid interface{}, issueID, noteID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { + return s.getSingleNoteAwardEmoji(pid, awardIssue, issueID, noteID, awardID, options...) +} + +// GetMergeRequestAwardEmojiOnNote gets an award emoji on a note from a +// merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes +func (s *AwardEmojiService) GetMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID, noteID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { + return s.getSingleNoteAwardEmoji(pid, awardMergeRequest, mergeRequestIID, noteID, awardID, + options...) +} + +// GetSnippetAwardEmojiOnNote gets an award emoji on a note from a snippet. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes +func (s *AwardEmojiService) GetSnippetAwardEmojiOnNote(pid interface{}, snippetIID, noteID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { + return s.getSingleNoteAwardEmoji(pid, awardSnippets, snippetIID, noteID, awardID, options...) +} + +func (s *AwardEmojiService) getSingleNoteAwardEmoji(pid interface{}, ressource string, resourceID, noteID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/%s/%d/notes/%d/award_emoji/%d", + url.QueryEscape(project), + ressource, + resourceID, + noteID, + awardID, + ) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + a := new(AwardEmoji) + resp, err := s.client.Do(req, &a) + if err != nil { + return nil, resp, err + } + + return a, resp, err +} + +// CreateIssuesAwardEmojiOnNote gets an award emoji on a note from an issue. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes +func (s *AwardEmojiService) CreateIssuesAwardEmojiOnNote(pid interface{}, issueID, noteID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { + return s.createAwardEmojiOnNote(pid, awardIssue, issueID, noteID, options...) +} + +// CreateMergeRequestAwardEmojiOnNote gets an award emoji on a note from a +// merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes +func (s *AwardEmojiService) CreateMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID, noteID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { + return s.createAwardEmojiOnNote(pid, awardMergeRequest, mergeRequestIID, noteID, options...) +} + +// CreateSnippetAwardEmojiOnNote gets an award emoji on a note from a snippet. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes +func (s *AwardEmojiService) CreateSnippetAwardEmojiOnNote(pid interface{}, snippetIID, noteID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { + return s.createAwardEmojiOnNote(pid, awardSnippets, snippetIID, noteID, options...) +} + +// CreateAwardEmojiOnNote award emoji on a note. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji-on-a-note +func (s *AwardEmojiService) createAwardEmojiOnNote(pid interface{}, resource string, resourceID, noteID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/%s/%d/notes/%d/award_emoji", + url.QueryEscape(project), + resource, + resourceID, + noteID, + ) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + a := new(AwardEmoji) + resp, err := s.client.Do(req, &a) + if err != nil { + return nil, resp, err + } + + return a, resp, err +} + +// DeleteIssuesAwardEmojiOnNote deletes an award emoji on a note from an issue. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes +func (s *AwardEmojiService) DeleteIssuesAwardEmojiOnNote(pid interface{}, issueID, noteID, awardID int, options ...OptionFunc) (*Response, error) { + return s.deleteAwardEmojiOnNote(pid, awardIssue, issueID, noteID, awardID, options...) +} + +// DeleteMergeRequestAwardEmojiOnNote deletes an award emoji on a note from a +// merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes +func (s *AwardEmojiService) DeleteMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID, noteID, awardID int, options ...OptionFunc) (*Response, error) { + return s.deleteAwardEmojiOnNote(pid, awardMergeRequest, mergeRequestIID, noteID, awardID, + options...) +} + +// DeleteSnippetAwardEmojiOnNote deletes an award emoji on a note from a snippet. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes +func (s *AwardEmojiService) DeleteSnippetAwardEmojiOnNote(pid interface{}, snippetIID, noteID, awardID int, options ...OptionFunc) (*Response, error) { + return s.deleteAwardEmojiOnNote(pid, awardSnippets, snippetIID, noteID, awardID, options...) +} + +func (s *AwardEmojiService) deleteAwardEmojiOnNote(pid interface{}, resource string, resourceID, noteID, awardID int, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/%s/%d/notes/%d/award_emoji/%d", + url.QueryEscape(project), + resource, + resourceID, + noteID, + awardID, + ) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/xanzy/go-gitlab/boards.go b/vendor/github.com/xanzy/go-gitlab/boards.go new file mode 100644 index 00000000..7579a5bd --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/boards.go @@ -0,0 +1,261 @@ +// +// Copyright 2015, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" +) + +// IssueBoardsService handles communication with the issue board related +// methods of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html +type IssueBoardsService struct { + client *Client +} + +// IssueBoard represents a GitLab issue board. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html +type IssueBoard struct { + ID int `json:"id"` + Name string `json:"name"` + Project *Project `json:"project"` + Milestone *Milestone `json:"milestone"` + Lists []*BoardList `json:"lists"` +} + +func (b IssueBoard) String() string { + return Stringify(b) +} + +// BoardList represents a GitLab board list. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html +type BoardList struct { + ID int `json:"id"` + Labels []*Label `json:"labels"` + Position int `json:"position"` +} + +func (b BoardList) String() string { + return Stringify(b) +} + +// ListIssueBoardsOptions represents the available ListIssueBoards() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#project-board +type ListIssueBoardsOptions ListOptions + +// ListIssueBoards gets a list of all issue boards in a project. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#project-board +func (s *IssueBoardsService) ListIssueBoards(pid interface{}, opt *ListIssueBoardsOptions, options ...OptionFunc) ([]*IssueBoard, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/boards", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var is []*IssueBoard + resp, err := s.client.Do(req, &is) + if err != nil { + return nil, resp, err + } + + return is, resp, err +} + +// GetIssueBoard gets a single issue board of a project. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#single-board +func (s *IssueBoardsService) GetIssueBoard(pid interface{}, board int, options ...OptionFunc) (*IssueBoard, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/boards/%d", url.QueryEscape(project), board) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + ib := new(IssueBoard) + resp, err := s.client.Do(req, ib) + if err != nil { + return nil, resp, err + } + + return ib, resp, err +} + +// GetIssueBoardListsOptions represents the available GetIssueBoardLists() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#list-board-lists +type GetIssueBoardListsOptions ListOptions + +// GetIssueBoardLists gets a list of the issue board's lists. Does not include +// backlog and closed lists. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#list-board-lists +func (s *IssueBoardsService) GetIssueBoardLists(pid interface{}, board int, opt *GetIssueBoardListsOptions, options ...OptionFunc) ([]*BoardList, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/boards/%d/lists", url.QueryEscape(project), board) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var bl []*BoardList + resp, err := s.client.Do(req, &bl) + if err != nil { + return nil, resp, err + } + + return bl, resp, err +} + +// GetIssueBoardList gets a single issue board list. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#single-board-list +func (s *IssueBoardsService) GetIssueBoardList(pid interface{}, board, list int, options ...OptionFunc) (*BoardList, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/boards/%d/lists/%d", + url.QueryEscape(project), + board, + list, + ) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + bl := new(BoardList) + resp, err := s.client.Do(req, bl) + if err != nil { + return nil, resp, err + } + + return bl, resp, err +} + +// CreateIssueBoardListOptions represents the available CreateIssueBoardList() +// options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#new-board-list +type CreateIssueBoardListOptions struct { + LabelID *int `url:"label_id" json:"label_id"` +} + +// CreateIssueBoardList creates a new issue board list. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#new-board-list +func (s *IssueBoardsService) CreateIssueBoardList(pid interface{}, board int, opt *CreateIssueBoardListOptions, options ...OptionFunc) (*BoardList, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/boards/%d/lists", url.QueryEscape(project), board) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + bl := new(BoardList) + resp, err := s.client.Do(req, bl) + if err != nil { + return nil, resp, err + } + + return bl, resp, err +} + +// UpdateIssueBoardListOptions represents the available UpdateIssueBoardList() +// options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#edit-board-list +type UpdateIssueBoardListOptions struct { + Position *int `url:"position" json:"position"` +} + +// UpdateIssueBoardList updates the position of an existing issue board list. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#edit-board-list +func (s *IssueBoardsService) UpdateIssueBoardList(pid interface{}, board, list int, opt *UpdateIssueBoardListOptions, options ...OptionFunc) (*BoardList, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/boards/%d/lists/%d", + url.QueryEscape(project), + board, + list, + ) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + bl := new(BoardList) + resp, err := s.client.Do(req, bl) + if err != nil { + return nil, resp, err + } + + return bl, resp, err +} + +// DeleteIssueBoardList soft deletes an issue board list. Only for admins and +// project owners. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/boards.html#delete-a-board-list +func (s *IssueBoardsService) DeleteIssueBoardList(pid interface{}, board, list int, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/boards/%d/lists/%d", + url.QueryEscape(project), + board, + list, + ) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/xanzy/go-gitlab/branches.go b/vendor/github.com/xanzy/go-gitlab/branches.go new file mode 100644 index 00000000..3f7bd0cd --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/branches.go @@ -0,0 +1,238 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" +) + +// BranchesService handles communication with the branch related methods +// of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/branches.html +type BranchesService struct { + client *Client +} + +// Branch represents a GitLab branch. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/branches.html +type Branch struct { + Commit *Commit `json:"commit"` + Name string `json:"name"` + Protected bool `json:"protected"` + Merged bool `json:"merged"` + DevelopersCanPush bool `json:"developers_can_push"` + DevelopersCanMerge bool `json:"developers_can_merge"` +} + +func (b Branch) String() string { + return Stringify(b) +} + +// ListBranchesOptions represents the available ListBranches() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/branches.html#list-repository-branches +type ListBranchesOptions ListOptions + +// ListBranches gets a list of repository branches from a project, sorted by +// name alphabetically. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/branches.html#list-repository-branches +func (s *BranchesService) ListBranches(pid interface{}, opts *ListBranchesOptions, options ...OptionFunc) ([]*Branch, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/branches", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opts, options) + if err != nil { + return nil, nil, err + } + + var b []*Branch + resp, err := s.client.Do(req, &b) + if err != nil { + return nil, resp, err + } + + return b, resp, err +} + +// GetBranch gets a single project repository branch. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/branches.html#get-single-repository-branch +func (s *BranchesService) GetBranch(pid interface{}, branch string, options ...OptionFunc) (*Branch, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/branches/%s", url.QueryEscape(project), url.QueryEscape(branch)) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + b := new(Branch) + resp, err := s.client.Do(req, b) + if err != nil { + return nil, resp, err + } + + return b, resp, err +} + +// ProtectBranchOptions represents the available ProtectBranch() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/branches.html#protect-repository-branch +type ProtectBranchOptions struct { + DevelopersCanPush *bool `url:"developers_can_push,omitempty" json:"developers_can_push,omitempty"` + DevelopersCanMerge *bool `url:"developers_can_merge,omitempty" json:"developers_can_merge,omitempty"` +} + +// ProtectBranch protects a single project repository branch. This is an +// idempotent function, protecting an already protected repository branch +// still returns a 200 OK status code. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/branches.html#protect-repository-branch +func (s *BranchesService) ProtectBranch(pid interface{}, branch string, opts *ProtectBranchOptions, options ...OptionFunc) (*Branch, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/branches/%s/protect", url.QueryEscape(project), url.QueryEscape(branch)) + + req, err := s.client.NewRequest("PUT", u, opts, options) + if err != nil { + return nil, nil, err + } + + b := new(Branch) + resp, err := s.client.Do(req, b) + if err != nil { + return nil, resp, err + } + + return b, resp, err +} + +// UnprotectBranch unprotects a single project repository branch. This is an +// idempotent function, unprotecting an already unprotected repository branch +// still returns a 200 OK status code. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/branches.html#unprotect-repository-branch +func (s *BranchesService) UnprotectBranch(pid interface{}, branch string, options ...OptionFunc) (*Branch, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/branches/%s/unprotect", url.QueryEscape(project), url.QueryEscape(branch)) + + req, err := s.client.NewRequest("PUT", u, nil, options) + if err != nil { + return nil, nil, err + } + + b := new(Branch) + resp, err := s.client.Do(req, b) + if err != nil { + return nil, resp, err + } + + return b, resp, err +} + +// CreateBranchOptions represents the available CreateBranch() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/branches.html#create-repository-branch +type CreateBranchOptions struct { + Branch *string `url:"branch,omitempty" json:"branch,omitempty"` + Ref *string `url:"ref,omitempty" json:"ref,omitempty"` +} + +// CreateBranch creates branch from commit SHA or existing branch. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/branches.html#create-repository-branch +func (s *BranchesService) CreateBranch(pid interface{}, opt *CreateBranchOptions, options ...OptionFunc) (*Branch, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/branches", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + b := new(Branch) + resp, err := s.client.Do(req, b) + if err != nil { + return nil, resp, err + } + + return b, resp, err +} + +// DeleteBranch deletes an existing branch. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/branches.html#delete-repository-branch +func (s *BranchesService) DeleteBranch(pid interface{}, branch string, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/repository/branches/%s", url.QueryEscape(project), url.QueryEscape(branch)) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// DeleteMergedBranches deletes all branches that are merged into the project's default branch. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/branches.html#delete-merged-branches +func (s *BranchesService) DeleteMergedBranches(pid interface{}, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/repository/merged_branches", url.QueryEscape(project)) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/xanzy/go-gitlab/broadcast_messages.go b/vendor/github.com/xanzy/go-gitlab/broadcast_messages.go new file mode 100644 index 00000000..aee852d4 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/broadcast_messages.go @@ -0,0 +1,172 @@ +// +// Copyright 2018, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "time" +) + +// BroadcastMessagesService handles communication with the broadcast +// messages methods of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/broadcast_messages.html +type BroadcastMessagesService struct { + client *Client +} + +// BroadcastMessage represents a GitLab issue board. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/broadcast_messages.html#get-all-broadcast-messages +type BroadcastMessage struct { + Message string `json:"message"` + StartsAt *time.Time `json:"starts_at"` + EndsAt *time.Time `json:"ends_at"` + Color string `json:"color"` + Font string `json:"font"` + ID int `json:"id"` + Active bool `json:"active"` +} + +// ListBroadcastMessagesOptions represents the available ListBroadcastMessages() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/broadcast_messages.html#get-all-broadcast-messages +type ListBroadcastMessagesOptions ListOptions + +// ListBroadcastMessages gets a list of all broadcasted messages. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/broadcast_messages.html#get-all-broadcast-messages +func (s *BroadcastMessagesService) ListBroadcastMessages(opt *ListBroadcastMessagesOptions, options ...OptionFunc) ([]*BroadcastMessage, *Response, error) { + req, err := s.client.NewRequest("GET", "broadcast_messages", opt, options) + if err != nil { + return nil, nil, err + } + + var bs []*BroadcastMessage + resp, err := s.client.Do(req, &bs) + if err != nil { + return nil, resp, err + } + + return bs, resp, err +} + +// GetBroadcastMessage gets a single broadcast message. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/broadcast_messages.html#get-a-specific-broadcast-message +func (s *BroadcastMessagesService) GetBroadcastMessage(broadcast int, options ...OptionFunc) (*BroadcastMessage, *Response, error) { + u := fmt.Sprintf("broadcast_messages/%d", broadcast) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + b := new(BroadcastMessage) + resp, err := s.client.Do(req, &b) + if err != nil { + return nil, resp, err + } + + return b, resp, err +} + +// CreateBroadcastMessageOptions represents the available CreateBroadcastMessage() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/broadcast_messages.html#create-a-broadcast-message +type CreateBroadcastMessageOptions struct { + Message *string `url:"message" json:"message"` + StartsAt *time.Time `url:"starts_at,omitempty" json:"starts_at,omitempty"` + EndsAt *time.Time `url:"ends_at,omitempty" json:"ends_at,omitempty"` + Color *string `url:"color,omitempty" json:"color,omitempty"` + Font *string `url:"font,omitempty" json:"font,omitempty"` +} + +// CreateBroadcastMessage creates a message to broadcast. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/broadcast_messages.html#create-a-broadcast-message +func (s *BroadcastMessagesService) CreateBroadcastMessage(opt *CreateBroadcastMessageOptions, options ...OptionFunc) (*BroadcastMessage, *Response, error) { + req, err := s.client.NewRequest("POST", "broadcast_messages", opt, options) + if err != nil { + return nil, nil, err + } + + b := new(BroadcastMessage) + resp, err := s.client.Do(req, &b) + if err != nil { + return nil, resp, err + } + + return b, resp, err +} + +// UpdateBroadcastMessageOptions represents the available CreateBroadcastMessage() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/broadcast_messages.html#update-a-broadcast-message +type UpdateBroadcastMessageOptions struct { + Message *string `url:"message,omitempty" json:"message,omitempty"` + StartsAt *time.Time `url:"starts_at,omitempty" json:"starts_at,omitempty"` + EndsAt *time.Time `url:"ends_at,omitempty" json:"ends_at,omitempty"` + Color *string `url:"color,omitempty" json:"color,omitempty"` + Font *string `url:"font,omitempty" json:"font,omitempty"` +} + +// UpdateBroadcastMessage update a broadcasted message. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/broadcast_messages.html#update-a-broadcast-message +func (s *BroadcastMessagesService) UpdateBroadcastMessage(broadcast int, opt *UpdateBroadcastMessageOptions, options ...OptionFunc) (*BroadcastMessage, *Response, error) { + u := fmt.Sprintf("broadcast_messages/%d", broadcast) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + b := new(BroadcastMessage) + resp, err := s.client.Do(req, &b) + if err != nil { + return nil, resp, err + } + + return b, resp, err +} + +// DeleteBroadcastMessage deletes a broadcasted message. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/broadcast_messages.html#delete-a-broadcast-message +func (s *BroadcastMessagesService) DeleteBroadcastMessage(broadcast int, options ...OptionFunc) (*Response, error) { + u := fmt.Sprintf("broadcast_messages/%d", broadcast) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/xanzy/go-gitlab/build_variables.go b/vendor/github.com/xanzy/go-gitlab/build_variables.go new file mode 100644 index 00000000..8a6c8cdd --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/build_variables.go @@ -0,0 +1,173 @@ +package gitlab + +import ( + "fmt" + "net/url" +) + +// BuildVariablesService handles communication with the project variables related methods +// of the Gitlab API +// +// Gitlab API Docs : https://docs.gitlab.com/ce/api/build_variables.html +type BuildVariablesService struct { + client *Client +} + +// BuildVariable represents a variable available for each build of the given project +// +// Gitlab API Docs : https://docs.gitlab.com/ce/api/build_variables.html +type BuildVariable struct { + Key string `json:"key"` + Value string `json:"value"` + Protected bool `json:"protected"` +} + +func (v BuildVariable) String() string { + return Stringify(v) +} + +// ListBuildVariablesOptions are the parameters to ListBuildVariables() +// +// Gitlab API Docs: +// https://docs.gitlab.com/ce/api/build_variables.html#list-project-variables +type ListBuildVariablesOptions ListOptions + +// ListBuildVariables gets the a list of project variables in a project +// +// Gitlab API Docs: +// https://docs.gitlab.com/ce/api/build_variables.html#list-project-variables +func (s *BuildVariablesService) ListBuildVariables(pid interface{}, opts *ListBuildVariablesOptions, options ...OptionFunc) ([]*BuildVariable, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/variables", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opts, options) + if err != nil { + return nil, nil, err + } + + var v []*BuildVariable + resp, err := s.client.Do(req, &v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetBuildVariable gets a single project variable of a project +// +// Gitlab API Docs: +// https://docs.gitlab.com/ce/api/build_variables.html#show-variable-details +func (s *BuildVariablesService) GetBuildVariable(pid interface{}, key string, options ...OptionFunc) (*BuildVariable, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/variables/%s", url.QueryEscape(project), key) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + v := new(BuildVariable) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// CreateBuildVariableOptions are the parameters to CreateBuildVariable() +// +// Gitlab API Docs: +// https://docs.gitlab.com/ce/api/build_variables.html#create-variable +type CreateBuildVariableOptions struct { + Key *string `url:"key" json:"key"` + Value *string `url:"value" json:"value"` + Protected *bool `url:"protected,omitempty" json:"protected,omitempty"` +} + +// CreateBuildVariable creates a variable for a given project +// +// Gitlab API Docs: +// https://docs.gitlab.com/ce/api/build_variables.html#create-variable +func (s *BuildVariablesService) CreateBuildVariable(pid interface{}, opt *CreateBuildVariableOptions, options ...OptionFunc) (*BuildVariable, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/variables", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + v := new(BuildVariable) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// UpdateBuildVariableOptions are the parameters to UpdateBuildVariable() +// +// Gitlab API Docs: +// https://docs.gitlab.com/ce/api/build_variables.html#update-variable +type UpdateBuildVariableOptions struct { + Key *string `url:"key" json:"key"` + Value *string `url:"value" json:"value"` + Protected *bool `url:"protected,omitempty" json:"protected,omitempty"` +} + +// UpdateBuildVariable updates an existing project variable +// The variable key must exist +// +// Gitlab API Docs: +// https://docs.gitlab.com/ce/api/build_variables.html#update-variable +func (s *BuildVariablesService) UpdateBuildVariable(pid interface{}, key string, opt *UpdateBuildVariableOptions, options ...OptionFunc) (*BuildVariable, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/variables/%s", url.QueryEscape(project), key) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + v := new(BuildVariable) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// RemoveBuildVariable removes a project variable of a given project identified by its key +// +// Gitlab API Docs: +// https://docs.gitlab.com/ce/api/build_variables.html#remove-variable +func (s *BuildVariablesService) RemoveBuildVariable(pid interface{}, key string, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/variables/%s", url.QueryEscape(project), key) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/xanzy/go-gitlab/commits.go b/vendor/github.com/xanzy/go-gitlab/commits.go new file mode 100644 index 00000000..5b5dc2a7 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/commits.go @@ -0,0 +1,448 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" + "time" +) + +// CommitsService handles communication with the commit related methods +// of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html +type CommitsService struct { + client *Client +} + +// Commit represents a GitLab commit. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html +type Commit struct { + ID string `json:"id"` + ShortID string `json:"short_id"` + Title string `json:"title"` + AuthorName string `json:"author_name"` + AuthorEmail string `json:"author_email"` + AuthoredDate *time.Time `json:"authored_date"` + CommitterName string `json:"committer_name"` + CommitterEmail string `json:"committer_email"` + CommittedDate *time.Time `json:"committed_date"` + CreatedAt *time.Time `json:"created_at"` + Message string `json:"message"` + ParentIDs []string `json:"parent_ids"` + Stats *CommitStats `json:"stats"` + Status *BuildStateValue `json:"status"` +} + +// CommitStats represents the number of added and deleted files in a commit. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html +type CommitStats struct { + Additions int `json:"additions"` + Deletions int `json:"deletions"` + Total int `json:"total"` +} + +func (c Commit) String() string { + return Stringify(c) +} + +// ListCommitsOptions represents the available ListCommits() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#list-repository-commits +type ListCommitsOptions struct { + ListOptions + RefName *string `url:"ref_name,omitempty" json:"ref_name,omitempty"` + Since *time.Time `url:"since,omitempty" json:"since,omitempty"` + Until *time.Time `url:"until,omitempty" json:"until,omitempty"` + Path *string `url:"path,omitempty" json:"path,omitempty"` + All *bool `url:"all,omitempty" json:"all,omitempty"` +} + +// ListCommits gets a list of repository commits in a project. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#list-commits +func (s *CommitsService) ListCommits(pid interface{}, opt *ListCommitsOptions, options ...OptionFunc) ([]*Commit, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/commits", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var c []*Commit + resp, err := s.client.Do(req, &c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// FileAction represents the available actions that can be performed on a file. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions +type FileAction string + +// The available file actions. +const ( + FileCreate FileAction = "create" + FileDelete FileAction = "delete" + FileMove FileAction = "move" + FileUpdate FileAction = "update" +) + +// CommitAction represents a single file action within a commit. +type CommitAction struct { + Action FileAction `url:"action" json:"action"` + FilePath string `url:"file_path" json:"file_path"` + PreviousPath string `url:"previous_path,omitempty" json:"previous_path,omitempty"` + Content string `url:"content,omitempty" json:"content,omitempty"` + Encoding string `url:"encoding,omitempty" json:"encoding,omitempty"` +} + +// GetCommit gets a specific commit identified by the commit hash or name of a +// branch or tag. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-a-single-commit +func (s *CommitsService) GetCommit(pid interface{}, sha string, options ...OptionFunc) (*Commit, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/commits/%s", url.QueryEscape(project), sha) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + c := new(Commit) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// CreateCommitOptions represents the available options for a new commit. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions +type CreateCommitOptions struct { + Branch *string `url:"branch" json:"branch"` + CommitMessage *string `url:"commit_message" json:"commit_message"` + StartBranch *string `url:"start_branch,omitempty" json:"start_branch,omitempty"` + Actions []*CommitAction `url:"actions" json:"actions"` + AuthorEmail *string `url:"author_email,omitempty" json:"author_email,omitempty"` + AuthorName *string `url:"author_name,omitempty" json:"author_name,omitempty"` +} + +// CreateCommit creates a commit with multiple files and actions. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions +func (s *CommitsService) CreateCommit(pid interface{}, opt *CreateCommitOptions, options ...OptionFunc) (*Commit, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/commits", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + var c *Commit + resp, err := s.client.Do(req, &c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// Diff represents a GitLab diff. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html +type Diff struct { + Diff string `json:"diff"` + NewPath string `json:"new_path"` + OldPath string `json:"old_path"` + AMode string `json:"a_mode"` + BMode string `json:"b_mode"` + NewFile bool `json:"new_file"` + RenamedFile bool `json:"renamed_file"` + DeletedFile bool `json:"deleted_file"` +} + +func (d Diff) String() string { + return Stringify(d) +} + +// GetCommitDiffOptions represents the available GetCommitDiff() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/commits.html#get-the-diff-of-a-commit +type GetCommitDiffOptions ListOptions + +// GetCommitDiff gets the diff of a commit in a project.. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/commits.html#get-the-diff-of-a-commit +func (s *CommitsService) GetCommitDiff(pid interface{}, sha string, opt *GetCommitDiffOptions, options ...OptionFunc) ([]*Diff, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/commits/%s/diff", url.QueryEscape(project), sha) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var d []*Diff + resp, err := s.client.Do(req, &d) + if err != nil { + return nil, resp, err + } + + return d, resp, err +} + +// CommitComment represents a GitLab commit comment. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html +type CommitComment struct { + Note string `json:"note"` + Path string `json:"path"` + Line int `json:"line"` + LineType string `json:"line_type"` + Author Author `json:"author"` +} + +// Author represents a GitLab commit author +type Author struct { + ID int `json:"id"` + Username string `json:"username"` + Email string `json:"email"` + Name string `json:"name"` + State string `json:"state"` + Blocked bool `json:"blocked"` + CreatedAt *time.Time `json:"created_at"` +} + +func (c CommitComment) String() string { + return Stringify(c) +} + +// GetCommitCommentsOptions represents the available GetCommitComments() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/commits.html#get-the-comments-of-a-commit +type GetCommitCommentsOptions ListOptions + +// GetCommitComments gets the comments of a commit in a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/commits.html#get-the-comments-of-a-commit +func (s *CommitsService) GetCommitComments(pid interface{}, sha string, opt *GetCommitCommentsOptions, options ...OptionFunc) ([]*CommitComment, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/commits/%s/comments", url.QueryEscape(project), sha) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var c []*CommitComment + resp, err := s.client.Do(req, &c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// PostCommitCommentOptions represents the available PostCommitComment() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/commits.html#post-comment-to-commit +type PostCommitCommentOptions struct { + Note *string `url:"note,omitempty" json:"note,omitempty"` + Path *string `url:"path" json:"path"` + Line *int `url:"line" json:"line"` + LineType *string `url:"line_type" json:"line_type"` +} + +// PostCommitComment adds a comment to a commit. Optionally you can post +// comments on a specific line of a commit. Therefor both path, line_new and +// line_old are required. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/commits.html#post-comment-to-commit +func (s *CommitsService) PostCommitComment(pid interface{}, sha string, opt *PostCommitCommentOptions, options ...OptionFunc) (*CommitComment, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/commits/%s/comments", url.QueryEscape(project), sha) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + c := new(CommitComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// GetCommitStatusesOptions represents the available GetCommitStatuses() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-the-status-of-a-commit +type GetCommitStatusesOptions struct { + ListOptions + Ref *string `url:"ref,omitempty" json:"ref,omitempty"` + Stage *string `url:"stage,omitempty" json:"stage,omitempty"` + Name *string `url:"name,omitempty" json:"name,omitempty"` + All *bool `url:"all,omitempty" json:"all,omitempty"` +} + +// CommitStatus represents a GitLab commit status. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-the-status-of-a-commit +type CommitStatus struct { + ID int `json:"id"` + SHA string `json:"sha"` + Ref string `json:"ref"` + Status string `json:"status"` + Name string `json:"name"` + TargetURL string `json:"target_url"` + Description string `json:"description"` + CreatedAt *time.Time `json:"created_at"` + StartedAt *time.Time `json:"started_at"` + FinishedAt *time.Time `json:"finished_at"` + Author Author `json:"author"` +} + +// GetCommitStatuses gets the statuses of a commit in a project. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-the-status-of-a-commit +func (s *CommitsService) GetCommitStatuses(pid interface{}, sha string, opt *GetCommitStatusesOptions, options ...OptionFunc) ([]*CommitStatus, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/commits/%s/statuses", url.QueryEscape(project), sha) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var cs []*CommitStatus + resp, err := s.client.Do(req, &cs) + if err != nil { + return nil, resp, err + } + + return cs, resp, err +} + +// SetCommitStatusOptions represents the available SetCommitStatus() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#post-the-status-to-commit +type SetCommitStatusOptions struct { + State BuildStateValue `url:"state" json:"state"` + Ref *string `url:"ref,omitempty" json:"ref,omitempty"` + Name *string `url:"name,omitempty" json:"name,omitempty"` + Context *string `url:"context,omitempty" json:"context,omitempty"` + TargetURL *string `url:"target_url,omitempty" json:"target_url,omitempty"` + Description *string `url:"description,omitempty" json:"description,omitempty"` +} + +// SetCommitStatus sets the status of a commit in a project. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#post-the-status-to-commit +func (s *CommitsService) SetCommitStatus(pid interface{}, sha string, opt *SetCommitStatusOptions, options ...OptionFunc) (*CommitStatus, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/statuses/%s", url.QueryEscape(project), sha) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + var cs *CommitStatus + resp, err := s.client.Do(req, &cs) + if err != nil { + return nil, resp, err + } + + return cs, resp, err +} + +// CherryPickCommitOptions represents the available options for cherry-picking a commit. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#cherry-pick-a-commit +type CherryPickCommitOptions struct { + TargetBranch *string `url:"branch" json:"branch,omitempty"` +} + +// CherryPickCommit sherry picks a commit to a given branch. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#cherry-pick-a-commit +func (s *CommitsService) CherryPickCommit(pid interface{}, sha string, opt *CherryPickCommitOptions, options ...OptionFunc) (*Commit, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/commits/%s/cherry_pick", + url.QueryEscape(project), url.QueryEscape(sha)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + var c *Commit + resp, err := s.client.Do(req, &c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/deploy_keys.go b/vendor/github.com/xanzy/go-gitlab/deploy_keys.go new file mode 100644 index 00000000..76444592 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/deploy_keys.go @@ -0,0 +1,201 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" + "time" +) + +// DeployKeysService handles communication with the keys related methods +// of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/deploy_keys.html +type DeployKeysService struct { + client *Client +} + +// DeployKey represents a GitLab deploy key. +type DeployKey struct { + ID int `json:"id"` + Title string `json:"title"` + Key string `json:"key"` + CanPush *bool `json:"can_push"` + CreatedAt *time.Time `json:"created_at"` +} + +func (k DeployKey) String() string { + return Stringify(k) +} + +// ListAllDeployKeys gets a list of all deploy keys +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/deploy_keys.html#list-all-deploy-keys +func (s *DeployKeysService) ListAllDeployKeys(options ...OptionFunc) ([]*DeployKey, *Response, error) { + req, err := s.client.NewRequest("GET", "deploy_keys", nil, options) + if err != nil { + return nil, nil, err + } + + var ks []*DeployKey + resp, err := s.client.Do(req, &ks) + if err != nil { + return nil, resp, err + } + + return ks, resp, err +} + +// ListProjectDeployKeysOptions represents the available ListProjectDeployKeys() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/deploy_keys.html#list-project-deploy-keys +type ListProjectDeployKeysOptions ListOptions + +// ListProjectDeployKeys gets a list of a project's deploy keys +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/deploy_keys.html#list-project-deploy-keys +func (s *DeployKeysService) ListProjectDeployKeys(pid interface{}, opt *ListProjectDeployKeysOptions, options ...OptionFunc) ([]*DeployKey, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/deploy_keys", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var ks []*DeployKey + resp, err := s.client.Do(req, &ks) + if err != nil { + return nil, resp, err + } + + return ks, resp, err +} + +// GetDeployKey gets a single deploy key. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/deploy_keys.html#single-deploy-key +func (s *DeployKeysService) GetDeployKey(pid interface{}, deployKey int, options ...OptionFunc) (*DeployKey, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/deploy_keys/%d", url.QueryEscape(project), deployKey) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + k := new(DeployKey) + resp, err := s.client.Do(req, k) + if err != nil { + return nil, resp, err + } + + return k, resp, err +} + +// AddDeployKeyOptions represents the available ADDDeployKey() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/deploy_keys.html#add-deploy-key +type AddDeployKeyOptions struct { + Title *string `url:"title,omitempty" json:"title,omitempty"` + Key *string `url:"key,omitempty" json:"key,omitempty"` + CanPush *bool `url:"can_push,omitempty" json:"can_push,omitempty"` +} + +// AddDeployKey creates a new deploy key for a project. If deploy key already +// exists in another project - it will be joined to project but only if +// original one was is accessible by same user. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/deploy_keys.html#add-deploy-key +func (s *DeployKeysService) AddDeployKey(pid interface{}, opt *AddDeployKeyOptions, options ...OptionFunc) (*DeployKey, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/deploy_keys", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + k := new(DeployKey) + resp, err := s.client.Do(req, k) + if err != nil { + return nil, resp, err + } + + return k, resp, err +} + +// DeleteDeployKey deletes a deploy key from a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/deploy_keys.html#delete-deploy-key +func (s *DeployKeysService) DeleteDeployKey(pid interface{}, deployKey int, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/deploy_keys/%d", url.QueryEscape(project), deployKey) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// EnableDeployKey enables a deploy key. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/deploy_keys.html#enable-deploy-key +func (s *DeployKeysService) EnableDeployKey(pid interface{}, deployKey int, options ...OptionFunc) (*DeployKey, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/deploy_keys/%d/enable", url.QueryEscape(project), deployKey) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + k := new(DeployKey) + resp, err := s.client.Do(req, k) + if err != nil { + return nil, resp, err + } + + return k, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/deployments.go b/vendor/github.com/xanzy/go-gitlab/deployments.go new file mode 100644 index 00000000..a648605e --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/deployments.go @@ -0,0 +1,121 @@ +// +// Copyright 2018, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gitlab + +import ( + "fmt" + "net/url" + "time" +) + +// DeploymentsService handles communication with the deployment related methods +// of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/deployments.html +type DeploymentsService struct { + client *Client +} + +// Deployment represents the Gitlab deployment +type Deployment struct { + ID int `json:"id"` + IID int `json:"iid"` + Ref string `json:"ref"` + Sha string `json:"sha"` + CreatedAt *time.Time `json:"created_at"` + User *ProjectUser `json:"user"` + Environment *Environment `json:"environment"` + Deployable struct { + ID int `json:"id"` + Status string `json:"status"` + Stage string `json:"stage"` + Name string `json:"name"` + Ref string `json:"ref"` + Tag bool `json:"tag"` + Coverage float64 `json:"coverage"` + CreatedAt *time.Time `json:"created_at"` + StartedAt *time.Time `json:"started_at"` + FinishedAt *time.Time `json:"finished_at"` + Duration float64 `json:"duration"` + User *User `json:"user"` + Commit *Commit `json:"commit"` + Pipeline struct { + ID int `json:"id"` + Sha string `json:"sha"` + Ref string `json:"ref"` + Status string `json:"status"` + } `json:"pipeline"` + Runner *Runner `json:"runner"` + } `json:"deployable"` +} + +// ListProjectDeploymentsOptions represents the available ListProjectDeployments() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/deployments.html#list-project-deployments +type ListProjectDeploymentsOptions struct { + ListOptions + OrderBy *OrderByValue `url:"order_by,omitempty" json:"order_by,omitempty"` + Sort *string `url:"sort,omitempty" json:"sort,omitempty"` +} + +// ListProjectDeployments gets a list of deployments in a project. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/deployments.html#list-project-deployments +func (s *DeploymentsService) ListProjectDeployments(pid interface{}, opts *ListProjectDeploymentsOptions, options ...OptionFunc) ([]*Deployment, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/deployments", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opts, options) + if err != nil { + return nil, nil, err + } + + var ds []*Deployment + resp, err := s.client.Do(req, &ds) + if err != nil { + return nil, resp, err + } + + return ds, resp, err +} + +// GetProjectDeployment get a deployment for a project. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/deployments.html#get-a-specific-deployment +func (s *DeploymentsService) GetProjectDeployment(pid interface{}, deployment int, options ...OptionFunc) (*Deployment, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/deployments/%d", url.QueryEscape(project), deployment) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + d := new(Deployment) + resp, err := s.client.Do(req, d) + if err != nil { + return nil, resp, err + } + + return d, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/environments.go b/vendor/github.com/xanzy/go-gitlab/environments.go new file mode 100644 index 00000000..c2305ee9 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/environments.go @@ -0,0 +1,166 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" +) + +// EnvironmentsService handles communication with the environment related methods +// of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/environments.html +type EnvironmentsService struct { + client *Client +} + +// Environment represents a GitLab environment. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/environments.html +type Environment struct { + ID int `json:"id"` + Name string `json:"name"` + Slug string `json:"slug"` + ExternalURL string `json:"external_url"` +} + +func (env Environment) String() string { + return Stringify(env) +} + +// ListEnvironmentsOptions represents the available ListEnvironments() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/environments.html#list-environments +type ListEnvironmentsOptions ListOptions + +// ListEnvironments gets a list of environments from a project, sorted by name +// alphabetically. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/environments.html#list-environments +func (s *EnvironmentsService) ListEnvironments(pid interface{}, opts *ListEnvironmentsOptions, options ...OptionFunc) ([]*Environment, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/environments", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opts, options) + if err != nil { + return nil, nil, err + } + + var envs []*Environment + resp, err := s.client.Do(req, &envs) + if err != nil { + return nil, resp, err + } + + return envs, resp, err +} + +// CreateEnvironmentOptions represents the available CreateEnvironment() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/environments.html#create-a-new-environment +type CreateEnvironmentOptions struct { + Name *string `url:"name,omitempty" json:"name,omitempty"` + ExternalURL *string `url:"external_url,omitempty" json:"external_url,omitempty"` +} + +// CreateEnvironment adds a environment to a project. This is an idempotent +// method and can be called multiple times with the same parameters. Createing +// an environment that is already a environment does not affect the +// existing environmentship. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/environments.html#create-a-new-environment +func (s *EnvironmentsService) CreateEnvironment(pid interface{}, opt *CreateEnvironmentOptions, options ...OptionFunc) (*Environment, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/environments", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + env := new(Environment) + resp, err := s.client.Do(req, env) + if err != nil { + return nil, resp, err + } + + return env, resp, err +} + +// EditEnvironmentOptions represents the available EditEnvironment() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/environments.html#edit-an-existing-environment +type EditEnvironmentOptions struct { + Name *string `url:"name,omitempty" json:"name,omitempty"` + ExternalURL *string `url:"external_url,omitempty" json:"external_url,omitempty"` +} + +// EditEnvironment updates a project team environment to a specified access level.. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/environments.html#edit-an-existing-environment +func (s *EnvironmentsService) EditEnvironment(pid interface{}, environment int, opt *EditEnvironmentOptions, options ...OptionFunc) (*Environment, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/environments/%d", url.QueryEscape(project), environment) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + env := new(Environment) + resp, err := s.client.Do(req, env) + if err != nil { + return nil, resp, err + } + + return env, resp, err +} + +// DeleteEnvironment removes a environment from a project team. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/environments.html#remove-a-environment-from-a-group-or-project +func (s *EnvironmentsService) DeleteEnvironment(pid interface{}, environment int, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/environments/%d", url.QueryEscape(project), environment) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/xanzy/go-gitlab/event_types.go b/vendor/github.com/xanzy/go-gitlab/event_types.go new file mode 100644 index 00000000..f051726e --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/event_types.go @@ -0,0 +1,649 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "time" +) + +// PushEvent represents a push event. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#push-events +type PushEvent struct { + ObjectKind string `json:"object_kind"` + Before string `json:"before"` + After string `json:"after"` + Ref string `json:"ref"` + CheckoutSha string `json:"checkout_sha"` + UserID int `json:"user_id"` + UserName string `json:"user_name"` + UserEmail string `json:"user_email"` + UserAvatar string `json:"user_avatar"` + ProjectID int `json:"project_id"` + Project struct { + Name string `json:"name"` + Description string `json:"description"` + AvatarURL string `json:"avatar_url"` + GitSSHURL string `json:"git_ssh_url"` + GitHTTPURL string `json:"git_http_url"` + Namespace string `json:"namespace"` + PathWithNamespace string `json:"path_with_namespace"` + DefaultBranch string `json:"default_branch"` + Homepage string `json:"homepage"` + URL string `json:"url"` + SSHURL string `json:"ssh_url"` + HTTPURL string `json:"http_url"` + WebURL string `json:"web_url"` + Visibility VisibilityValue `json:"visibility"` + } `json:"project"` + Repository *Repository `json:"repository"` + Commits []*struct { + ID string `json:"id"` + Message string `json:"message"` + Timestamp *time.Time `json:"timestamp"` + URL string `json:"url"` + Author struct { + Name string `json:"name"` + Email string `json:"email"` + } `json:"author"` + Added []string `json:"added"` + Modified []string `json:"modified"` + Removed []string `json:"removed"` + } `json:"commits"` + TotalCommitsCount int `json:"total_commits_count"` +} + +// TagEvent represents a tag event. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#tag-events +type TagEvent struct { + ObjectKind string `json:"object_kind"` + Before string `json:"before"` + After string `json:"after"` + Ref string `json:"ref"` + CheckoutSha string `json:"checkout_sha"` + UserID int `json:"user_id"` + UserName string `json:"user_name"` + UserAvatar string `json:"user_avatar"` + ProjectID int `json:"project_id"` + Project struct { + Name string `json:"name"` + Description string `json:"description"` + AvatarURL string `json:"avatar_url"` + GitSSHURL string `json:"git_ssh_url"` + GitHTTPURL string `json:"git_http_url"` + Namespace string `json:"namespace"` + PathWithNamespace string `json:"path_with_namespace"` + DefaultBranch string `json:"default_branch"` + Homepage string `json:"homepage"` + URL string `json:"url"` + SSHURL string `json:"ssh_url"` + HTTPURL string `json:"http_url"` + WebURL string `json:"web_url"` + Visibility VisibilityValue `json:"visibility"` + } `json:"project"` + Repository *Repository `json:"repository"` + Commits []*struct { + ID string `json:"id"` + Message string `json:"message"` + Timestamp *time.Time `json:"timestamp"` + URL string `json:"url"` + Author struct { + Name string `json:"name"` + Email string `json:"email"` + } `json:"author"` + Added []string `json:"added"` + Modified []string `json:"modified"` + Removed []string `json:"removed"` + } `json:"commits"` + TotalCommitsCount int `json:"total_commits_count"` +} + +// IssueEvent represents a issue event. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#issues-events +type IssueEvent struct { + ObjectKind string `json:"object_kind"` + User *User `json:"user"` + Project struct { + Name string `json:"name"` + Description string `json:"description"` + AvatarURL string `json:"avatar_url"` + GitSSHURL string `json:"git_ssh_url"` + GitHTTPURL string `json:"git_http_url"` + Namespace string `json:"namespace"` + PathWithNamespace string `json:"path_with_namespace"` + DefaultBranch string `json:"default_branch"` + Homepage string `json:"homepage"` + URL string `json:"url"` + SSHURL string `json:"ssh_url"` + HTTPURL string `json:"http_url"` + WebURL string `json:"web_url"` + Visibility VisibilityValue `json:"visibility"` + } `json:"project"` + Repository *Repository `json:"repository"` + ObjectAttributes struct { + ID int `json:"id"` + Title string `json:"title"` + AssigneeID int `json:"assignee_id"` + AuthorID int `json:"author_id"` + ProjectID int `json:"project_id"` + CreatedAt string `json:"created_at"` // Should be *time.Time (see Gitlab issue #21468) + UpdatedAt string `json:"updated_at"` // Should be *time.Time (see Gitlab issue #21468) + Position int `json:"position"` + BranchName string `json:"branch_name"` + Description string `json:"description"` + MilestoneID int `json:"milestone_id"` + State string `json:"state"` + IID int `json:"iid"` + URL string `json:"url"` + Action string `json:"action"` + } `json:"object_attributes"` + Assignee struct { + Name string `json:"name"` + Username string `json:"username"` + AvatarURL string `json:"avatar_url"` + } `json:"assignee"` +} + +// CommitCommentEvent represents a comment on a commit event. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#comment-on-commit +type CommitCommentEvent struct { + ObjectKind string `json:"object_kind"` + User *User `json:"user"` + ProjectID int `json:"project_id"` + Project struct { + Name string `json:"name"` + Description string `json:"description"` + AvatarURL string `json:"avatar_url"` + GitSSHURL string `json:"git_ssh_url"` + GitHTTPURL string `json:"git_http_url"` + Namespace string `json:"namespace"` + PathWithNamespace string `json:"path_with_namespace"` + DefaultBranch string `json:"default_branch"` + Homepage string `json:"homepage"` + URL string `json:"url"` + SSHURL string `json:"ssh_url"` + HTTPURL string `json:"http_url"` + WebURL string `json:"web_url"` + Visibility VisibilityValue `json:"visibility"` + } `json:"project"` + Repository *Repository `json:"repository"` + ObjectAttributes struct { + ID int `json:"id"` + Note string `json:"note"` + NoteableType string `json:"noteable_type"` + AuthorID int `json:"author_id"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + ProjectID int `json:"project_id"` + Attachment string `json:"attachment"` + LineCode string `json:"line_code"` + CommitID string `json:"commit_id"` + NoteableID int `json:"noteable_id"` + System bool `json:"system"` + StDiff struct { + Diff string `json:"diff"` + NewPath string `json:"new_path"` + OldPath string `json:"old_path"` + AMode string `json:"a_mode"` + BMode string `json:"b_mode"` + NewFile bool `json:"new_file"` + RenamedFile bool `json:"renamed_file"` + DeletedFile bool `json:"deleted_file"` + } `json:"st_diff"` + } `json:"object_attributes"` + Commit *struct { + ID string `json:"id"` + Message string `json:"message"` + Timestamp *time.Time `json:"timestamp"` + URL string `json:"url"` + Author struct { + Name string `json:"name"` + Email string `json:"email"` + } `json:"author"` + } `json:"commit"` +} + +// MergeCommentEvent represents a comment on a merge event. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#comment-on-merge-request +type MergeCommentEvent struct { + ObjectKind string `json:"object_kind"` + User *User `json:"user"` + ProjectID int `json:"project_id"` + Project struct { + Name string `json:"name"` + Description string `json:"description"` + AvatarURL string `json:"avatar_url"` + GitSSHURL string `json:"git_ssh_url"` + GitHTTPURL string `json:"git_http_url"` + Namespace string `json:"namespace"` + PathWithNamespace string `json:"path_with_namespace"` + DefaultBranch string `json:"default_branch"` + Homepage string `json:"homepage"` + URL string `json:"url"` + SSHURL string `json:"ssh_url"` + HTTPURL string `json:"http_url"` + WebURL string `json:"web_url"` + Visibility VisibilityValue `json:"visibility"` + } `json:"project"` + ObjectAttributes struct { + ID int `json:"id"` + Note string `json:"note"` + NoteableType string `json:"noteable_type"` + AuthorID int `json:"author_id"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + ProjectID int `json:"project_id"` + Attachment string `json:"attachment"` + LineCode string `json:"line_code"` + CommitID string `json:"commit_id"` + NoteableID int `json:"noteable_id"` + System bool `json:"system"` + StDiff *Diff `json:"st_diff"` + URL string `json:"url"` + } `json:"object_attributes"` + Repository *Repository `json:"repository"` + MergeRequest struct { + ID int `json:"id"` + TargetBranch string `json:"target_branch"` + SourceBranch string `json:"source_branch"` + SourceProjectID int `json:"source_project_id"` + AuthorID int `json:"author_id"` + AssigneeID int `json:"assignee_id"` + Title string `json:"title"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + MilestoneID int `json:"milestone_id"` + State string `json:"state"` + MergeStatus string `json:"merge_status"` + TargetProjectID int `json:"target_project_id"` + IID int `json:"iid"` + Description string `json:"description"` + Position int `json:"position"` + LockedAt string `json:"locked_at"` + UpdatedByID int `json:"updated_by_id"` + MergeError string `json:"merge_error"` + MergeParams struct { + ForceRemoveSourceBranch string `json:"force_remove_source_branch"` + } `json:"merge_params"` + MergeWhenPipelineSucceeds bool `json:"merge_when_pipeline_succeeds"` + MergeUserID int `json:"merge_user_id"` + MergeCommitSha string `json:"merge_commit_sha"` + DeletedAt string `json:"deleted_at"` + InProgressMergeCommitSha string `json:"in_progress_merge_commit_sha"` + LockVersion int `json:"lock_version"` + ApprovalsBeforeMerge string `json:"approvals_before_merge"` + RebaseCommitSha string `json:"rebase_commit_sha"` + TimeEstimate int `json:"time_estimate"` + Squash bool `json:"squash"` + LastEditedAt string `json:"last_edited_at"` + LastEditedByID int `json:"last_edited_by_id"` + Source *Repository `json:"source"` + Target *Repository `json:"target"` + LastCommit struct { + ID string `json:"id"` + Message string `json:"message"` + Timestamp *time.Time `json:"timestamp"` + URL string `json:"url"` + Author struct { + Name string `json:"name"` + Email string `json:"email"` + } `json:"author"` + } `json:"last_commit"` + WorkInProgress bool `json:"work_in_progress"` + TotalTimeSpent int `json:"total_time_spent"` + } `json:"merge_request"` +} + +// IssueCommentEvent represents a comment on an issue event. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#comment-on-issue +type IssueCommentEvent struct { + ObjectKind string `json:"object_kind"` + User *User `json:"user"` + ProjectID int `json:"project_id"` + Project struct { + Name string `json:"name"` + Description string `json:"description"` + AvatarURL string `json:"avatar_url"` + GitSSHURL string `json:"git_ssh_url"` + GitHTTPURL string `json:"git_http_url"` + Namespace string `json:"namespace"` + PathWithNamespace string `json:"path_with_namespace"` + DefaultBranch string `json:"default_branch"` + Homepage string `json:"homepage"` + URL string `json:"url"` + SSHURL string `json:"ssh_url"` + HTTPURL string `json:"http_url"` + WebURL string `json:"web_url"` + Visibility VisibilityValue `json:"visibility"` + } `json:"project"` + Repository *Repository `json:"repository"` + ObjectAttributes struct { + ID int `json:"id"` + Note string `json:"note"` + NoteableType string `json:"noteable_type"` + AuthorID int `json:"author_id"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + ProjectID int `json:"project_id"` + Attachment string `json:"attachment"` + LineCode string `json:"line_code"` + CommitID string `json:"commit_id"` + NoteableID int `json:"noteable_id"` + System bool `json:"system"` + StDiff []*Diff `json:"st_diff"` + URL string `json:"url"` + } `json:"object_attributes"` + Issue *Issue `json:"issue"` +} + +// SnippetCommentEvent represents a comment on a snippet event. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#comment-on-code-snippet +type SnippetCommentEvent struct { + ObjectKind string `json:"object_kind"` + User *User `json:"user"` + ProjectID int `json:"project_id"` + Project struct { + Name string `json:"name"` + Description string `json:"description"` + AvatarURL string `json:"avatar_url"` + GitSSHURL string `json:"git_ssh_url"` + GitHTTPURL string `json:"git_http_url"` + Namespace string `json:"namespace"` + PathWithNamespace string `json:"path_with_namespace"` + DefaultBranch string `json:"default_branch"` + Homepage string `json:"homepage"` + URL string `json:"url"` + SSHURL string `json:"ssh_url"` + HTTPURL string `json:"http_url"` + WebURL string `json:"web_url"` + Visibility VisibilityValue `json:"visibility"` + } `json:"project"` + Repository *Repository `json:"repository"` + ObjectAttributes struct { + ID int `json:"id"` + Note string `json:"note"` + NoteableType string `json:"noteable_type"` + AuthorID int `json:"author_id"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + ProjectID int `json:"project_id"` + Attachment string `json:"attachment"` + LineCode string `json:"line_code"` + CommitID string `json:"commit_id"` + NoteableID int `json:"noteable_id"` + System bool `json:"system"` + StDiff *Diff `json:"st_diff"` + URL string `json:"url"` + } `json:"object_attributes"` + Snippet *Snippet `json:"snippet"` +} + +// MergeEvent represents a merge event. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#merge-request-events +type MergeEvent struct { + ObjectKind string `json:"object_kind"` + User *User `json:"user"` + Project struct { + Name string `json:"name"` + Description string `json:"description"` + AvatarURL string `json:"avatar_url"` + GitSSHURL string `json:"git_ssh_url"` + GitHTTPURL string `json:"git_http_url"` + Namespace string `json:"namespace"` + PathWithNamespace string `json:"path_with_namespace"` + DefaultBranch string `json:"default_branch"` + Homepage string `json:"homepage"` + URL string `json:"url"` + SSHURL string `json:"ssh_url"` + HTTPURL string `json:"http_url"` + WebURL string `json:"web_url"` + Visibility VisibilityValue `json:"visibility"` + } `json:"project"` + ObjectAttributes struct { + ID int `json:"id"` + TargetBranch string `json:"target_branch"` + SourceBranch string `json:"source_branch"` + SourceProjectID int `json:"source_project_id"` + AuthorID int `json:"author_id"` + AssigneeID int `json:"assignee_id"` + Title string `json:"title"` + CreatedAt string `json:"created_at"` // Should be *time.Time (see Gitlab issue #21468) + UpdatedAt string `json:"updated_at"` // Should be *time.Time (see Gitlab issue #21468) + StCommits []*Commit `json:"st_commits"` + StDiffs []*Diff `json:"st_diffs"` + MilestoneID int `json:"milestone_id"` + State string `json:"state"` + MergeStatus string `json:"merge_status"` + TargetProjectID int `json:"target_project_id"` + IID int `json:"iid"` + Description string `json:"description"` + Position int `json:"position"` + LockedAt string `json:"locked_at"` + UpdatedByID int `json:"updated_by_id"` + MergeError string `json:"merge_error"` + MergeParams struct { + ForceRemoveSourceBranch string `json:"force_remove_source_branch"` + } `json:"merge_params"` + MergeWhenBuildSucceeds bool `json:"merge_when_build_succeeds"` + MergeUserID int `json:"merge_user_id"` + MergeCommitSha string `json:"merge_commit_sha"` + DeletedAt string `json:"deleted_at"` + ApprovalsBeforeMerge string `json:"approvals_before_merge"` + RebaseCommitSha string `json:"rebase_commit_sha"` + InProgressMergeCommitSha string `json:"in_progress_merge_commit_sha"` + LockVersion int `json:"lock_version"` + TimeEstimate int `json:"time_estimate"` + Source *Repository `json:"source"` + Target *Repository `json:"target"` + LastCommit struct { + ID string `json:"id"` + Message string `json:"message"` + Timestamp *time.Time `json:"timestamp"` + URL string `json:"url"` + Author struct { + Name string `json:"name"` + Email string `json:"email"` + } `json:"author"` + } `json:"last_commit"` + WorkInProgress bool `json:"work_in_progress"` + URL string `json:"url"` + Action string `json:"action"` + Assignee struct { + Name string `json:"name"` + Username string `json:"username"` + AvatarURL string `json:"avatar_url"` + } `json:"assignee"` + } `json:"object_attributes"` + Repository *Repository `json:"repository"` + Assignee struct { + Name string `json:"name"` + Username string `json:"username"` + AvatarURL string `json:"avatar_url"` + } `json:"assignee"` +} + +// WikiPageEvent represents a wiki page event. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#wiki-page-events +type WikiPageEvent struct { + ObjectKind string `json:"object_kind"` + User *User `json:"user"` + Project struct { + Name string `json:"name"` + Description string `json:"description"` + AvatarURL string `json:"avatar_url"` + GitSSHURL string `json:"git_ssh_url"` + GitHTTPURL string `json:"git_http_url"` + Namespace string `json:"namespace"` + PathWithNamespace string `json:"path_with_namespace"` + DefaultBranch string `json:"default_branch"` + Homepage string `json:"homepage"` + URL string `json:"url"` + SSHURL string `json:"ssh_url"` + HTTPURL string `json:"http_url"` + WebURL string `json:"web_url"` + Visibility VisibilityValue `json:"visibility"` + } `json:"project"` + Wiki struct { + WebURL string `json:"web_url"` + GitSSHURL string `json:"git_ssh_url"` + GitHTTPURL string `json:"git_http_url"` + PathWithNamespace string `json:"path_with_namespace"` + DefaultBranch string `json:"default_branch"` + } `json:"wiki"` + ObjectAttributes struct { + Title string `json:"title"` + Content string `json:"content"` + Format string `json:"format"` + Message string `json:"message"` + Slug string `json:"slug"` + URL string `json:"url"` + Action string `json:"action"` + } `json:"object_attributes"` +} + +// PipelineEvent represents a pipeline event. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#pipeline-events +type PipelineEvent struct { + ObjectKind string `json:"object_kind"` + ObjectAttributes struct { + ID int `json:"id"` + Ref string `json:"ref"` + Tag bool `json:"tag"` + Sha string `json:"sha"` + BeforeSha string `json:"before_sha"` + Status string `json:"status"` + Stages []string `json:"stages"` + CreatedAt string `json:"created_at"` + FinishedAt string `json:"finished_at"` + Duration int `json:"duration"` + } `json:"object_attributes"` + User struct { + Name string `json:"name"` + Username string `json:"username"` + AvatarURL string `json:"avatar_url"` + } `json:"user"` + Project struct { + Name string `json:"name"` + Description string `json:"description"` + AvatarURL string `json:"avatar_url"` + GitSSHURL string `json:"git_ssh_url"` + GitHTTPURL string `json:"git_http_url"` + Namespace string `json:"namespace"` + PathWithNamespace string `json:"path_with_namespace"` + DefaultBranch string `json:"default_branch"` + Homepage string `json:"homepage"` + URL string `json:"url"` + SSHURL string `json:"ssh_url"` + HTTPURL string `json:"http_url"` + WebURL string `json:"web_url"` + Visibility VisibilityValue `json:"visibility"` + } `json:"project"` + Commit struct { + ID string `json:"id"` + Message string `json:"message"` + Timestamp *time.Time `json:"timestamp"` + URL string `json:"url"` + Author struct { + Name string `json:"name"` + Email string `json:"email"` + } `json:"author"` + } `json:"commit"` + Builds []struct { + ID int `json:"id"` + Stage string `json:"stage"` + Name string `json:"name"` + Status string `json:"status"` + CreatedAt string `json:"created_at"` + StartedAt string `json:"started_at"` + FinishedAt string `json:"finished_at"` + When string `json:"when"` + Manual bool `json:"manual"` + User struct { + Name string `json:"name"` + Username string `json:"username"` + AvatarURL string `json:"avatar_url"` + } `json:"user"` + Runner struct { + ID int `json:"id"` + Description string `json:"description"` + Active bool `json:"active"` + IsShared bool `json:"is_shared"` + } `json:"runner"` + ArtifactsFile struct { + Filename string `json:"filename"` + Size int `json:"size"` + } `json:"artifacts_file"` + } `json:"builds"` +} + +//BuildEvent represents a build event +// +// GitLab API docs: +// https://docs.gitlab.com/ce/web_hooks/web_hooks.html#build-events +type BuildEvent struct { + ObjectKind string `json:"object_kind"` + Ref string `json:"ref"` + Tag bool `json:"tag"` + BeforeSha string `json:"before_sha"` + Sha string `json:"sha"` + BuildID int `json:"build_id"` + BuildName string `json:"build_name"` + BuildStage string `json:"build_stage"` + BuildStatus string `json:"build_status"` + BuildStartedAt string `json:"build_started_at"` + BuildFinishedAt string `json:"build_finished_at"` + BuildDuration float64 `json:"build_duration"` + BuildAllowFailure bool `json:"build_allow_failure"` + ProjectID int `json:"project_id"` + ProjectName string `json:"project_name"` + User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + } `json:"user"` + Commit struct { + ID int `json:"id"` + Sha string `json:"sha"` + Message string `json:"message"` + AuthorName string `json:"author_name"` + AuthorEmail string `json:"author_email"` + Status string `json:"status"` + Duration int `json:"duration"` + StartedAt string `json:"started_at"` + FinishedAt string `json:"finished_at"` + } `json:"commit"` + Repository *Repository `json:"repository"` +} diff --git a/vendor/github.com/xanzy/go-gitlab/events.go b/vendor/github.com/xanzy/go-gitlab/events.go new file mode 100644 index 00000000..4740e4ab --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/events.go @@ -0,0 +1,147 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" + "time" +) + +// EventsService handles communication with the event related methods of +// the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/events.html +type EventsService struct { + client *Client +} + +// ContributionEvent represents a user's contribution +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/events.html#get-user-contribution-events +type ContributionEvent struct { + Title string `json:"title"` + ProjectID int `json:"project_id"` + ActionName string `json:"action_name"` + TargetID int `json:"target_id"` + TargetIID int `json:"target_iid"` + TargetType string `json:"target_type"` + AuthorID int `json:"author_id"` + TargetTitle string `json:"target_title"` + CreatedAt *time.Time `json:"created_at"` + PushData struct { + CommitCount int `json:"commit_count"` + Action string `json:"action"` + RefType string `json:"ref_type"` + CommitFrom string `json:"commit_from"` + CommitTo string `json:"commit_to"` + Ref string `json:"ref"` + CommitTitle string `json:"commit_title"` + } `json:"push_data"` + Note *Note `json:"note"` + Author struct { + Name string `json:"name"` + Username string `json:"username"` + ID int `json:"id"` + State string `json:"state"` + AvatarURL string `json:"avatar_url"` + WebURL string `json:"web_url"` + } `json:"author"` + AuthorUsername string `json:"author_username"` +} + +// ListContributionEventsOptions represents the options for GetUserContributionEvents +// +// GitLap API docs: +// https://docs.gitlab.com/ce/api/events.html#get-user-contribution-events +type ListContributionEventsOptions struct { + ListOptions + Action *EventTypeValue `json:"action,omitempty"` + TargetType *EventTargetTypeValue `json:"target_type,omitempty"` + Before *ISOTime `json:"before,omitempty"` + After *ISOTime `json:"after,omitempty"` + Sort *string `json:"sort,omitempty"` +} + +// ListUserContributionEvents retrieves user contribution events +// for the specified user, sorted from newest to oldest. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/events.html#get-user-contribution-events +func (s *UsersService) ListUserContributionEvents(uid interface{}, opt *ListContributionEventsOptions, options ...OptionFunc) ([]*ContributionEvent, *Response, error) { + user, err := parseID(uid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("users/%s/events", user) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var cs []*ContributionEvent + resp, err := s.client.Do(req, &cs) + if err != nil { + return nil, resp, err + } + + return cs, resp, err +} + +// ListCurrentUserContributionEvents gets a list currently authenticated user's events +// +// GitLab API docs: https://docs.gitlab.com/ce/api/events.html#list-currently-authenticated-user-39-s-events +func (s *EventsService) ListCurrentUserContributionEvents(opt *ListContributionEventsOptions, options ...OptionFunc) ([]*ContributionEvent, *Response, error) { + req, err := s.client.NewRequest("GET", "events", opt, options) + if err != nil { + return nil, nil, err + } + + var cs []*ContributionEvent + resp, err := s.client.Do(req, &cs) + if err != nil { + return nil, resp, err + } + + return cs, resp, err +} + +// ListProjectContributionEvents gets a list currently authenticated user's events +// +// GitLab API docs: https://docs.gitlab.com/ce/api/events.html#list-a-project-39-s-visible-events +func (s *EventsService) ListProjectContributionEvents(pid interface{}, opt *ListContributionEventsOptions, options ...OptionFunc) ([]*ContributionEvent, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("%s/events", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var cs []*ContributionEvent + resp, err := s.client.Do(req, &cs) + if err != nil { + return nil, resp, err + } + + return cs, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/feature_flags.go b/vendor/github.com/xanzy/go-gitlab/feature_flags.go new file mode 100644 index 00000000..b6380ab0 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/feature_flags.go @@ -0,0 +1,79 @@ +package gitlab + +import ( + "fmt" + "net/url" +) + +// FeaturesService handles the communication with the application FeaturesService +// related methods of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/features.html +type FeaturesService struct { + client *Client +} + +// Feature represents a GitLab feature flag. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/features.html +type Feature struct { + Name string `json:"name"` + State string `json:"state"` + Gates []Gate +} + +// Gate represents a gate of a GitLab feature flag. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/features.html +type Gate struct { + Key string `json:"key"` + Value interface{} `json:"value"` +} + +func (f Feature) String() string { + return Stringify(f) +} + +// ListFeatures gets a list of feature flags +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/features.html#list-all-features +func (s *FeaturesService) ListFeatures(options ...OptionFunc) ([]*Feature, *Response, error) { + req, err := s.client.NewRequest("GET", "features", nil, options) + if err != nil { + return nil, nil, err + } + + var f []*Feature + resp, err := s.client.Do(req, &f) + if err != nil { + return nil, resp, err + } + return f, resp, err +} + +// SetFeatureFlag sets or creates a feature flag gate +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/features.html#set-or-create-a-feature +func (s *FeaturesService) SetFeatureFlag(name string, value interface{}, options ...OptionFunc) (*Feature, *Response, error) { + u := fmt.Sprintf("features/%s", url.QueryEscape(name)) + + opt := struct { + Value interface{} `url:"value" json:"value"` + }{ + value, + } + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + f := &Feature{} + resp, err := s.client.Do(req, f) + if err != nil { + return nil, resp, err + } + return f, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/gitignore_templates.go b/vendor/github.com/xanzy/go-gitlab/gitignore_templates.go new file mode 100644 index 00000000..5c911f45 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/gitignore_templates.go @@ -0,0 +1,84 @@ +// +// Copyright 2018, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" +) + +// GitIgnoreTemplatesService handles communication with the gitignore +// templates related methods of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/templates/gitignores.html +type GitIgnoreTemplatesService struct { + client *Client +} + +// GitIgnoreTemplate represents a GitLab gitignore template. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/templates/gitignores.html +type GitIgnoreTemplate struct { + Name string `json:"name"` + Content string `json:"content"` +} + +// ListTemplatesOptions represents the available ListAllTemplates() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/templates/gitignores.html#list-gitignore-templates +type ListTemplatesOptions ListOptions + +// ListTemplates get a list of available git ignore templates +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/templates/gitignores.html#list-gitignore-templates +func (s *GitIgnoreTemplatesService) ListTemplates(opt *ListTemplatesOptions, options ...OptionFunc) ([]*GitIgnoreTemplate, *Response, error) { + req, err := s.client.NewRequest("GET", "templates/gitignores", opt, options) + if err != nil { + return nil, nil, err + } + + var gs []*GitIgnoreTemplate + resp, err := s.client.Do(req, &gs) + if err != nil { + return nil, resp, err + } + + return gs, resp, err +} + +// GetTemplate get a git ignore template +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/templates/gitignores.html#single-gitignore-template +func (s *GitIgnoreTemplatesService) GetTemplate(key string, options ...OptionFunc) (*GitIgnoreTemplate, *Response, error) { + u := fmt.Sprintf("templates/gitignores/%s", url.QueryEscape(key)) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + g := new(GitIgnoreTemplate) + resp, err := s.client.Do(req, g) + if err != nil { + return nil, resp, err + } + + return g, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/gitlab.go b/vendor/github.com/xanzy/go-gitlab/gitlab.go new file mode 100644 index 00000000..7d6df8ff --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/gitlab.go @@ -0,0 +1,854 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +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" +) + +// authType represents an authentication type within GitLab. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/ +type authType int + +// List of available authentication types. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/ +const ( + basicAuth authType = iota + oAuthToken + privateToken +) + +// AccessLevelValue represents a permission level within GitLab. +// +// GitLab API docs: https://docs.gitlab.com/ce/permissions/permissions.html +type AccessLevelValue int + +// List of available access levels +// +// GitLab API docs: https://docs.gitlab.com/ce/permissions/permissions.html +const ( + NoPermissions AccessLevelValue = 0 + GuestPermissions AccessLevelValue = 10 + ReporterPermissions AccessLevelValue = 20 + DeveloperPermissions AccessLevelValue = 30 + MasterPermissions AccessLevelValue = 40 + 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 + +// String implements the fmt.Stringer interface. +func (l NotificationLevelValue) String() string { + return notificationLevelNames[l] +} + +// MarshalJSON implements the json.Marshaler interface. +func (l NotificationLevelValue) MarshalJSON() ([]byte, error) { + return json.Marshal(l.String()) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +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: + // No action needed. + default: + return fmt.Errorf("json: cannot unmarshal %T into Go value of type %T", raw, *l) + } + + return nil +} + +// List of valid notification levels. +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, +} + +// 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/ +type VisibilityValue string + +// List of available visibility levels +// +// GitLab API docs: https://docs.gitlab.com/ce/api/ +const ( + PrivateVisibility VisibilityValue = "private" + InternalVisibility VisibilityValue = "internal" + 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 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. + authType authType + + // 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. + 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 +// support pagination. +type ListOptions struct { + // For paginated result sets, page of results to retrieve. + Page int `url:"page,omitempty" json:"page,omitempty"` + + // For paginated result sets, the number of results to include per page. + PerPage int `url:"per_page,omitempty" json:"per_page,omitempty"` +} + +// 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 or personal token. +func NewClient(httpClient *http.Client, token string) *Client { + 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 { + 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 { + // Should never happen since defaultBaseURL is our constant. + panic(err) + } + + // Create the internal timeStats service. + 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.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} + + return c +} + +// BaseURL return a copy of the baseURL. +func (c *Client) BaseURL() *url.URL { + u := *c.baseURL + return &u +} + +// SetBaseURL sets the base URL for API requests to a custom endpoint. urlStr +// should always be specified with a trailing slash. +func (c *Client) SetBaseURL(urlStr string) error { + // Make sure the given URL end with a slash + if !strings.HasSuffix(urlStr, "/") { + urlStr += "/" + } + + 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 +// urlStr, in which case it is resolved relative to the base URL of the Client. +// Relative URL paths should always be specified without a preceding slash. If +// specified, the value pointed to by body is JSON encoded and included as the +// request body. +func (c *Client) NewRequest(method, path string, opt interface{}, options []OptionFunc) (*http.Request, error) { + u := *c.baseURL + // Set the encoded opaque data + u.Opaque = c.baseURL.Path + path + + 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 +} + +// Response is a GitLab API response. This wraps the standard http.Response +// returned from GitLab and provides convenient access to things like +// pagination links. +type Response struct { + *http.Response + + // These fields provide the page values for paginating through a set of + // 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. + TotalItems int + TotalPages int + ItemsPerPage int + CurrentPage int + NextPage int + PreviousPage int +} + +// newResponse creates a new Response for the provided http.Response. +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" +) + +// populatePageValues parses the HTTP Link response headers and populates the +// various pagination link values in the Response. +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) + } +} + +// Do sends an API request and returns the API response. The API response is +// JSON decoded and stored in the value pointed to by v, or returned as an +// error if an API error has occurred. If v implements the io.Writer +// interface, the raw response body will be written to v, without attempting to +// first decode it. +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 { + // even though there was an error, we still return the response + // in case the caller wants to inspect it further + 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 +} + +// Helper function to accept and format both the project ID or name as project +// identifier for all API calls. +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) + } +} + +// An ErrorResponse reports one or more errors caused by an API request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/README.html#data-validation-and-error-reporting +type ErrorResponse struct { + Response *http.Response + Message string +} + +func (e *ErrorResponse) Error() string { + path, _ := url.QueryUnescape(e.Response.Request.URL.Opaque) + 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) +} + +// CheckResponse checks the API response for errors, and returns them if present. +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 { + var raw interface{} + if err := json.Unmarshal(data, &raw); err != nil { + errorResponse.Message = "failed to parse unknown error format" + } + + errorResponse.Message = parseError(raw) + } + + return errorResponse +} + +// Format: +// { +// "message": { +// "": [ +// "", +// "", +// ... +// ], +// "": { +// "": [ +// "", +// "", +// ... +// ], +// } +// }, +// "error": "" +// } +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) + } +} + +// OptionFunc can be passed to all API requests to make the API call as if you were +// another user, provided your private token is from an administrator account. +// +// GitLab docs: https://docs.gitlab.com/ce/api/README.html#sudo +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 + } +} + +// WithContext runs the request with the provided context +func WithContext(ctx context.Context) OptionFunc { + return func(req *http.Request) error { + *req = *req.WithContext(ctx) + return nil + } +} + +// Bool is a helper routine that allocates a new bool value +// to store v and returns a pointer to it. +func Bool(v bool) *bool { + p := new(bool) + *p = v + return p +} + +// Int is a helper routine that allocates a new int32 value +// to store v and returns a pointer to it, but unlike Int32 +// its argument value is an int. +func Int(v int) *int { + p := new(int) + *p = v + return p +} + +// String is a helper routine that allocates a new string value +// to store v and returns a pointer to it. +func String(v string) *string { + p := new(string) + *p = v + return p +} + +// AccessLevel is a helper routine that allocates a new AccessLevelValue +// to store v and returns a pointer to it. +func AccessLevel(v AccessLevelValue) *AccessLevelValue { + p := new(AccessLevelValue) + *p = v + 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 { + p := new(NotificationLevelValue) + *p = v + 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 { + p := new(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 +} diff --git a/vendor/github.com/xanzy/go-gitlab/group_members.go b/vendor/github.com/xanzy/go-gitlab/group_members.go new file mode 100644 index 00000000..9423ff68 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/group_members.go @@ -0,0 +1,195 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" + "time" +) + +// GroupMembersService handles communication with the group members +// related methods of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/members.html +type GroupMembersService struct { + client *Client +} + +// GroupMember represents a GitLab group member. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/members.html +type GroupMember struct { + ID int `json:"id"` + Username string `json:"username"` + Email string `json:"email"` + Name string `json:"name"` + State string `json:"state"` + CreatedAt *time.Time `json:"created_at"` + AccessLevel AccessLevelValue `json:"access_level"` + ExpiresAt *ISOTime `json:"expires_at"` +} + +// ListGroupMembersOptions represents the available ListGroupMembers() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/members.html#list-all-members-of-a-group-or-project +type ListGroupMembersOptions struct { + ListOptions + Query *string `url:"query,omitempty" json:"query,omitempty"` +} + +// ListGroupMembers get a list of group members viewable by the authenticated +// user. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/members.html#list-all-members-of-a-group-or-project +func (s *GroupsService) ListGroupMembers(gid interface{}, opt *ListGroupMembersOptions, options ...OptionFunc) ([]*GroupMember, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s/members", url.QueryEscape(group)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var gm []*GroupMember + resp, err := s.client.Do(req, &gm) + if err != nil { + return nil, resp, err + } + + return gm, resp, err +} + +// AddGroupMemberOptions represents the available AddGroupMember() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/members.html#add-a-member-to-a-group-or-project +type AddGroupMemberOptions struct { + UserID *int `url:"user_id,omitempty" json:"user_id,omitempty"` + AccessLevel *AccessLevelValue `url:"access_level,omitempty" json:"access_level,omitempty"` + ExpiresAt *string `url:"expires_at,omitempty" json:"expires_at"` +} + +// GetGroupMember gets a member of a group. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/members.html#get-a-member-of-a-group-or-project +func (s *GroupMembersService) GetGroupMember(gid interface{}, user int, options ...OptionFunc) (*GroupMember, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s/members/%d", url.QueryEscape(group), user) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + gm := new(GroupMember) + resp, err := s.client.Do(req, gm) + if err != nil { + return nil, resp, err + } + + return gm, resp, err +} + +// AddGroupMember adds a user to the list of group members. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/members.html#add-a-member-to-a-group-or-project +func (s *GroupMembersService) AddGroupMember(gid interface{}, opt *AddGroupMemberOptions, options ...OptionFunc) (*GroupMember, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s/members", url.QueryEscape(group)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + gm := new(GroupMember) + resp, err := s.client.Do(req, gm) + if err != nil { + return nil, resp, err + } + + return gm, resp, err +} + +// EditGroupMemberOptions represents the available EditGroupMember() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/members.html#edit-a-member-of-a-group-or-project +type EditGroupMemberOptions struct { + AccessLevel *AccessLevelValue `url:"access_level,omitempty" json:"access_level,omitempty"` + ExpiresAt *string `url:"expires_at,omitempty" json:"expires_at"` +} + +// EditGroupMember updates a member of a group. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/members.html#edit-a-member-of-a-group-or-project +func (s *GroupMembersService) EditGroupMember(gid interface{}, user int, opt *EditGroupMemberOptions, options ...OptionFunc) (*GroupMember, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s/members/%d", url.QueryEscape(group), user) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + gm := new(GroupMember) + resp, err := s.client.Do(req, gm) + if err != nil { + return nil, resp, err + } + + return gm, resp, err +} + +// RemoveGroupMember removes user from user team. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/members.html#remove-a-member-from-a-group-or-project +func (s *GroupMembersService) RemoveGroupMember(gid interface{}, user int, options ...OptionFunc) (*Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("groups/%s/members/%d", url.QueryEscape(group), user) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/xanzy/go-gitlab/group_milestones.go b/vendor/github.com/xanzy/go-gitlab/group_milestones.go new file mode 100644 index 00000000..8335a47b --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/group_milestones.go @@ -0,0 +1,250 @@ +// +// Copyright 2018, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" + "time" +) + +// GroupMilestonesService handles communication with the milestone related +// methods of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/group_milestones.html +type GroupMilestonesService struct { + client *Client +} + +// GroupMilestone represents a GitLab milestone. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/group_milestones.html +type GroupMilestone struct { + ID int `json:"id"` + IID int `json:"iid"` + GroupID int `json:"group_id"` + Title string `json:"title"` + Description string `json:"description"` + StartDate *ISOTime `json:"start_date"` + DueDate *ISOTime `json:"due_date"` + State string `json:"state"` + UpdatedAt *time.Time `json:"updated_at"` + CreatedAt *time.Time `json:"created_at"` +} + +func (m GroupMilestone) String() string { + return Stringify(m) +} + +// ListGroupMilestonesOptions represents the available +// ListGroupMilestones() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/group_milestones.html#list-group-milestones +type ListGroupMilestonesOptions struct { + ListOptions + IIDs []int `url:"iids,omitempty" json:"iids,omitempty"` + State string `url:"state,omitempty" json:"state,omitempty"` + Search string `url:"search,omitempty" json:"search,omitempty"` +} + +// ListGroupMilestones returns a list of group milestones. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/group_milestones.html#list-group-milestones +func (s *GroupMilestonesService) ListGroupMilestones(gid interface{}, opt *ListGroupMilestonesOptions, options ...OptionFunc) ([]*GroupMilestone, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s/milestones", url.QueryEscape(group)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var m []*GroupMilestone + resp, err := s.client.Do(req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// GetGroupMilestone gets a single group milestone. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/group_milestones.html#get-single-milestone +func (s *GroupMilestonesService) GetGroupMilestone(gid interface{}, milestone int, options ...OptionFunc) (*GroupMilestone, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s/milestones/%d", url.QueryEscape(group), milestone) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + m := new(GroupMilestone) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// CreateGroupMilestoneOptions represents the available CreateGroupMilestone() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/group_milestones.html#create-new-milestone +type CreateGroupMilestoneOptions struct { + Title *string `url:"title,omitempty" json:"title,omitempty"` + Description *string `url:"description,omitempty" json:"description,omitempty"` + StartDate *ISOTime `url:"start_date,omitempty" json:"start_date,omitempty"` + DueDate *ISOTime `url:"due_date,omitempty" json:"due_date,omitempty"` +} + +// CreateGroupMilestone creates a new group milestone. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/group_milestones.html#create-new-milestone +func (s *GroupMilestonesService) CreateGroupMilestone(gid interface{}, opt *CreateGroupMilestoneOptions, options ...OptionFunc) (*GroupMilestone, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s/milestones", url.QueryEscape(group)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + m := new(GroupMilestone) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// UpdateGroupMilestoneOptions represents the available UpdateGroupMilestone() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/group_milestones.html#edit-milestone +type UpdateGroupMilestoneOptions struct { + Title *string `url:"title,omitempty" json:"title,omitempty"` + Description *string `url:"description,omitempty" json:"description,omitempty"` + StartDate *ISOTime `url:"start_date,omitempty" json:"start_date,omitempty"` + DueDate *ISOTime `url:"due_date,omitempty" json:"due_date,omitempty"` + StateEvent *string `url:"state_event,omitempty" json:"state_event,omitempty"` +} + +// UpdateGroupMilestone updates an existing group milestone. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/group_milestones.html#edit-milestone +func (s *GroupMilestonesService) UpdateGroupMilestone(gid interface{}, milestone int, opt *UpdateGroupMilestoneOptions, options ...OptionFunc) (*GroupMilestone, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s/milestones/%d", url.QueryEscape(group), milestone) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + m := new(GroupMilestone) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// GetGroupMilestoneIssuesOptions represents the available GetGroupMilestoneIssues() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/group_milestones.html#get-all-issues-assigned-to-a-single-milestone +type GetGroupMilestoneIssuesOptions ListOptions + +// GetGroupMilestoneIssues gets all issues assigned to a single group milestone. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/group_milestones.html#get-all-issues-assigned-to-a-single-milestone +func (s *GroupMilestonesService) GetGroupMilestoneIssues(gid interface{}, milestone int, opt *GetGroupMilestoneIssuesOptions, options ...OptionFunc) ([]*Issue, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s/milestones/%d/issues", url.QueryEscape(group), milestone) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var i []*Issue + resp, err := s.client.Do(req, &i) + if err != nil { + return nil, resp, err + } + + return i, resp, err +} + +// GetGroupMilestoneMergeRequestsOptions represents the available +// GetGroupMilestoneMergeRequests() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/group_milestones.html#get-all-merge-requests-assigned-to-a-single-milestone +type GetGroupMilestoneMergeRequestsOptions ListOptions + +// GetGroupMilestoneMergeRequests gets all merge requests assigned to a +// single group milestone. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/group_milestones.html#get-all-merge-requests-assigned-to-a-single-milestone +func (s *GroupMilestonesService) GetGroupMilestoneMergeRequests(gid interface{}, milestone int, opt *GetGroupMilestoneMergeRequestsOptions, options ...OptionFunc) ([]*MergeRequest, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s/milestones/%d/merge_requests", url.QueryEscape(group), milestone) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var mr []*MergeRequest + resp, err := s.client.Do(req, &mr) + if err != nil { + return nil, resp, err + } + + return mr, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/groups.go b/vendor/github.com/xanzy/go-gitlab/groups.go new file mode 100644 index 00000000..d379915c --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/groups.go @@ -0,0 +1,307 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" +) + +// GroupsService handles communication with the group related methods of +// the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html +type GroupsService struct { + client *Client +} + +// Group represents a GitLab group. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html +type Group struct { + ID int `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + Description string `json:"description"` + Visibility *VisibilityValue `json:"visibility"` + LFSEnabled bool `json:"lfs_enabled"` + AvatarURL string `json:"avatar_url"` + WebURL string `json:"web_url"` + RequestAccessEnabled bool `json:"request_access_enabled"` + FullName string `json:"full_name"` + FullPath string `json:"full_path"` + ParentID int `json:"parent_id"` + Projects []*Project `json:"projects"` + Statistics *StorageStatistics `json:"statistics"` +} + +// ListGroupsOptions represents the available ListGroups() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#list-project-groups +type ListGroupsOptions struct { + ListOptions + AllAvailable *bool `url:"all_available,omitempty" json:"all_available,omitempty"` + OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` + Owned *bool `url:"owned,omitempty" json:"owned,omitempty"` + Search *string `url:"search,omitempty" json:"search,omitempty"` + Sort *string `url:"sort,omitempty" json:"sort,omitempty"` + Statistics *bool `url:"statistics,omitempty" json:"statistics,omitempty"` +} + +// ListGroups gets a list of groups (as user: my groups, as admin: all groups). +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/groups.html#list-project-groups +func (s *GroupsService) ListGroups(opt *ListGroupsOptions, options ...OptionFunc) ([]*Group, *Response, error) { + req, err := s.client.NewRequest("GET", "groups", opt, options) + if err != nil { + return nil, nil, err + } + + var g []*Group + resp, err := s.client.Do(req, &g) + if err != nil { + return nil, resp, err + } + + return g, resp, err +} + +// GetGroup gets all details of a group. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#details-of-a-group +func (s *GroupsService) GetGroup(gid interface{}, options ...OptionFunc) (*Group, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s", url.QueryEscape(group)) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + g := new(Group) + resp, err := s.client.Do(req, g) + if err != nil { + return nil, resp, err + } + + return g, resp, err +} + +// CreateGroupOptions represents the available CreateGroup() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#new-group +type CreateGroupOptions struct { + Name *string `url:"name,omitempty" json:"name,omitempty"` + Path *string `url:"path,omitempty" json:"path,omitempty"` + Description *string `url:"description,omitempty" json:"description,omitempty"` + Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"` + LFSEnabled *bool `url:"lfs_enabled,omitempty" json:"lfs_enabled,omitempty"` + RequestAccessEnabled *bool `url:"request_access_enabled,omitempty" json:"request_access_enabled,omitempty"` + ParentID *int `url:"parent_id,omitempty" json:"parent_id,omitempty"` +} + +// CreateGroup creates a new project group. Available only for users who can +// create groups. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#new-group +func (s *GroupsService) CreateGroup(opt *CreateGroupOptions, options ...OptionFunc) (*Group, *Response, error) { + req, err := s.client.NewRequest("POST", "groups", opt, options) + if err != nil { + return nil, nil, err + } + + g := new(Group) + resp, err := s.client.Do(req, g) + if err != nil { + return nil, resp, err + } + + return g, resp, err +} + +// TransferGroup transfers a project to the Group namespace. Available only +// for admin. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/groups.html#transfer-project-to-group +func (s *GroupsService) TransferGroup(gid interface{}, pid interface{}, options ...OptionFunc) (*Group, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + + u := fmt.Sprintf("groups/%s/projects/%s", url.QueryEscape(group), + url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + g := new(Group) + resp, err := s.client.Do(req, g) + if err != nil { + return nil, resp, err + } + + return g, resp, err +} + +// UpdateGroupOptions represents the set of available options to update a Group; +// as of today these are exactly the same available when creating a new Group. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#update-group +type UpdateGroupOptions CreateGroupOptions + +// UpdateGroup updates an existing group; only available to group owners and +// administrators. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#update-group +func (s *GroupsService) UpdateGroup(gid interface{}, opt *UpdateGroupOptions, options ...OptionFunc) (*Group, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s", url.QueryEscape(group)) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + g := new(Group) + resp, err := s.client.Do(req, g) + if err != nil { + return nil, resp, err + } + + return g, resp, err +} + +// DeleteGroup removes group with all projects inside. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#remove-group +func (s *GroupsService) DeleteGroup(gid interface{}, options ...OptionFunc) (*Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("groups/%s", url.QueryEscape(group)) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// SearchGroup get all groups that match your string in their name or path. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#search-for-group +func (s *GroupsService) SearchGroup(query string, options ...OptionFunc) ([]*Group, *Response, error) { + var q struct { + Search string `url:"search,omitempty" json:"search,omitempty"` + } + q.Search = query + + req, err := s.client.NewRequest("GET", "groups", &q, options) + if err != nil { + return nil, nil, err + } + + var g []*Group + resp, err := s.client.Do(req, &g) + if err != nil { + return nil, resp, err + } + + return g, resp, err +} + +// ListGroupProjectsOptions represents the available ListGroupProjects() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/groups.html#list-a-group-39-s-projects +type ListGroupProjectsOptions ListProjectsOptions + +// ListGroupProjects get a list of group projects +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/groups.html#list-a-group-39-s-projects +func (s *GroupsService) ListGroupProjects(gid interface{}, opt *ListGroupProjectsOptions, options ...OptionFunc) ([]*Project, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s/projects", url.QueryEscape(group)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var p []*Project + resp, err := s.client.Do(req, &p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// ListSubgroupsOptions represents the available ListSubgroupsOptions() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/groups.html#list-a-groups-s-subgroups +type ListSubgroupsOptions ListGroupsOptions + +// ListSubgroups gets a list of subgroups for a given project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/groups.html#list-a-groups-s-subgroups +func (s *GroupsService) ListSubgroups(gid interface{}, opt *ListSubgroupsOptions, options ...OptionFunc) ([]*Group, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s/subgroups", url.QueryEscape(group)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var g []*Group + resp, err := s.client.Do(req, &g) + if err != nil { + return nil, resp, err + } + + return g, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/issue_links.go b/vendor/github.com/xanzy/go-gitlab/issue_links.go new file mode 100644 index 00000000..5dfd76aa --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/issue_links.go @@ -0,0 +1,128 @@ +// +// Copyright 2017, Arkbriar +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" +) + +// IssueLinksService handles communication with the issue relations related methods +// of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ee/api/issue_links.html +type IssueLinksService struct { + client *Client +} + +// IssueLink represents a two-way relation between two issues. +// +// GitLab API docs: https://docs.gitlab.com/ee/api/issue_links.html +type IssueLink struct { + SourceIssue *Issue `json:"source_issue"` + TargetIssue *Issue `json:"target_issue"` +} + +// ListIssueRelations gets a list of related issues of a given issue, +// sorted by the relationship creation datetime (ascending). +// +// Issues will be filtered according to the user authorizations. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/issue_links.html#list-issue-relations +func (s *IssueLinksService) ListIssueRelations(pid interface{}, issueIID int, options ...OptionFunc) ([]*Issue, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/issues/%d/links", url.QueryEscape(project), issueIID) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + var is []*Issue + resp, err := s.client.Do(req, &is) + if err != nil { + return nil, resp, err + } + + return is, resp, err +} + +// CreateIssueLinkOptions represents the available CreateIssueLink() options. +// +// GitLab API docs: https://docs.gitlab.com/ee/api/issue_links.html +type CreateIssueLinkOptions struct { + TargetProjectID *string `json:"target_project_id"` + TargetIssueIID *string `json:"target_issue_iid"` +} + +// CreateIssueLink creates a two-way relation between two issues. +// User must be allowed to update both issues in order to succeed. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/issue_links.html#create-an-issue-link +func (s *IssueLinksService) CreateIssueLink(pid interface{}, issueIID int, opt *CreateIssueLinkOptions, options ...OptionFunc) (*IssueLink, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/issues/%d/links", url.QueryEscape(project), issueIID) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + i := new(IssueLink) + resp, err := s.client.Do(req, &i) + if err != nil { + return nil, resp, err + } + + return i, resp, err +} + +// DeleteIssueLink deletes an issue link, thus removes the two-way relationship. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/issue_links.html#delete-an-issue-link +func (s *IssueLinksService) DeleteIssueLink(pid interface{}, issueIID, issueLinkID int, options ...OptionFunc) (*IssueLink, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/issues/%d/links/%d", + url.QueryEscape(project), + issueIID, + issueLinkID) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, nil, err + } + + i := new(IssueLink) + resp, err := s.client.Do(req, &i) + if err != nil { + return nil, resp, err + } + + return i, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/issues.go b/vendor/github.com/xanzy/go-gitlab/issues.go new file mode 100644 index 00000000..51c3a19a --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/issues.go @@ -0,0 +1,403 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "encoding/json" + "fmt" + "net/url" + "strings" + "time" +) + +// IssuesService handles communication with the issue related methods +// of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html +type IssuesService struct { + client *Client + timeStats *timeStatsService +} + +// Issue represents a GitLab issue. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html +type Issue struct { + ID int `json:"id"` + IID int `json:"iid"` + ProjectID int `json:"project_id"` + Milestone *Milestone `json:"milestone"` + Author struct { + ID int `json:"id"` + Username string `json:"username"` + Email string `json:"email"` + Name string `json:"name"` + State string `json:"state"` + CreatedAt *time.Time `json:"created_at"` + } `json:"author"` + Description string `json:"description"` + State string `json:"state"` + Assignees []struct { + ID int `json:"id"` + Username string `json:"username"` + Email string `json:"email"` + Name string `json:"name"` + State string `json:"state"` + CreatedAt *time.Time `json:"created_at"` + } `json:"assignees"` + Assignee struct { + ID int `json:"id"` + Name string `json:"name"` + Username string `json:"username"` + State string `json:"state"` + AvatarURL string `json:"avatar_url"` + WebURL string `json:"web_url"` + } `json:"assignee"` + Upvotes int `json:"upvotes"` + Downvotes int `json:"downvotes"` + Labels []string `json:"labels"` + Title string `json:"title"` + UpdatedAt *time.Time `json:"updated_at"` + CreatedAt *time.Time `json:"created_at"` + ClosedAt *time.Time `json:"closed_at"` + Subscribed bool `json:"subscribed"` + UserNotesCount int `json:"user_notes_count"` + DueDate *ISOTime `json:"due_date"` + WebURL string `json:"web_url"` + TimeStats *TimeStats `json:"time_stats"` + Confidential bool `json:"confidential"` + Weight int `json:"weight"` + DiscussionLocked bool `json:"discussion_locked"` + Links struct { + Self string `json:"self"` + Notes string `json:"notes"` + AwardEmoji string `json:"award_emoji"` + Project string `json:"project"` + } `json:"_links"` + IssueLinkID int `json:"issue_link_id"` +} + +func (i Issue) String() string { + return Stringify(i) +} + +// Labels is a custom type with specific marshaling characteristics. +type Labels []string + +// MarshalJSON implements the json.Marshaler interface. +func (l *Labels) MarshalJSON() ([]byte, error) { + return json.Marshal(strings.Join(*l, ",")) +} + +// ListIssuesOptions represents the available ListIssues() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-issues +type ListIssuesOptions struct { + ListOptions + State *string `url:"state,omitempty" json:"state,omitempty"` + Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"` + Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"` + Scope *string `url:"scope,omitempty" json:"scope,omitempty"` + AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"` + AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"` + MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"` + IIDs []int `url:"iids[],omitempty" json:"iids,omitempty"` + OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` + Sort *string `url:"sort,omitempty" json:"sort,omitempty"` + Search *string `url:"search,omitempty" json:"search,omitempty"` + CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"` + CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"` + UpdatedAfter *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"` + UpdatedBefore *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"` +} + +// ListIssues gets all issues created by authenticated user. This function +// takes pagination parameters page and per_page to restrict the list of issues. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-issues +func (s *IssuesService) ListIssues(opt *ListIssuesOptions, options ...OptionFunc) ([]*Issue, *Response, error) { + req, err := s.client.NewRequest("GET", "issues", opt, options) + if err != nil { + return nil, nil, err + } + + var i []*Issue + resp, err := s.client.Do(req, &i) + if err != nil { + return nil, resp, err + } + + return i, resp, err +} + +// ListGroupIssuesOptions represents the available ListGroupIssues() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-group-issues +type ListGroupIssuesOptions struct { + ListOptions + State *string `url:"state,omitempty" json:"state,omitempty"` + Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"` + IIDs []int `url:"iids[],omitempty" json:"iids,omitempty"` + Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"` + Scope *string `url:"scope,omitempty" json:"scope,omitempty"` + AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"` + AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"` + MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"` + OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` + Sort *string `url:"sort,omitempty" json:"sort,omitempty"` + Search *string `url:"search,omitempty" json:"search,omitempty"` + CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"` + CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"` + UpdatedAfter *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"` + UpdatedBefore *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"` +} + +// ListGroupIssues gets a list of group issues. This function accepts +// pagination parameters page and per_page to return the list of group issues. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-group-issues +func (s *IssuesService) ListGroupIssues(pid interface{}, opt *ListGroupIssuesOptions, options ...OptionFunc) ([]*Issue, *Response, error) { + group, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s/issues", url.QueryEscape(group)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var i []*Issue + resp, err := s.client.Do(req, &i) + if err != nil { + return nil, resp, err + } + + return i, resp, err +} + +// ListProjectIssuesOptions represents the available ListProjectIssues() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-project-issues +type ListProjectIssuesOptions struct { + ListOptions + IIDs []int `url:"iids[],omitempty" json:"iids,omitempty"` + State *string `url:"state,omitempty" json:"state,omitempty"` + Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"` + Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"` + Scope *string `url:"scope,omitempty" json:"scope,omitempty"` + AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"` + AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"` + MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"` + OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` + Sort *string `url:"sort,omitempty" json:"sort,omitempty"` + Search *string `url:"search,omitempty" json:"search,omitempty"` + CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"` + CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"` + UpdatedAfter *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"` + UpdatedBefore *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"` +} + +// ListProjectIssues gets a list of project issues. This function accepts +// pagination parameters page and per_page to return the list of project issues. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-project-issues +func (s *IssuesService) ListProjectIssues(pid interface{}, opt *ListProjectIssuesOptions, options ...OptionFunc) ([]*Issue, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/issues", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var i []*Issue + resp, err := s.client.Do(req, &i) + if err != nil { + return nil, resp, err + } + + return i, resp, err +} + +// GetIssue gets a single project issue. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#single-issues +func (s *IssuesService) GetIssue(pid interface{}, issue int, options ...OptionFunc) (*Issue, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/issues/%d", url.QueryEscape(project), issue) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + i := new(Issue) + resp, err := s.client.Do(req, i) + if err != nil { + return nil, resp, err + } + + return i, resp, err +} + +// CreateIssueOptions represents the available CreateIssue() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#new-issues +type CreateIssueOptions struct { + Title *string `url:"title,omitempty" json:"title,omitempty"` + Description *string `url:"description,omitempty" json:"description,omitempty"` + Confidential *bool `url:"confidential,omitempty" json:"confidential,omitempty"` + AssigneeIDs []int `url:"assignee_ids,omitempty" json:"assignee_ids,omitempty"` + MilestoneID *int `url:"milestone_id,omitempty" json:"milestone_id,omitempty"` + Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"` + CreatedAt *time.Time `url:"created_at,omitempty" json:"created_at,omitempty"` + DueDate *ISOTime `url:"due_date,omitempty" json:"due_date,omitempty"` + MergeRequestToResolveDiscussionsOf *int `url:"merge_request_to_resolve_discussions_of,omitempty" json:"merge_request_to_resolve_discussions_of,omitempty"` + DiscussionToResolve *string `url:"discussion_to_resolve,omitempty" json:"discussion_to_resolve,omitempty"` + Weight *int `url:"weight,omitempty" json:"weight,omitempty"` +} + +// CreateIssue creates a new project issue. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#new-issues +func (s *IssuesService) CreateIssue(pid interface{}, opt *CreateIssueOptions, options ...OptionFunc) (*Issue, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/issues", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + i := new(Issue) + resp, err := s.client.Do(req, i) + if err != nil { + return nil, resp, err + } + + return i, resp, err +} + +// UpdateIssueOptions represents the available UpdateIssue() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#edit-issues +type UpdateIssueOptions struct { + Title *string `url:"title,omitempty" json:"title,omitempty"` + Description *string `url:"description,omitempty" json:"description,omitempty"` + Confidential *bool `url:"confidential,omitempty" json:"confidential,omitempty"` + AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"` + MilestoneID *int `url:"milestone_id,omitempty" json:"milestone_id,omitempty"` + Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"` + StateEvent *string `url:"state_event,omitempty" json:"state_event,omitempty"` + UpdatedAt *time.Time `url:"updated_at,omitempty" json:"updated_at,omitempty"` + DueDate *ISOTime `url:"due_date,omitempty" json:"due_date,omitempty"` + DiscussionLocked *bool `url:"discussion_locked,omitempty" json:"discussion_locked,omitempty"` +} + +// UpdateIssue updates an existing project issue. This function is also used +// to mark an issue as closed. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#edit-issues +func (s *IssuesService) UpdateIssue(pid interface{}, issue int, opt *UpdateIssueOptions, options ...OptionFunc) (*Issue, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/issues/%d", url.QueryEscape(project), issue) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + i := new(Issue) + resp, err := s.client.Do(req, i) + if err != nil { + return nil, resp, err + } + + return i, resp, err +} + +// DeleteIssue deletes a single project issue. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#delete-an-issue +func (s *IssuesService) DeleteIssue(pid interface{}, issue int, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/issues/%d", url.QueryEscape(project), issue) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// SetTimeEstimate sets the time estimate for a single project issue. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/issues.html#set-a-time-estimate-for-an-issue +func (s *IssuesService) SetTimeEstimate(pid interface{}, issue int, opt *SetTimeEstimateOptions, options ...OptionFunc) (*TimeStats, *Response, error) { + return s.timeStats.setTimeEstimate(pid, "issues", issue, opt, options...) +} + +// ResetTimeEstimate resets the time estimate for a single project issue. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/issues.html#reset-the-time-estimate-for-an-issue +func (s *IssuesService) ResetTimeEstimate(pid interface{}, issue int, options ...OptionFunc) (*TimeStats, *Response, error) { + return s.timeStats.resetTimeEstimate(pid, "issues", issue, options...) +} + +// AddSpentTime adds spent time for a single project issue. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/issues.html#add-spent-time-for-an-issue +func (s *IssuesService) AddSpentTime(pid interface{}, issue int, opt *AddSpentTimeOptions, options ...OptionFunc) (*TimeStats, *Response, error) { + return s.timeStats.addSpentTime(pid, "issues", issue, opt, options...) +} + +// ResetSpentTime resets the spent time for a single project issue. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/issues.html#reset-spent-time-for-an-issue +func (s *IssuesService) ResetSpentTime(pid interface{}, issue int, options ...OptionFunc) (*TimeStats, *Response, error) { + return s.timeStats.resetSpentTime(pid, "issues", issue, options...) +} + +// GetTimeSpent gets the spent time for a single project issue. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/issues.html#get-time-tracking-stats +func (s *IssuesService) GetTimeSpent(pid interface{}, issue int, options ...OptionFunc) (*TimeStats, *Response, error) { + return s.timeStats.getTimeSpent(pid, "issues", issue, options...) +} diff --git a/vendor/github.com/xanzy/go-gitlab/jobs.go b/vendor/github.com/xanzy/go-gitlab/jobs.go new file mode 100644 index 00000000..669f96dd --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/jobs.go @@ -0,0 +1,350 @@ +// +// Copyright 2017, Arkbriar +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "bytes" + "fmt" + "io" + "net/url" + "time" +) + +// JobsService handles communication with the ci builds related methods +// of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/jobs.html +type JobsService struct { + client *Client +} + +// Job represents a ci build. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/jobs.html +type Job struct { + Commit *Commit `json:"commit"` + CreatedAt *time.Time `json:"created_at"` + Coverage float64 `json:"coverage"` + ArtifactsFile struct { + Filename string `json:"filename"` + Size int `json:"size"` + } `json:"artifacts_file"` + FinishedAt *time.Time `json:"finished_at"` + ID int `json:"id"` + Name string `json:"name"` + Ref string `json:"ref"` + Runner struct { + ID int `json:"id"` + Description string `json:"description"` + Active bool `json:"active"` + IsShared bool `json:"is_shared"` + Name string `json:"name"` + } `json:"runner"` + Stage string `json:"stage"` + StartedAt *time.Time `json:"started_at"` + Status string `json:"status"` + Tag bool `json:"tag"` + User *User `json:"user"` +} + +// ListJobsOptions are options for two list apis +type ListJobsOptions struct { + ListOptions + Scope []BuildStateValue `url:"scope,omitempty" json:"scope,omitempty"` +} + +// ListProjectJobs gets a list of jobs in a project. +// +// The scope of jobs to show, one or array of: created, pending, running, +// failed, success, canceled, skipped; showing all jobs if none provided +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/jobs.html#list-project-jobs +func (s *JobsService) ListProjectJobs(pid interface{}, opts *ListJobsOptions, options ...OptionFunc) ([]Job, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/jobs", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opts, options) + if err != nil { + return nil, nil, err + } + + var jobs []Job + resp, err := s.client.Do(req, &jobs) + if err != nil { + return nil, resp, err + } + + return jobs, resp, err +} + +// ListPipelineJobs gets a list of jobs for specific pipeline in a +// project. If the pipeline ID is not found, it will respond with 404. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/jobs.html#list-pipeline-jobs +func (s *JobsService) ListPipelineJobs(pid interface{}, pipelineID int, opts *ListJobsOptions, options ...OptionFunc) ([]*Job, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/pipelines/%d/jobs", url.QueryEscape(project), pipelineID) + + req, err := s.client.NewRequest("GET", u, opts, options) + if err != nil { + return nil, nil, err + } + + var jobs []*Job + resp, err := s.client.Do(req, &jobs) + if err != nil { + return nil, resp, err + } + + return jobs, resp, err +} + +// GetJob gets a single job of a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/jobs.html#get-a-single-job +func (s *JobsService) GetJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/jobs/%d", url.QueryEscape(project), jobID) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + job := new(Job) + resp, err := s.client.Do(req, job) + if err != nil { + return nil, resp, err + } + + return job, resp, err +} + +// GetJobArtifacts get jobs artifacts of a project +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/jobs.html#get-job-artifacts +func (s *JobsService) GetJobArtifacts(pid interface{}, jobID int, options ...OptionFunc) (io.Reader, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/jobs/%d/artifacts", url.QueryEscape(project), jobID) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + artifactsBuf := new(bytes.Buffer) + resp, err := s.client.Do(req, artifactsBuf) + if err != nil { + return nil, resp, err + } + + return artifactsBuf, resp, err +} + +// DownloadArtifactsFile download the artifacts file from the given +// reference name and job provided the job finished successfully. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/jobs.html#download-the-artifacts-file +func (s *JobsService) DownloadArtifactsFile(pid interface{}, refName string, job string, options ...OptionFunc) (io.Reader, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/jobs/artifacts/%s/download?job=%s", url.QueryEscape(project), refName, job) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + artifactsBuf := new(bytes.Buffer) + resp, err := s.client.Do(req, artifactsBuf) + if err != nil { + return nil, resp, err + } + + return artifactsBuf, resp, err +} + +// GetTraceFile gets a trace of a specific job of a project +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/jobs.html#get-a-trace-file +func (s *JobsService) GetTraceFile(pid interface{}, jobID int, options ...OptionFunc) (io.Reader, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/jobs/%d/trace", url.QueryEscape(project), jobID) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + traceBuf := new(bytes.Buffer) + resp, err := s.client.Do(req, traceBuf) + if err != nil { + return nil, resp, err + } + + return traceBuf, resp, err +} + +// CancelJob cancels a single job of a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/jobs.html#cancel-a-job +func (s *JobsService) CancelJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/jobs/%d/cancel", url.QueryEscape(project), jobID) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + job := new(Job) + resp, err := s.client.Do(req, job) + if err != nil { + return nil, resp, err + } + + return job, resp, err +} + +// RetryJob retries a single job of a project +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/jobs.html#retry-a-job +func (s *JobsService) RetryJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/jobs/%d/retry", url.QueryEscape(project), jobID) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + job := new(Job) + resp, err := s.client.Do(req, job) + if err != nil { + return nil, resp, err + } + + return job, resp, err +} + +// EraseJob erases a single job of a project, removes a job +// artifacts and a job trace. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/jobs.html#erase-a-job +func (s *JobsService) EraseJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/jobs/%d/erase", url.QueryEscape(project), jobID) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + job := new(Job) + resp, err := s.client.Do(req, job) + if err != nil { + return nil, resp, err + } + + return job, resp, err +} + +// KeepArtifacts prevents artifacts from being deleted when +// expiration is set. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/jobs.html#keep-artifacts +func (s *JobsService) KeepArtifacts(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/jobs/%d/artifacts/keep", url.QueryEscape(project), jobID) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + job := new(Job) + resp, err := s.client.Do(req, job) + if err != nil { + return nil, resp, err + } + + return job, resp, err +} + +// PlayJob triggers a manual action to start a job. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/jobs.html#play-a-job +func (s *JobsService) PlayJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/jobs/%d/play", url.QueryEscape(project), jobID) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + job := new(Job) + resp, err := s.client.Do(req, job) + if err != nil { + return nil, resp, err + } + + return job, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/labels.go b/vendor/github.com/xanzy/go-gitlab/labels.go new file mode 100644 index 00000000..2f8307d2 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/labels.go @@ -0,0 +1,228 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" +) + +// LabelsService handles communication with the label related methods of the +// GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html +type LabelsService struct { + client *Client +} + +// Label represents a GitLab label. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html +type Label struct { + ID int `json:"id"` + Name string `json:"name"` + Color string `json:"color"` + Description string `json:"description"` + OpenIssuesCount int `json:"open_issues_count"` + ClosedIssuesCount int `json:"closed_issues_count"` + OpenMergeRequestsCount int `json:"open_merge_requests_count"` + Subscribed bool `json:"subscribed"` + Priority int `json:"priority"` +} + +func (l Label) String() string { + return Stringify(l) +} + +// ListLabelsOptions represents the available ListLabels() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#list-labels +type ListLabelsOptions ListOptions + +// ListLabels gets all labels for given project. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#list-labels +func (s *LabelsService) ListLabels(pid interface{}, opt *ListLabelsOptions, options ...OptionFunc) ([]*Label, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/labels", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var l []*Label + resp, err := s.client.Do(req, &l) + if err != nil { + return nil, resp, err + } + + return l, resp, err +} + +// CreateLabelOptions represents the available CreateLabel() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#create-a-new-label +type CreateLabelOptions struct { + Name *string `url:"name,omitempty" json:"name,omitempty"` + Color *string `url:"color,omitempty" json:"color,omitempty"` + Description *string `url:"description,omitempty" json:"description,omitempty"` +} + +// CreateLabel creates a new label for given repository with given name and +// color. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#create-a-new-label +func (s *LabelsService) CreateLabel(pid interface{}, opt *CreateLabelOptions, options ...OptionFunc) (*Label, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/labels", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + l := new(Label) + resp, err := s.client.Do(req, l) + if err != nil { + return nil, resp, err + } + + return l, resp, err +} + +// DeleteLabelOptions represents the available DeleteLabel() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#delete-a-label +type DeleteLabelOptions struct { + Name *string `url:"name,omitempty" json:"name,omitempty"` +} + +// DeleteLabel deletes a label given by its name. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#delete-a-label +func (s *LabelsService) DeleteLabel(pid interface{}, opt *DeleteLabelOptions, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/labels", url.QueryEscape(project)) + + req, err := s.client.NewRequest("DELETE", u, opt, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// UpdateLabelOptions represents the available UpdateLabel() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#delete-a-label +type UpdateLabelOptions struct { + Name *string `url:"name,omitempty" json:"name,omitempty"` + NewName *string `url:"new_name,omitempty" json:"new_name,omitempty"` + Color *string `url:"color,omitempty" json:"color,omitempty"` + Description *string `url:"description,omitempty" json:"description,omitempty"` +} + +// UpdateLabel updates an existing label with new name or now color. At least +// one parameter is required, to update the label. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#edit-an-existing-label +func (s *LabelsService) UpdateLabel(pid interface{}, opt *UpdateLabelOptions, options ...OptionFunc) (*Label, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/labels", url.QueryEscape(project)) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + l := new(Label) + resp, err := s.client.Do(req, l) + if err != nil { + return nil, resp, err + } + + return l, resp, err +} + +// SubscribeToLabel subscribes the authenticated user to a label to receive +// notifications. If the user is already subscribed to the label, the status +// code 304 is returned. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/labels.html#subscribe-to-a-label +func (s *LabelsService) SubscribeToLabel(pid interface{}, labelID interface{}, options ...OptionFunc) (*Label, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + label, err := parseID(labelID) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/labels/%s/subscribe", url.QueryEscape(project), label) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + l := new(Label) + resp, err := s.client.Do(req, l) + if err != nil { + return nil, resp, err + } + + return l, resp, err +} + +// UnsubscribeFromLabel unsubscribes the authenticated user from a label to not +// receive notifications from it. If the user is not subscribed to the label, the +// status code 304 is returned. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/labels.html#unsubscribe-from-a-label +func (s *LabelsService) UnsubscribeFromLabel(pid interface{}, labelID interface{}, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + label, err := parseID(labelID) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/labels/%s/unsubscribe", url.QueryEscape(project), label) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/xanzy/go-gitlab/merge_request_approvals.go b/vendor/github.com/xanzy/go-gitlab/merge_request_approvals.go new file mode 100644 index 00000000..e03cb618 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/merge_request_approvals.go @@ -0,0 +1,125 @@ +package gitlab + +import ( + "fmt" + "net/url" + "time" +) + +// MergeRequestApprovalsService handles communication with the merge request +// approvals related methods of the GitLab API. This includes reading/updating +// approval settings and approve/unapproving merge requests +// +// GitLab API docs: https://docs.gitlab.com/ee/api/merge_request_approvals.html +type MergeRequestApprovalsService struct { + client *Client +} + +// MergeRequestApprovals represents GitLab merge request approvals. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/merge_request_approvals.html#merge-request-level-mr-approvals +type MergeRequestApprovals struct { + ID int `json:"id"` + ProjectID int `json:"project_id"` + Title string `json:"title"` + Description string `json:"description"` + State string `json:"state"` + CreatedAt *time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at"` + MergeStatus string `json:"merge_status"` + ApprovalsRequired int `json:"approvals_required"` + ApprovalsLeft int `json:"approvals_left"` + ApprovedBy []struct { + User struct { + ID int `json:"id"` + Name string `json:"name"` + Username string `json:"username"` + State string `json:"state"` + AvatarURL string `json:"avatar_url"` + WebURL string `json:"web_url"` + } `json:"user"` + } `json:"approved_by"` + Approvers []struct { + User struct { + ID int `json:"id"` + Name string `json:"name"` + Username string `json:"username"` + State string `json:"state"` + AvatarURL string `json:"avatar_url"` + WebURL string `json:"web_url"` + } `json:"user"` + } `json:"approvers"` + ApproverGroups []struct { + Group struct { + ID int `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + Description string `json:"description"` + Visibility string `json:"visibility"` + AvatarURL string `json:"avatar_url"` + WebURL string `json:"web_url"` + FullName string `json:"full_name"` + FullPath string `json:"full_path"` + LFSEnabled bool `json:"lfs_enabled"` + RequestAccessEnabled bool `json:"request_access_enabled"` + } `json:"group"` + } `json:"approver_group"` +} + +func (m MergeRequestApprovals) String() string { + return Stringify(m) +} + +// ApproveMergeRequestOptions represents the available ApproveMergeRequest() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/merge_request_approvals.html#approve-merge-request +type ApproveMergeRequestOptions struct { + Sha *string `url:"sha,omitempty" json:"sha,omitempty"` +} + +// ApproveMergeRequest approves a merge request on GitLab. If a non-empty sha +// is provided then it must match the sha at the HEAD of the MR. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/merge_request_approvals.html#approve-merge-request +func (s *MergeRequestApprovalsService) ApproveMergeRequest(pid interface{}, mr int, opt *ApproveMergeRequestOptions, options ...OptionFunc) (*MergeRequestApprovals, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/approve", url.QueryEscape(project), mr) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + m := new(MergeRequestApprovals) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// UnapproveMergeRequest unapproves a previously approved merge request on GitLab. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/merge_request_approvals.html#unapprove-merge-request +func (s *MergeRequestApprovalsService) UnapproveMergeRequest(pid interface{}, mr int, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/unapprove", url.QueryEscape(project), mr) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/xanzy/go-gitlab/merge_requests.go b/vendor/github.com/xanzy/go-gitlab/merge_requests.go new file mode 100644 index 00000000..269ef0a1 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/merge_requests.go @@ -0,0 +1,743 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" + "time" +) + +// MergeRequestsService handles communication with the merge requests related +// methods of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/merge_requests.html +type MergeRequestsService struct { + client *Client + timeStats *timeStatsService +} + +// MergeRequest represents a GitLab merge request. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/merge_requests.html +type MergeRequest struct { + ID int `json:"id"` + IID int `json:"iid"` + TargetBranch string `json:"target_branch"` + SourceBranch string `json:"source_branch"` + ProjectID int `json:"project_id"` + Title string `json:"title"` + State string `json:"state"` + CreatedAt *time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at"` + Upvotes int `json:"upvotes"` + Downvotes int `json:"downvotes"` + Author struct { + ID int `json:"id"` + Username string `json:"username"` + Name string `json:"name"` + State string `json:"state"` + CreatedAt *time.Time `json:"created_at"` + } `json:"author"` + Assignee struct { + ID int `json:"id"` + Username string `json:"username"` + Name string `json:"name"` + State string `json:"state"` + CreatedAt *time.Time `json:"created_at"` + } `json:"assignee"` + SourceProjectID int `json:"source_project_id"` + TargetProjectID int `json:"target_project_id"` + Labels []string `json:"labels"` + Description string `json:"description"` + WorkInProgress bool `json:"work_in_progress"` + Milestone *Milestone `json:"milestone"` + MergeWhenPipelineSucceeds bool `json:"merge_when_pipeline_succeeds"` + MergeStatus string `json:"merge_status"` + MergedBy struct { + ID int `json:"id"` + Username string `json:"username"` + Name string `json:"name"` + State string `json:"state"` + CreatedAt *time.Time `json:"created_at"` + } `json:"merged_by"` + MergedAt *time.Time `json:"merged_at"` + ClosedBy struct { + ID int `json:"id"` + Username string `json:"username"` + Name string `json:"name"` + State string `json:"state"` + CreatedAt *time.Time `json:"created_at"` + } `json:"closed_by"` + ClosedAt *time.Time `json:"closed_at"` + Subscribed bool `json:"subscribed"` + SHA string `json:"sha"` + MergeCommitSHA string `json:"merge_commit_sha"` + UserNotesCount int `json:"user_notes_count"` + ChangesCount string `json:"changes_count"` + ShouldRemoveSourceBranch bool `json:"should_remove_source_branch"` + ForceRemoveSourceBranch bool `json:"force_remove_source_branch"` + WebURL string `json:"web_url"` + DiscussionLocked bool `json:"discussion_locked"` + Changes []struct { + OldPath string `json:"old_path"` + NewPath string `json:"new_path"` + AMode string `json:"a_mode"` + BMode string `json:"b_mode"` + Diff string `json:"diff"` + NewFile bool `json:"new_file"` + RenamedFile bool `json:"renamed_file"` + DeletedFile bool `json:"deleted_file"` + } `json:"changes"` + TimeStats *TimeStats `json:"time_stats"` + Squash bool `json:"squash"` + Pipeline struct { + ID int `json:"id"` + Ref string `json:"ref"` + SHA string `json:"sha"` + Status string `json:"status"` + } `json:"pipeline"` +} + +func (m MergeRequest) String() string { + return Stringify(m) +} + +// MergeRequestDiffVersion represents Gitlab merge request version. +// +// Gitlab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#get-a-single-mr-diff-version +type MergeRequestDiffVersion struct { + ID int `json:"id"` + HeadCommitSHA string `json:"head_commit_sha,omitempty"` + BaseCommitSHA string `json:"base_commit_sha,omitempty"` + StartCommitSHA string `json:"start_commit_sha,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + MergeRequestID int `json:"merge_request_id,omitempty"` + State string `json:"state,omitempty"` + RealSize string `json:"real_size,omitempty"` + Commits []*Commit `json:"commits,omitempty"` + Diffs []*Diff `json:"diffs,omitempty"` +} + +func (m MergeRequestDiffVersion) String() string { + return Stringify(m) +} + +// ListMergeRequestsOptions represents the available ListMergeRequests() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#list-merge-requests +type ListMergeRequestsOptions struct { + ListOptions + State *string `url:"state,omitempty" json:"state,omitempty"` + OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` + Sort *string `url:"sort,omitempty" json:"sort,omitempty"` + Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"` + View *string `url:"view,omitempty" json:"view,omitempty"` + Labels Labels `url:"labels,omitempty" json:"labels,omitempty"` + CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"` + CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"` + Scope *string `url:"scope,omitempty" json:"scope,omitempty"` + AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"` + AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"` + MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"` +} + +// ListMergeRequests gets all merge requests. The state parameter can be used +// to get only merge requests with a given state (opened, closed, or merged) +// or all of them (all). The pagination parameters page and per_page can be +// used to restrict the list of merge requests. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#list-merge-requests +func (s *MergeRequestsService) ListMergeRequests(opt *ListMergeRequestsOptions, options ...OptionFunc) ([]*MergeRequest, *Response, error) { + req, err := s.client.NewRequest("GET", "merge_requests", opt, options) + if err != nil { + return nil, nil, err + } + + var m []*MergeRequest + resp, err := s.client.Do(req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// ListProjectMergeRequestsOptions represents the available ListMergeRequests() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#list-project-merge-requests +type ListProjectMergeRequestsOptions struct { + ListOptions + IIDs []int `url:"iids[],omitempty" json:"iids,omitempty"` + State *string `url:"state,omitempty" json:"state,omitempty"` + OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` + Sort *string `url:"sort,omitempty" json:"sort,omitempty"` + Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"` + View *string `url:"view,omitempty" json:"view,omitempty"` + Labels Labels `url:"labels,omitempty" json:"labels,omitempty"` + CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"` + CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"` + Scope *string `url:"scope,omitempty" json:"scope,omitempty"` + AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"` + AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"` + MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"` +} + +// ListProjectMergeRequests gets all merge requests for this project. The state +// parameter can be used to get only merge requests with a given state (opened, +// closed, or merged) or all of them (all). The pagination parameters page and +// per_page can be used to restrict the list of merge requests. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#list-merge-requests +func (s *MergeRequestsService) ListProjectMergeRequests(pid interface{}, opt *ListProjectMergeRequestsOptions, options ...OptionFunc) ([]*MergeRequest, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var m []*MergeRequest + resp, err := s.client.Do(req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// GetMergeRequest shows information about a single merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#get-single-mr +func (s *MergeRequestsService) GetMergeRequest(pid interface{}, mergeRequest int, options ...OptionFunc) (*MergeRequest, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d", url.QueryEscape(project), mergeRequest) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + m := new(MergeRequest) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// GetMergeRequestApprovals gets information about a merge requests approvals +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/merge_request_approvals.html#merge-request-level-mr-approvals +func (s *MergeRequestsService) GetMergeRequestApprovals(pid interface{}, mergeRequest int, options ...OptionFunc) (*MergeRequestApprovals, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/approvals", url.QueryEscape(project), mergeRequest) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + a := new(MergeRequestApprovals) + resp, err := s.client.Do(req, a) + if err != nil { + return nil, resp, err + } + + return a, resp, err +} + +// GetMergeRequestCommitsOptions represents the available GetMergeRequestCommits() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#get-single-mr-commits +type GetMergeRequestCommitsOptions ListOptions + +// GetMergeRequestCommits gets a list of merge request commits. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#get-single-mr-commits +func (s *MergeRequestsService) GetMergeRequestCommits(pid interface{}, mergeRequest int, opt *GetMergeRequestCommitsOptions, options ...OptionFunc) ([]*Commit, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/commits", url.QueryEscape(project), mergeRequest) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var c []*Commit + resp, err := s.client.Do(req, &c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// GetMergeRequestChanges shows information about the merge request including +// its files and changes. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#get-single-mr-changes +func (s *MergeRequestsService) GetMergeRequestChanges(pid interface{}, mergeRequest int, options ...OptionFunc) (*MergeRequest, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/changes", url.QueryEscape(project), mergeRequest) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + m := new(MergeRequest) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// ListMergeRequestPipelines gets all pipelines for the provided merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#list-mr-pipelines +func (s *MergeRequestsService) ListMergeRequestPipelines(pid interface{}, mergeRequest int, options ...OptionFunc) (PipelineList, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%v/pipelines", url.QueryEscape(project), mergeRequest) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + var p PipelineList + resp, err := s.client.Do(req, &p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// GetIssuesClosedOnMergeOptions represents the available GetIssuesClosedOnMerge() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#list-issues-that-will-close-on-merge +type GetIssuesClosedOnMergeOptions ListOptions + +// GetIssuesClosedOnMerge gets all the issues that would be closed by merging the +// provided merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#list-issues-that-will-close-on-merge +func (s *MergeRequestsService) GetIssuesClosedOnMerge(pid interface{}, mergeRequest int, opt *GetIssuesClosedOnMergeOptions, options ...OptionFunc) ([]*Issue, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("/projects/%s/merge_requests/%v/closes_issues", url.QueryEscape(project), mergeRequest) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var i []*Issue + resp, err := s.client.Do(req, &i) + if err != nil { + return nil, resp, err + } + + return i, resp, err +} + +// CreateMergeRequestOptions represents the available CreateMergeRequest() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#create-mr +type CreateMergeRequestOptions struct { + Title *string `url:"title,omitempty" json:"title,omitempty"` + Description *string `url:"description,omitempty" json:"description,omitempty"` + SourceBranch *string `url:"source_branch,omitempty" json:"source_branch,omitempty"` + TargetBranch *string `url:"target_branch,omitempty" json:"target_branch,omitempty"` + AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"` + TargetProjectID *int `url:"target_project_id,omitempty" json:"target_project_id,omitempty"` +} + +// CreateMergeRequest creates a new merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#create-mr +func (s *MergeRequestsService) CreateMergeRequest(pid interface{}, opt *CreateMergeRequestOptions, options ...OptionFunc) (*MergeRequest, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + m := new(MergeRequest) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// UpdateMergeRequestOptions represents the available UpdateMergeRequest() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#update-mr +type UpdateMergeRequestOptions struct { + Title *string `url:"title,omitempty" json:"title,omitempty"` + Description *string `url:"description,omitempty" json:"description,omitempty"` + TargetBranch *string `url:"target_branch,omitempty" json:"target_branch,omitempty"` + AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"` + Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"` + MilestoneID *int `url:"milestone_id,omitempty" json:"milestone_id,omitempty"` + StateEvent *string `url:"state_event,omitempty" json:"state_event,omitempty"` +} + +// UpdateMergeRequest updates an existing project milestone. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#update-mr +func (s *MergeRequestsService) UpdateMergeRequest(pid interface{}, mergeRequest int, opt *UpdateMergeRequestOptions, options ...OptionFunc) (*MergeRequest, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d", url.QueryEscape(project), mergeRequest) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + m := new(MergeRequest) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// DeleteMergeRequest deletes a merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#delete-a-merge-request +func (s *MergeRequestsService) DeleteMergeRequest(pid interface{}, mergeRequest int, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d", url.QueryEscape(project), mergeRequest) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// AcceptMergeRequestOptions represents the available AcceptMergeRequest() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#accept-mr +type AcceptMergeRequestOptions struct { + MergeCommitMessage *string `url:"merge_commit_message,omitempty" json:"merge_commit_message,omitempty"` + ShouldRemoveSourceBranch *bool `url:"should_remove_source_branch,omitempty" json:"should_remove_source_branch,omitempty"` + MergeWhenPipelineSucceeds *bool `url:"merge_when_pipeline_succeeds,omitempty" json:"merge_when_pipeline_succeeds,omitempty"` + Sha *string `url:"sha,omitempty" json:"sha,omitempty"` +} + +// AcceptMergeRequest merges changes submitted with MR using this API. If merge +// success you get 200 OK. If it has some conflicts and can not be merged - you +// get 405 and error message 'Branch cannot be merged'. If merge request is +// already merged or closed - you get 405 and error message 'Method Not Allowed' +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#accept-mr +func (s *MergeRequestsService) AcceptMergeRequest(pid interface{}, mergeRequest int, opt *AcceptMergeRequestOptions, options ...OptionFunc) (*MergeRequest, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/merge", url.QueryEscape(project), mergeRequest) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + m := new(MergeRequest) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// CancelMergeWhenPipelineSucceeds cancels a merge when pipeline succeeds. If +// you don't have permissions to accept this merge request - you'll get a 401. +// If the merge request is already merged or closed - you get 405 and error +// message 'Method Not Allowed'. In case the merge request is not set to be +// merged when the pipeline succeeds, you'll also get a 406 error. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#cancel-merge-when-pipeline-succeeds +func (s *MergeRequestsService) CancelMergeWhenPipelineSucceeds(pid interface{}, mergeRequest int, options ...OptionFunc) (*MergeRequest, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/cancel_merge_when_pipeline_succeeds", url.QueryEscape(project), mergeRequest) + + req, err := s.client.NewRequest("PUT", u, nil, options) + if err != nil { + return nil, nil, err + } + + m := new(MergeRequest) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// GetMergeRequestDiffVersionsOptions represents the available +// GetMergeRequestDiffVersions() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#get-mr-diff-versions +type GetMergeRequestDiffVersionsOptions ListOptions + +// GetMergeRequestDiffVersions get a list of merge request diff versions. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#get-mr-diff-versions +func (s *MergeRequestsService) GetMergeRequestDiffVersions(pid interface{}, mergeRequest int, opt *GetMergeRequestDiffVersionsOptions, options ...OptionFunc) ([]*MergeRequestDiffVersion, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/versions", url.QueryEscape(project), mergeRequest) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var v []*MergeRequestDiffVersion + resp, err := s.client.Do(req, &v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetSingleMergeRequestDiffVersion get a single MR diff version +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#get-a-single-mr-diff-version +func (s *MergeRequestsService) GetSingleMergeRequestDiffVersion(pid interface{}, mergeRequest, version int, options ...OptionFunc) (*MergeRequestDiffVersion, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/versions/%d", url.QueryEscape(project), mergeRequest, version) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + var v = new(MergeRequestDiffVersion) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// SubscribeToMergeRequest subscribes the authenticated user to the given merge request +// to receive notifications. If the user is already subscribed to the +// merge request, the status code 304 is returned. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#subscribe-to-a-merge-request +func (s *MergeRequestsService) SubscribeToMergeRequest(pid interface{}, mergeRequest int, options ...OptionFunc) (*MergeRequest, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/subscribe", url.QueryEscape(project), mergeRequest) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + m := new(MergeRequest) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// UnsubscribeFromMergeRequest unsubscribes the authenticated user from the given merge request +// to not receive notifications from that merge request. If the user is +// not subscribed to the merge request, status code 304 is returned. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#unsubscribe-from-a-merge-request +func (s *MergeRequestsService) UnsubscribeFromMergeRequest(pid interface{}, mergeRequest int, options ...OptionFunc) (*MergeRequest, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/unsubscribe", url.QueryEscape(project), mergeRequest) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + m := new(MergeRequest) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// CreateTodo manually creates a todo for the current user on a merge request. +// If there already exists a todo for the user on that merge request, +// status code 304 is returned. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#create-a-todo +func (s *MergeRequestsService) CreateTodo(pid interface{}, mergeRequest int, options ...OptionFunc) (*Todo, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/todo", url.QueryEscape(project), mergeRequest) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + t := new(Todo) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// SetTimeEstimate sets the time estimate for a single project merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#set-a-time-estimate-for-a-merge-request +func (s *MergeRequestsService) SetTimeEstimate(pid interface{}, mergeRequest int, opt *SetTimeEstimateOptions, options ...OptionFunc) (*TimeStats, *Response, error) { + return s.timeStats.setTimeEstimate(pid, "merge_requests", mergeRequest, opt, options...) +} + +// ResetTimeEstimate resets the time estimate for a single project merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#reset-the-time-estimate-for-a-merge-request +func (s *MergeRequestsService) ResetTimeEstimate(pid interface{}, mergeRequest int, options ...OptionFunc) (*TimeStats, *Response, error) { + return s.timeStats.resetTimeEstimate(pid, "merge_requests", mergeRequest, options...) +} + +// AddSpentTime adds spent time for a single project merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#add-spent-time-for-a-merge-request +func (s *MergeRequestsService) AddSpentTime(pid interface{}, mergeRequest int, opt *AddSpentTimeOptions, options ...OptionFunc) (*TimeStats, *Response, error) { + return s.timeStats.addSpentTime(pid, "merge_requests", mergeRequest, opt, options...) +} + +// ResetSpentTime resets the spent time for a single project merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#reset-spent-time-for-a-merge-request +func (s *MergeRequestsService) ResetSpentTime(pid interface{}, mergeRequest int, options ...OptionFunc) (*TimeStats, *Response, error) { + return s.timeStats.resetSpentTime(pid, "merge_requests", mergeRequest, options...) +} + +// GetTimeSpent gets the spent time for a single project merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/merge_requests.html#get-time-tracking-stats +func (s *MergeRequestsService) GetTimeSpent(pid interface{}, mergeRequest int, options ...OptionFunc) (*TimeStats, *Response, error) { + return s.timeStats.getTimeSpent(pid, "merge_requests", mergeRequest, options...) +} diff --git a/vendor/github.com/xanzy/go-gitlab/milestones.go b/vendor/github.com/xanzy/go-gitlab/milestones.go new file mode 100644 index 00000000..c1a69f93 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/milestones.go @@ -0,0 +1,267 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" + "time" +) + +// MilestonesService handles communication with the milestone related methods +// of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/milestones.html +type MilestonesService struct { + client *Client +} + +// Milestone represents a GitLab milestone. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/milestones.html +type Milestone struct { + ID int `json:"id"` + IID int `json:"iid"` + ProjectID int `json:"project_id"` + Title string `json:"title"` + Description string `json:"description"` + StartDate *ISOTime `json:"start_date"` + DueDate *ISOTime `json:"due_date"` + State string `json:"state"` + UpdatedAt *time.Time `json:"updated_at"` + CreatedAt *time.Time `json:"created_at"` +} + +func (m Milestone) String() string { + return Stringify(m) +} + +// ListMilestonesOptions represents the available ListMilestones() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/milestones.html#list-project-milestones +type ListMilestonesOptions struct { + ListOptions + IIDs []int `url:"iids,omitempty" json:"iids,omitempty"` + State string `url:"state,omitempty" json:"state,omitempty"` + Search string `url:"search,omitempty" json:"search,omitempty"` +} + +// ListMilestones returns a list of project milestones. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/milestones.html#list-project-milestones +func (s *MilestonesService) ListMilestones(pid interface{}, opt *ListMilestonesOptions, options ...OptionFunc) ([]*Milestone, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/milestones", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var m []*Milestone + resp, err := s.client.Do(req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// GetMilestone gets a single project milestone. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/milestones.html#get-single-milestone +func (s *MilestonesService) GetMilestone(pid interface{}, milestone int, options ...OptionFunc) (*Milestone, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/milestones/%d", url.QueryEscape(project), milestone) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + m := new(Milestone) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// CreateMilestoneOptions represents the available CreateMilestone() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/milestones.html#create-new-milestone +type CreateMilestoneOptions struct { + Title *string `url:"title,omitempty" json:"title,omitempty"` + Description *string `url:"description,omitempty" json:"description,omitempty"` + StartDate *ISOTime `url:"start_date,omitempty" json:"start_date,omitempty"` + DueDate *ISOTime `url:"due_date,omitempty" json:"due_date,omitempty"` +} + +// CreateMilestone creates a new project milestone. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/milestones.html#create-new-milestone +func (s *MilestonesService) CreateMilestone(pid interface{}, opt *CreateMilestoneOptions, options ...OptionFunc) (*Milestone, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/milestones", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + m := new(Milestone) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// UpdateMilestoneOptions represents the available UpdateMilestone() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/milestones.html#edit-milestone +type UpdateMilestoneOptions struct { + Title *string `url:"title,omitempty" json:"title,omitempty"` + Description *string `url:"description,omitempty" json:"description,omitempty"` + StartDate *ISOTime `url:"start_date,omitempty" json:"start_date,omitempty"` + DueDate *ISOTime `url:"due_date,omitempty" json:"due_date,omitempty"` + StateEvent *string `url:"state_event,omitempty" json:"state_event,omitempty"` +} + +// UpdateMilestone updates an existing project milestone. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/milestones.html#edit-milestone +func (s *MilestonesService) UpdateMilestone(pid interface{}, milestone int, opt *UpdateMilestoneOptions, options ...OptionFunc) (*Milestone, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/milestones/%d", url.QueryEscape(project), milestone) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + m := new(Milestone) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// DeleteMilestone deletes a specified project milestone. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/milestones.html#delete-project-milestone +func (s *MilestonesService) DeleteMilestone(pid interface{}, milestone int, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/milestones/%d", url.QueryEscape(project), milestone) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// GetMilestoneIssuesOptions represents the available GetMilestoneIssues() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/milestones.html#get-all-issues-assigned-to-a-single-milestone +type GetMilestoneIssuesOptions ListOptions + +// GetMilestoneIssues gets all issues assigned to a single project milestone. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/milestones.html#get-all-issues-assigned-to-a-single-milestone +func (s *MilestonesService) GetMilestoneIssues(pid interface{}, milestone int, opt *GetMilestoneIssuesOptions, options ...OptionFunc) ([]*Issue, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/milestones/%d/issues", url.QueryEscape(project), milestone) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var i []*Issue + resp, err := s.client.Do(req, &i) + if err != nil { + return nil, resp, err + } + + return i, resp, err +} + +// GetMilestoneMergeRequestsOptions represents the available +// GetMilestoneMergeRequests() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/milestones.html#get-all-merge-requests-assigned-to-a-single-milestone +type GetMilestoneMergeRequestsOptions ListOptions + +// GetMilestoneMergeRequests gets all merge requests assigned to a single +// project milestone. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/milestones.html#get-all-merge-requests-assigned-to-a-single-milestone +func (s *MilestonesService) GetMilestoneMergeRequests(pid interface{}, milestone int, opt *GetMilestoneMergeRequestsOptions, options ...OptionFunc) ([]*MergeRequest, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/milestones/%d/merge_requests", url.QueryEscape(project), milestone) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var mr []*MergeRequest + resp, err := s.client.Do(req, &mr) + if err != nil { + return nil, resp, err + } + + return mr, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/namespaces.go b/vendor/github.com/xanzy/go-gitlab/namespaces.go new file mode 100644 index 00000000..9add6449 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/namespaces.go @@ -0,0 +1,122 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" +) + +// NamespacesService handles communication with the namespace related methods +// of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/namespaces.html +type NamespacesService struct { + client *Client +} + +// Namespace represents a GitLab namespace. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/namespaces.html +type Namespace struct { + ID int `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + Kind string `json:"kind"` + FullPath string `json:"full_path"` + ParentID int `json:"parent_id"` + MembersCountWithDescendants int `json:"members_count_with_descendants"` +} + +func (n Namespace) String() string { + return Stringify(n) +} + +// ListNamespacesOptions represents the available ListNamespaces() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/namespaces.html#list-namespaces +type ListNamespacesOptions struct { + ListOptions + Search *string `url:"search,omitempty" json:"search,omitempty"` +} + +// ListNamespaces gets a list of projects accessible by the authenticated user. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/namespaces.html#list-namespaces +func (s *NamespacesService) ListNamespaces(opt *ListNamespacesOptions, options ...OptionFunc) ([]*Namespace, *Response, error) { + req, err := s.client.NewRequest("GET", "namespaces", opt, options) + if err != nil { + return nil, nil, err + } + + var n []*Namespace + resp, err := s.client.Do(req, &n) + if err != nil { + return nil, resp, err + } + + return n, resp, err +} + +// SearchNamespace gets all namespaces that match your string in their name +// or path. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/namespaces.html#search-for-namespace +func (s *NamespacesService) SearchNamespace(query string, options ...OptionFunc) ([]*Namespace, *Response, error) { + var q struct { + Search string `url:"search,omitempty" json:"search,omitempty"` + } + q.Search = query + + req, err := s.client.NewRequest("GET", "namespaces", &q, options) + if err != nil { + return nil, nil, err + } + + var n []*Namespace + resp, err := s.client.Do(req, &n) + if err != nil { + return nil, resp, err + } + + return n, resp, err +} + +// GetNamespace gets a namespace by id. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/namespaces.html#get-namespace-by-id +func (s *NamespacesService) GetNamespace(id interface{}, options ...OptionFunc) (*Namespace, *Response, error) { + namespace, err := parseID(id) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("namespaces/%s", namespace) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + n := new(Namespace) + resp, err := s.client.Do(req, n) + if err != nil { + return nil, resp, err + } + + return n, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/notes.go b/vendor/github.com/xanzy/go-gitlab/notes.go new file mode 100644 index 00000000..2ad7f9d4 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/notes.go @@ -0,0 +1,490 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" + "time" +) + +// NotesService handles communication with the notes related methods +// of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/notes.html +type NotesService struct { + client *Client +} + +// Note represents a GitLab note. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/notes.html +type Note struct { + ID int `json:"id"` + Body string `json:"body"` + Attachment string `json:"attachment"` + Title string `json:"title"` + FileName string `json:"file_name"` + Author struct { + ID int `json:"id"` + Username string `json:"username"` + Email string `json:"email"` + Name string `json:"name"` + State string `json:"state"` + CreatedAt *time.Time `json:"created_at"` + AvatarURL string `json:"avatar_url"` + WebURL string `json:"web_url"` + } `json:"author"` + System bool `json:"system"` + ExpiresAt *time.Time `json:"expires_at"` + UpdatedAt *time.Time `json:"updated_at"` + CreatedAt *time.Time `json:"created_at"` + NoteableID int `json:"noteable_id"` + NoteableType string `json:"noteable_type"` + NoteableIID int `json:"noteable_iid"` +} + +func (n Note) String() string { + return Stringify(n) +} + +// ListIssueNotesOptions represents the available ListIssueNotes() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notes.html#list-project-issue-notes +type ListIssueNotesOptions ListOptions + +// ListIssueNotes gets a list of all notes for a single issue. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notes.html#list-project-issue-notes +func (s *NotesService) ListIssueNotes(pid interface{}, issue int, opt *ListIssueNotesOptions, options ...OptionFunc) ([]*Note, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/issues/%d/notes", url.QueryEscape(project), issue) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var n []*Note + resp, err := s.client.Do(req, &n) + if err != nil { + return nil, resp, err + } + + return n, resp, err +} + +// GetIssueNote returns a single note for a specific project issue. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notes.html#get-single-issue-note +func (s *NotesService) GetIssueNote(pid interface{}, issue, note int, options ...OptionFunc) (*Note, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/issues/%d/notes/%d", url.QueryEscape(project), issue, note) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + n := new(Note) + resp, err := s.client.Do(req, n) + if err != nil { + return nil, resp, err + } + + return n, resp, err +} + +// CreateIssueNoteOptions represents the available CreateIssueNote() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notes.html#create-new-issue-note +type CreateIssueNoteOptions struct { + Body *string `url:"body,omitempty" json:"body,omitempty"` +} + +// CreateIssueNote creates a new note to a single project issue. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notes.html#create-new-issue-note +func (s *NotesService) CreateIssueNote(pid interface{}, issue int, opt *CreateIssueNoteOptions, options ...OptionFunc) (*Note, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/issues/%d/notes", url.QueryEscape(project), issue) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + n := new(Note) + resp, err := s.client.Do(req, n) + if err != nil { + return nil, resp, err + } + + return n, resp, err +} + +// UpdateIssueNoteOptions represents the available UpdateIssueNote() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notes.html#modify-existing-issue-note +type UpdateIssueNoteOptions struct { + Body *string `url:"body,omitempty" json:"body,omitempty"` +} + +// UpdateIssueNote modifies existing note of an issue. +// +// https://docs.gitlab.com/ce/api/notes.html#modify-existing-issue-note +func (s *NotesService) UpdateIssueNote(pid interface{}, issue, note int, opt *UpdateIssueNoteOptions, options ...OptionFunc) (*Note, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/issues/%d/notes/%d", url.QueryEscape(project), issue, note) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + n := new(Note) + resp, err := s.client.Do(req, n) + if err != nil { + return nil, resp, err + } + + return n, resp, err +} + +// DeleteIssueNote deletes an existing note of an issue. +// +// https://docs.gitlab.com/ce/api/notes.html#delete-an-issue-note +func (s *NotesService) DeleteIssueNote(pid interface{}, issue, note int, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/issues/%d/notes/%d", url.QueryEscape(project), issue, note) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// ListSnippetNotesOptions represents the available ListSnippetNotes() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notes.html#list-all-snippet-notes +type ListSnippetNotesOptions ListOptions + +// ListSnippetNotes gets a list of all notes for a single snippet. Snippet +// notes are comments users can post to a snippet. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notes.html#list-all-snippet-notes +func (s *NotesService) ListSnippetNotes(pid interface{}, snippet int, opt *ListSnippetNotesOptions, options ...OptionFunc) ([]*Note, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/snippets/%d/notes", url.QueryEscape(project), snippet) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var n []*Note + resp, err := s.client.Do(req, &n) + if err != nil { + return nil, resp, err + } + + return n, resp, err +} + +// GetSnippetNote returns a single note for a given snippet. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notes.html#get-single-snippet-note +func (s *NotesService) GetSnippetNote(pid interface{}, snippet, note int, options ...OptionFunc) (*Note, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/snippets/%d/notes/%d", url.QueryEscape(project), snippet, note) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + n := new(Note) + resp, err := s.client.Do(req, n) + if err != nil { + return nil, resp, err + } + + return n, resp, err +} + +// CreateSnippetNoteOptions represents the available CreateSnippetNote() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notes.html#create-new-snippet-note +type CreateSnippetNoteOptions struct { + Body *string `url:"body,omitempty" json:"body,omitempty"` +} + +// CreateSnippetNote creates a new note for a single snippet. Snippet notes are +// comments users can post to a snippet. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notes.html#create-new-snippet-note +func (s *NotesService) CreateSnippetNote(pid interface{}, snippet int, opt *CreateSnippetNoteOptions, options ...OptionFunc) (*Note, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/snippets/%d/notes", url.QueryEscape(project), snippet) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + n := new(Note) + resp, err := s.client.Do(req, n) + if err != nil { + return nil, resp, err + } + + return n, resp, err +} + +// UpdateSnippetNoteOptions represents the available UpdateSnippetNote() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notes.html#modify-existing-snippet-note +type UpdateSnippetNoteOptions struct { + Body *string `url:"body,omitempty" json:"body,omitempty"` +} + +// UpdateSnippetNote modifies existing note of a snippet. +// +// https://docs.gitlab.com/ce/api/notes.html#modify-existing-snippet-note +func (s *NotesService) UpdateSnippetNote(pid interface{}, snippet, note int, opt *UpdateSnippetNoteOptions, options ...OptionFunc) (*Note, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/snippets/%d/notes/%d", url.QueryEscape(project), snippet, note) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + n := new(Note) + resp, err := s.client.Do(req, n) + if err != nil { + return nil, resp, err + } + + return n, resp, err +} + +// DeleteSnippetNote deletes an existing note of a snippet. +// +// https://docs.gitlab.com/ce/api/notes.html#delete-a-snippet-note +func (s *NotesService) DeleteSnippetNote(pid interface{}, snippet, note int, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/snippets/%d/notes/%d", url.QueryEscape(project), snippet, note) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// ListMergeRequestNotesOptions represents the available ListMergeRequestNotes() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notes.html#list-all-merge-request-notes +type ListMergeRequestNotesOptions ListOptions + +// ListMergeRequestNotes gets a list of all notes for a single merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notes.html#list-all-merge-request-notes +func (s *NotesService) ListMergeRequestNotes(pid interface{}, mergeRequest int, opt *ListMergeRequestNotesOptions, options ...OptionFunc) ([]*Note, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/notes", url.QueryEscape(project), mergeRequest) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var n []*Note + resp, err := s.client.Do(req, &n) + if err != nil { + return nil, resp, err + } + + return n, resp, err +} + +// GetMergeRequestNote returns a single note for a given merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notes.html#get-single-merge-request-note +func (s *NotesService) GetMergeRequestNote(pid interface{}, mergeRequest, note int, options ...OptionFunc) (*Note, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/notes/%d", url.QueryEscape(project), mergeRequest, note) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + n := new(Note) + resp, err := s.client.Do(req, n) + if err != nil { + return nil, resp, err + } + + return n, resp, err +} + +// CreateMergeRequestNoteOptions represents the available +// CreateMergeRequestNote() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notes.html#create-new-merge-request-note +type CreateMergeRequestNoteOptions struct { + Body *string `url:"body,omitempty" json:"body,omitempty"` +} + +// CreateMergeRequestNote creates a new note for a single merge request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notes.html#create-new-merge-request-note +func (s *NotesService) CreateMergeRequestNote(pid interface{}, mergeRequest int, opt *CreateMergeRequestNoteOptions, options ...OptionFunc) (*Note, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/merge_requests/%d/notes", url.QueryEscape(project), mergeRequest) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + n := new(Note) + resp, err := s.client.Do(req, n) + if err != nil { + return nil, resp, err + } + + return n, resp, err +} + +// UpdateMergeRequestNoteOptions represents the available +// UpdateMergeRequestNote() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notes.html#modify-existing-merge-request-note +type UpdateMergeRequestNoteOptions struct { + Body *string `url:"body,omitempty" json:"body,omitempty"` +} + +// UpdateMergeRequestNote modifies existing note of a merge request. +// +// https://docs.gitlab.com/ce/api/notes.html#modify-existing-merge-request-note +func (s *NotesService) UpdateMergeRequestNote(pid interface{}, mergeRequest, note int, opt *UpdateMergeRequestNoteOptions, options ...OptionFunc) (*Note, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf( + "projects/%s/merge_requests/%d/notes/%d", url.QueryEscape(project), mergeRequest, note) + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + n := new(Note) + resp, err := s.client.Do(req, n) + if err != nil { + return nil, resp, err + } + + return n, resp, err +} + +// DeleteMergeRequestNote deletes an existing note of a merge request. +// +// https://docs.gitlab.com/ce/api/notes.html#delete-a-merge-request-note +func (s *NotesService) DeleteMergeRequestNote(pid interface{}, mergeRequest, note int, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf( + "projects/%s/merge_requests/%d/notes/%d", url.QueryEscape(project), mergeRequest, note) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/xanzy/go-gitlab/notifications.go b/vendor/github.com/xanzy/go-gitlab/notifications.go new file mode 100644 index 00000000..a5501dd7 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/notifications.go @@ -0,0 +1,214 @@ +package gitlab + +import ( + "errors" + "fmt" + "net/url" +) + +// NotificationSettingsService handles communication with the notification settings +// related methods of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/notification_settings.html +type NotificationSettingsService struct { + client *Client +} + +// NotificationSettings represents the Gitlab notification setting. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notification_settings.html#notification-settings +type NotificationSettings struct { + Level NotificationLevelValue `json:"level"` + NotificationEmail string `json:"notification_email"` + Events *NotificationEvents `json:"events"` +} + +// NotificationEvents represents the available notification setting events. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notification_settings.html#notification-settings +type NotificationEvents struct { + CloseIssue bool `json:"close_issue"` + CloseMergeRequest bool `json:"close_merge_request"` + FailedPipeline bool `json:"failed_pipeline"` + MergeMergeRequest bool `json:"merge_merge_request"` + NewIssue bool `json:"new_issue"` + NewMergeRequest bool `json:"new_merge_request"` + NewNote bool `json:"new_note"` + ReassignIssue bool `json:"reassign_issue"` + ReassignMergeRequest bool `json:"reassign_merge_request"` + ReopenIssue bool `json:"reopen_issue"` + ReopenMergeRequest bool `json:"reopen_merge_request"` + SuccessPipeline bool `json:"success_pipeline"` +} + +func (ns NotificationSettings) String() string { + return Stringify(ns) +} + +// GetGlobalSettings returns current notification settings and email address. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notification_settings.html#global-notification-settings +func (s *NotificationSettingsService) GetGlobalSettings(options ...OptionFunc) (*NotificationSettings, *Response, error) { + u := "notification_settings" + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + ns := new(NotificationSettings) + resp, err := s.client.Do(req, ns) + if err != nil { + return nil, resp, err + } + + return ns, resp, err +} + +// NotificationSettingsOptions represents the available options that can be passed +// to the API when updating the notification settings. +type NotificationSettingsOptions struct { + Level *NotificationLevelValue `url:"level,omitempty" json:"level,omitempty"` + NotificationEmail *string `url:"notification_email,omitempty" json:"notification_email,omitempty"` + CloseIssue *bool `url:"close_issue,omitempty" json:"close_issue,omitempty"` + CloseMergeRequest *bool `url:"close_merge_request,omitempty" json:"close_merge_request,omitempty"` + FailedPipeline *bool `url:"failed_pipeline,omitempty" json:"failed_pipeline,omitempty"` + MergeMergeRequest *bool `url:"merge_merge_request,omitempty" json:"merge_merge_request,omitempty"` + NewIssue *bool `url:"new_issue,omitempty" json:"new_issue,omitempty"` + NewMergeRequest *bool `url:"new_merge_request,omitempty" json:"new_merge_request,omitempty"` + NewNote *bool `url:"new_note,omitempty" json:"new_note,omitempty"` + ReassignIssue *bool `url:"reassign_issue,omitempty" json:"reassign_issue,omitempty"` + ReassignMergeRequest *bool `url:"reassign_merge_request,omitempty" json:"reassign_merge_request,omitempty"` + ReopenIssue *bool `url:"reopen_issue,omitempty" json:"reopen_issue,omitempty"` + ReopenMergeRequest *bool `url:"reopen_merge_request,omitempty" json:"reopen_merge_request,omitempty"` + SuccessPipeline *bool `url:"success_pipeline,omitempty" json:"success_pipeline,omitempty"` +} + +// UpdateGlobalSettings updates current notification settings and email address. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notification_settings.html#update-global-notification-settings +func (s *NotificationSettingsService) UpdateGlobalSettings(opt *NotificationSettingsOptions, options ...OptionFunc) (*NotificationSettings, *Response, error) { + if opt.Level != nil && *opt.Level == GlobalNotificationLevel { + return nil, nil, errors.New( + "notification level 'global' is not valid for global notification settings") + } + + u := "notification_settings" + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + ns := new(NotificationSettings) + resp, err := s.client.Do(req, ns) + if err != nil { + return nil, resp, err + } + + return ns, resp, err +} + +// GetSettingsForGroup returns current group notification settings. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notification_settings.html#group-project-level-notification-settings +func (s *NotificationSettingsService) GetSettingsForGroup(gid interface{}, options ...OptionFunc) (*NotificationSettings, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s/notification_settings", url.QueryEscape(group)) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + ns := new(NotificationSettings) + resp, err := s.client.Do(req, ns) + if err != nil { + return nil, resp, err + } + + return ns, resp, err +} + +// GetSettingsForProject returns current project notification settings. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notification_settings.html#group-project-level-notification-settings +func (s *NotificationSettingsService) GetSettingsForProject(pid interface{}, options ...OptionFunc) (*NotificationSettings, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/notification_settings", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + ns := new(NotificationSettings) + resp, err := s.client.Do(req, ns) + if err != nil { + return nil, resp, err + } + + return ns, resp, err +} + +// UpdateSettingsForGroup updates current group notification settings. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notification_settings.html#update-group-project-level-notification-settings +func (s *NotificationSettingsService) UpdateSettingsForGroup(gid interface{}, opt *NotificationSettingsOptions, options ...OptionFunc) (*NotificationSettings, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s/notification_settings", url.QueryEscape(group)) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + ns := new(NotificationSettings) + resp, err := s.client.Do(req, ns) + if err != nil { + return nil, resp, err + } + + return ns, resp, err +} + +// UpdateSettingsForProject updates current project notification settings. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/notification_settings.html#update-group-project-level-notification-settings +func (s *NotificationSettingsService) UpdateSettingsForProject(pid interface{}, opt *NotificationSettingsOptions, options ...OptionFunc) (*NotificationSettings, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/notification_settings", url.QueryEscape(project)) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + ns := new(NotificationSettings) + resp, err := s.client.Do(req, ns) + if err != nil { + return nil, resp, err + } + + return ns, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/pages_domains.go b/vendor/github.com/xanzy/go-gitlab/pages_domains.go new file mode 100644 index 00000000..3cd72e21 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/pages_domains.go @@ -0,0 +1,175 @@ +package gitlab + +import ( + "fmt" + "net/url" + "time" +) + +// PagesDomainsService handles communication with the pages domains +// related methods of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/pages_domains.html +type PagesDomainsService struct { + client *Client +} + +// PagesDomain represents a pages domain. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/pages_domains.html +type PagesDomain struct { + Domain string `json:"domain"` + URL string `json:"url"` + ProjectID int `json:"project_id"` + Verified bool `json:"verified"` + VerificationCode string `json:"verification_code"` + EnabledUntil *time.Time `json:"enabled_until"` + Certificate struct { + Expired bool `json:"expired"` + Expiration *time.Time `json:"expiration"` + } `json:"certificate"` +} + +// ListPagesDomainsOptions represents the available ListPagesDomains() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pages_domains.html#list-pages-domains +type ListPagesDomainsOptions ListOptions + +// ListPagesDomains gets a list of project pages domains. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pages_domains.html#list-pages-domains +func (s *PagesDomainsService) ListPagesDomains(pid interface{}, opt *ListPagesDomainsOptions, options ...OptionFunc) ([]*PagesDomain, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/pages/domains", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var pd []*PagesDomain + resp, err := s.client.Do(req, &pd) + if err != nil { + return nil, resp, err + } + + return pd, resp, err +} + +// GetPagesDomain get a specific pages domain for a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pages_domains.html#single-pages-domain +func (s *PagesDomainsService) GetPagesDomain(pid interface{}, domain string, options ...OptionFunc) (*PagesDomain, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/pages/domains/%s", url.QueryEscape(project), domain) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + pd := new(PagesDomain) + resp, err := s.client.Do(req, pd) + if err != nil { + return nil, resp, err + } + + return pd, resp, err +} + +// CreatePagesDomainOptions represents the available CreatePagesDomain() options. +// +// GitLab API docs: +// // https://docs.gitlab.com/ce/api/pages_domains.html#create-new-pages-domain +type CreatePagesDomainOptions struct { + Domain *string `url:"domain,omitempty" json:"domain,omitempty"` + Certificate *string `url:"certifiate,omitempty" json:"certifiate,omitempty"` + Key *string `url:"key,omitempty" json:"key,omitempty"` +} + +// CreatePagesDomain creates a new project pages domain. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pages_domains.html#create-new-pages-domain +func (s *PagesDomainsService) CreatePagesDomain(pid interface{}, opt *CreatePagesDomainOptions, options ...OptionFunc) (*PagesDomain, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/pages/domains", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + pd := new(PagesDomain) + resp, err := s.client.Do(req, pd) + if err != nil { + return nil, resp, err + } + + return pd, resp, err +} + +// UpdatePagesDomainOptions represents the available UpdatePagesDomain() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pages_domains.html#update-pages-domain +type UpdatePagesDomainOptions struct { + Cerificate *string `url:"certifiate" json:"certifiate"` + Key *string `url:"key" json:"key"` +} + +// UpdatePagesDomain updates an existing project pages domain. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pages_domains.html#update-pages-domain +func (s *PagesDomainsService) UpdatePagesDomain(pid interface{}, domain string, opt *UpdatePagesDomainOptions, options ...OptionFunc) (*PagesDomain, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/pages/domains/%s", url.QueryEscape(project), domain) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + pd := new(PagesDomain) + resp, err := s.client.Do(req, pd) + if err != nil { + return nil, resp, err + } + + return pd, resp, err +} + +// DeletePagesDomain deletes an existing prject pages domain. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pages_domains.html#delete-pages-domain +func (s *PagesDomainsService) DeletePagesDomain(pid interface{}, domain string, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/pages/domains/%s", url.QueryEscape(project), domain) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/xanzy/go-gitlab/pipeline_schedules.go b/vendor/github.com/xanzy/go-gitlab/pipeline_schedules.go new file mode 100644 index 00000000..bb935835 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/pipeline_schedules.go @@ -0,0 +1,341 @@ +// +// Copyright 2018, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" + "time" +) + +// PipelineSchedulesService handles communication with the pipeline +// schedules related methods of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/pipeline_schedules.html +type PipelineSchedulesService struct { + client *Client +} + +// PipelineVariable represents a pipeline schedule variable. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_schedules.html#pipeline-schedule-variable +type PipelineVariable struct { + Key string `json:"key"` + Value string `json:"value"` +} + +// PipelineSchedule represents a pipeline schedule. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_schedules.html +type PipelineSchedule struct { + ID int `json:"id"` + Description string `json:"description"` + Ref string `json:"ref"` + Cron string `json:"cron"` + CronTimezone string `json:"cron_timezone"` + NextRunAt *time.Time `json:"next_run_at"` + Active bool `json:"active"` + CreatedAt *time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at"` + Owner *User `json:"owner"` + LastPipeline struct { + ID int `json:"id"` + Sha string `json:"sha"` + Ref string `json:"ref"` + Status string `json:"status"` + } `json:"last_pipeline"` + Variables []*PipelineVariable `json:"variables"` +} + +// ListPipelineSchedulesOptions represents the available ListPipelineTriggers() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_triggers.html#list-project-triggers +type ListPipelineSchedulesOptions ListOptions + +// ListPipelineSchedules gets a list of project triggers. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_schedules.html +func (s *PipelineSchedulesService) ListPipelineSchedules(pid interface{}, opt *ListPipelineSchedulesOptions, options ...OptionFunc) ([]*PipelineSchedule, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/pipeline_schedules", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var ps []*PipelineSchedule + resp, err := s.client.Do(req, &ps) + if err != nil { + return nil, resp, err + } + + return ps, resp, err +} + +// GetPipelineSchedule gets a pipeline schedule. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_schedules.html +func (s *PipelineSchedulesService) GetPipelineSchedule(pid interface{}, schedule int, options ...OptionFunc) (*PipelineSchedule, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/pipeline_schedules/%d", url.QueryEscape(project), schedule) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + p := new(PipelineSchedule) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// CreatePipelineScheduleOptions represents the available +// CreatePipelineSchedule() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule +type CreatePipelineScheduleOptions struct { + Description *string `url:"description" json:"description"` + Ref *string `url:"ref" json:"ref"` + Cron *string `url:"cron" json:"cron"` + CronTimezone *string `url:"cron_timezone,omitempty" json:"cron_timezone,omitempty"` + Active *bool `url:"active,omitempty" json:"active,omitempty"` +} + +// CreatePipelineSchedule creates a pipeline schedule. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule +func (s *PipelineSchedulesService) CreatePipelineSchedule(pid interface{}, opt *CreatePipelineScheduleOptions, options ...OptionFunc) (*PipelineSchedule, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/pipeline_schedules", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + p := new(PipelineSchedule) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// EditPipelineScheduleOptions represents the available +// EditPipelineSchedule() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule +type EditPipelineScheduleOptions struct { + Description *string `url:"description,omitempty" json:"description,omitempty"` + Ref *string `url:"ref,omitempty" json:"ref,omitempty"` + Cron *string `url:"cron,omitempty" json:"cron,omitempty"` + CronTimezone *string `url:"cron_timezone,omitempty" json:"cron_timezone,omitempty"` + Active *bool `url:"active,omitempty" json:"active,omitempty"` +} + +// EditPipelineSchedule edits a pipeline schedule. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_schedules.html#edit-a-pipeline-schedule +func (s *PipelineSchedulesService) EditPipelineSchedule(pid interface{}, schedule int, opt *EditPipelineScheduleOptions, options ...OptionFunc) (*PipelineSchedule, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/pipeline_schedules/%d", url.QueryEscape(project), schedule) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + p := new(PipelineSchedule) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// TakeOwnershipOfPipelineSchedule sets the owner of the specified +// pipeline schedule to the user issuing the request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_schedules.html#take-ownership-of-a-pipeline-schedule +func (s *PipelineSchedulesService) TakeOwnershipOfPipelineSchedule(pid interface{}, schedule int, options ...OptionFunc) (*PipelineSchedule, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/take_ownership", url.QueryEscape(project), schedule) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + p := new(PipelineSchedule) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// DeletePipelineSchedule deletes a pipeline schedule. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_schedules.html#delete-a-pipeline-schedule +func (s *PipelineSchedulesService) DeletePipelineSchedule(pid interface{}, schedule int, options ...OptionFunc) (*PipelineSchedule, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/pipeline_schedules/%d", url.QueryEscape(project), schedule) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, nil, err + } + + p := new(PipelineSchedule) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// CreatePipelineScheduleVariableOptions represents the available +// CreatePipelineScheduleVariable() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule +type CreatePipelineScheduleVariableOptions struct { + Key *string `url:"key" json:"key"` + Value *string `url:"value" json:"value"` +} + +// CreatePipelineScheduleVariable creates a pipeline schedule variable. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule +func (s *PipelineSchedulesService) CreatePipelineScheduleVariable(pid interface{}, schedule int, opt *CreatePipelineScheduleVariableOptions, options ...OptionFunc) (*PipelineVariable, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/variables", url.QueryEscape(project), schedule) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + p := new(PipelineVariable) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// EditPipelineScheduleVariableOptions represents the available +// EditPipelineScheduleVariable() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_schedules.html#edit-a-pipeline-schedule-variable +type EditPipelineScheduleVariableOptions struct { + Value *string `url:"value" json:"value"` +} + +// EditPipelineScheduleVariable creates a pipeline schedule variable. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_schedules.html#edit-a-pipeline-schedule-variable +func (s *PipelineSchedulesService) EditPipelineScheduleVariable(pid interface{}, schedule int, key string, opt *EditPipelineScheduleVariableOptions, options ...OptionFunc) (*PipelineVariable, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/variables/%s", url.QueryEscape(project), schedule, key) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + p := new(PipelineVariable) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// DeletePipelineScheduleVariable creates a pipeline schedule variable. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_schedules.html#delete-a-pipeline-schedule-variable +func (s *PipelineSchedulesService) DeletePipelineScheduleVariable(pid interface{}, schedule int, key string, options ...OptionFunc) (*PipelineVariable, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/variables/%s", url.QueryEscape(project), schedule, key) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, nil, err + } + + p := new(PipelineVariable) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/pipeline_triggers.go b/vendor/github.com/xanzy/go-gitlab/pipeline_triggers.go new file mode 100644 index 00000000..6e8dfb8a --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/pipeline_triggers.go @@ -0,0 +1,232 @@ +package gitlab + +import ( + "fmt" + "net/url" + "time" +) + +// PipelineTriggersService handles Project pipeline triggers. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_triggers.html +type PipelineTriggersService struct { + client *Client +} + +// PipelineTrigger represents a project pipeline trigger. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_triggers.html#pipeline-triggers +type PipelineTrigger struct { + ID int `json:"id"` + Description string `json:"description"` + CreatedAt *time.Time `json:"created_at"` + DeletedAt *time.Time `json:"deleted_at"` + LastUsed *time.Time `json:"last_used"` + Token string `json:"token"` + UpdatedAt *time.Time `json:"updated_at"` + Owner *User `json:"owner"` +} + +// ListPipelineTriggersOptions represents the available ListPipelineTriggers() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_triggers.html#list-project-triggers +type ListPipelineTriggersOptions ListOptions + +// ListPipelineTriggers gets a list of project triggers. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_triggers.html#list-project-triggers +func (s *PipelineTriggersService) ListPipelineTriggers(pid interface{}, opt *ListPipelineTriggersOptions, options ...OptionFunc) ([]*PipelineTrigger, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/triggers", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var pt []*PipelineTrigger + resp, err := s.client.Do(req, &pt) + if err != nil { + return nil, resp, err + } + + return pt, resp, err +} + +// GetPipelineTrigger gets a specific pipeline trigger for a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_triggers.html#get-trigger-details +func (s *PipelineTriggersService) GetPipelineTrigger(pid interface{}, trigger int, options ...OptionFunc) (*PipelineTrigger, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/triggers/%d", url.QueryEscape(project), trigger) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + pt := new(PipelineTrigger) + resp, err := s.client.Do(req, pt) + if err != nil { + return nil, resp, err + } + + return pt, resp, err +} + +// AddPipelineTriggerOptions represents the available AddPipelineTrigger() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_triggers.html#create-a-project-trigger +type AddPipelineTriggerOptions struct { + Description *string `url:"description,omitempty" json:"description,omitempty"` +} + +// AddPipelineTrigger adds a pipeline trigger to a specified project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_triggers.html#create-a-project-trigger +func (s *PipelineTriggersService) AddPipelineTrigger(pid interface{}, opt *AddPipelineTriggerOptions, options ...OptionFunc) (*PipelineTrigger, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/triggers", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + pt := new(PipelineTrigger) + resp, err := s.client.Do(req, pt) + if err != nil { + return nil, resp, err + } + + return pt, resp, err +} + +// EditPipelineTriggerOptions represents the available EditPipelineTrigger() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_triggers.html#update-a-project-trigger +type EditPipelineTriggerOptions struct { + Description *string `url:"description,omitempty" json:"description,omitempty"` +} + +// EditPipelineTrigger edits a trigger for a specified project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_triggers.html#update-a-project-trigger +func (s *PipelineTriggersService) EditPipelineTrigger(pid interface{}, trigger int, opt *EditPipelineTriggerOptions, options ...OptionFunc) (*PipelineTrigger, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/triggers/%d", url.QueryEscape(project), trigger) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + pt := new(PipelineTrigger) + resp, err := s.client.Do(req, pt) + if err != nil { + return nil, resp, err + } + + return pt, resp, err +} + +// TakeOwnershipOfPipelineTrigger sets the owner of the specified +// pipeline trigger to the user issuing the request. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_triggers.html#take-ownership-of-a-project-trigger +func (s *PipelineTriggersService) TakeOwnershipOfPipelineTrigger(pid interface{}, trigger int, options ...OptionFunc) (*PipelineTrigger, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/triggers/%d/take_ownership", url.QueryEscape(project), trigger) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + pt := new(PipelineTrigger) + resp, err := s.client.Do(req, pt) + if err != nil { + return nil, resp, err + } + + return pt, resp, err +} + +// DeletePipelineTrigger removes a trigger from a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipeline_triggers.html#remove-a-project-trigger +func (s *PipelineTriggersService) DeletePipelineTrigger(pid interface{}, trigger int, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/triggers/%d", url.QueryEscape(project), trigger) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// RunPipelineTriggerOptions represents the available RunPipelineTrigger() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/ci/triggers/README.html#triggering-a-pipeline +type RunPipelineTriggerOptions struct { + Ref *string `url:"ref" json:"ref"` + Token *string `url:"token" json:"token"` + Variables map[string]string `url:"variables,omitempty" json:"variables,omitempty"` +} + +// RunPipelineTrigger starts a trigger from a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/ci/triggers/README.html#triggering-a-pipeline +func (s *PipelineTriggersService) RunPipelineTrigger(pid interface{}, opt *RunPipelineTriggerOptions, options ...OptionFunc) (*Pipeline, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/trigger/pipeline", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + pt := new(Pipeline) + resp, err := s.client.Do(req, pt) + if err != nil { + return nil, resp, err + } + + return pt, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/pipelines.go b/vendor/github.com/xanzy/go-gitlab/pipelines.go new file mode 100644 index 00000000..0c736dd2 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/pipelines.go @@ -0,0 +1,220 @@ +// +// Copyright 2017, Igor Varavko +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" + "time" +) + +// PipelinesService handles communication with the repositories related +// methods of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html +type PipelinesService struct { + client *Client +} + +// Pipeline represents a GitLab pipeline. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html +type Pipeline struct { + ID int `json:"id"` + Status string `json:"status"` + Ref string `json:"ref"` + Sha string `json:"sha"` + BeforeSha string `json:"before_sha"` + Tag bool `json:"tag"` + YamlErrors string `json:"yaml_errors"` + User struct { + Name string `json:"name"` + Username string `json:"username"` + ID int `json:"id"` + State string `json:"state"` + AvatarURL string `json:"avatar_url"` + WebURL string `json:"web_url"` + } + UpdatedAt *time.Time `json:"updated_at"` + CreatedAt *time.Time `json:"created_at"` + StartedAt *time.Time `json:"started_at"` + FinishedAt *time.Time `json:"finished_at"` + CommittedAt *time.Time `json:"committed_at"` + Duration int `json:"duration"` + Coverage string `json:"coverage"` +} + +func (i Pipeline) String() string { + return Stringify(i) +} + +// PipelineList represents a GitLab list project pipelines +// +// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#list-project-pipelines +type PipelineList []struct { + ID int `json:"id"` + Status string `json:"status"` + Ref string `json:"ref"` + Sha string `json:"sha"` +} + +func (i PipelineList) String() string { + return Stringify(i) +} + +// ListProjectPipelinesOptions represents the available ListProjectPipelines() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#list-project-pipelines +type ListProjectPipelinesOptions struct { + ListOptions + Scope *string `url:"scope,omitempty" json:"scope,omitempty"` + Status *BuildStateValue `url:"status,omitempty" json:"status,omitempty"` + Ref *string `url:"ref,omitempty" json:"ref,omitempty"` + YamlErrors *bool `url:"yaml_errors,omitempty" json:"yaml_errors,omitempty"` + Name *string `url:"name,omitempty" json:"name,omitempty"` + Username *string `url:"username,omitempty" json:"username,omitempty"` + OrderBy *OrderByValue `url:"order_by,omitempty" json:"order_by,omitempty"` + Sort *string `url:"sort,omitempty" json:"sort,omitempty"` +} + +// ListProjectPipelines gets a list of project piplines. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#list-project-pipelines +func (s *PipelinesService) ListProjectPipelines(pid interface{}, opt *ListProjectPipelinesOptions, options ...OptionFunc) (PipelineList, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/pipelines", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var p PipelineList + resp, err := s.client.Do(req, &p) + if err != nil { + return nil, resp, err + } + return p, resp, err +} + +// GetPipeline gets a single project pipeline. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#get-a-single-pipeline +func (s *PipelinesService) GetPipeline(pid interface{}, pipeline int, options ...OptionFunc) (*Pipeline, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/pipelines/%d", url.QueryEscape(project), pipeline) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + p := new(Pipeline) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// CreatePipelineOptions represents the available CreatePipeline() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#create-a-new-pipeline +type CreatePipelineOptions struct { + Ref *string `url:"ref,omitempty" json:"ref"` +} + +// CreatePipeline creates a new project pipeline. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#create-a-new-pipeline +func (s *PipelinesService) CreatePipeline(pid interface{}, opt *CreatePipelineOptions, options ...OptionFunc) (*Pipeline, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/pipeline", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + p := new(Pipeline) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// RetryPipelineBuild retries failed builds in a pipeline +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/pipelines.html#retry-failed-builds-in-a-pipeline +func (s *PipelinesService) RetryPipelineBuild(pid interface{}, pipelineID int, options ...OptionFunc) (*Pipeline, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/pipelines/%d/retry", project, pipelineID) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + p := new(Pipeline) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// CancelPipelineBuild cancels a pipeline builds +// +// GitLab API docs: +//https://docs.gitlab.com/ce/api/pipelines.html#cancel-a-pipelines-builds +func (s *PipelinesService) CancelPipelineBuild(pid interface{}, pipelineID int, options ...OptionFunc) (*Pipeline, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/pipelines/%d/cancel", project, pipelineID) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + p := new(Pipeline) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/project_members.go b/vendor/github.com/xanzy/go-gitlab/project_members.go new file mode 100644 index 00000000..70beb72d --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/project_members.go @@ -0,0 +1,179 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" +) + +// ProjectMembersService handles communication with the project members +// related methods of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/members.html +type ProjectMembersService struct { + client *Client +} + +// ListProjectMembersOptions represents the available ListProjectMembers() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/members.html#list-all-members-of-a-group-or-project +type ListProjectMembersOptions struct { + ListOptions + Query *string `url:"query,omitempty" json:"query,omitempty"` +} + +// ListProjectMembers gets a list of a project's team members. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/members.html#list-all-members-of-a-group-or-project +func (s *ProjectMembersService) ListProjectMembers(pid interface{}, opt *ListProjectMembersOptions, options ...OptionFunc) ([]*ProjectMember, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/members", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var pm []*ProjectMember + resp, err := s.client.Do(req, &pm) + if err != nil { + return nil, resp, err + } + + return pm, resp, err +} + +// GetProjectMember gets a project team member. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/members.html#get-a-member-of-a-group-or-project +func (s *ProjectMembersService) GetProjectMember(pid interface{}, user int, options ...OptionFunc) (*ProjectMember, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/members/%d", url.QueryEscape(project), user) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + pm := new(ProjectMember) + resp, err := s.client.Do(req, pm) + if err != nil { + return nil, resp, err + } + + return pm, resp, err +} + +// AddProjectMemberOptions represents the available AddProjectMember() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/members.html#add-a-member-to-a-group-or-project +type AddProjectMemberOptions struct { + UserID *int `url:"user_id,omitempty" json:"user_id,omitempty"` + AccessLevel *AccessLevelValue `url:"access_level,omitempty" json:"access_level,omitempty"` +} + +// AddProjectMember adds a user to a project team. This is an idempotent +// method and can be called multiple times with the same parameters. Adding +// team membership to a user that is already a member does not affect the +// existing membership. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/members.html#add-a-member-to-a-group-or-project +func (s *ProjectMembersService) AddProjectMember(pid interface{}, opt *AddProjectMemberOptions, options ...OptionFunc) (*ProjectMember, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/members", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + pm := new(ProjectMember) + resp, err := s.client.Do(req, pm) + if err != nil { + return nil, resp, err + } + + return pm, resp, err +} + +// EditProjectMemberOptions represents the available EditProjectMember() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/members.html#edit-a-member-of-a-group-or-project +type EditProjectMemberOptions struct { + AccessLevel *AccessLevelValue `url:"access_level,omitempty" json:"access_level,omitempty"` +} + +// EditProjectMember updates a project team member to a specified access level.. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/members.html#edit-a-member-of-a-group-or-project +func (s *ProjectMembersService) EditProjectMember(pid interface{}, user int, opt *EditProjectMemberOptions, options ...OptionFunc) (*ProjectMember, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/members/%d", url.QueryEscape(project), user) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + pm := new(ProjectMember) + resp, err := s.client.Do(req, pm) + if err != nil { + return nil, resp, err + } + + return pm, resp, err +} + +// DeleteProjectMember removes a user from a project team. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/members.html#remove-a-member-from-a-group-or-project +func (s *ProjectMembersService) DeleteProjectMember(pid interface{}, user int, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/members/%d", url.QueryEscape(project), user) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/xanzy/go-gitlab/project_snippets.go b/vendor/github.com/xanzy/go-gitlab/project_snippets.go new file mode 100644 index 00000000..00428196 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/project_snippets.go @@ -0,0 +1,207 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "bytes" + "fmt" + "net/url" +) + +// ProjectSnippetsService handles communication with the project snippets +// related methods of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/project_snippets.html +type ProjectSnippetsService struct { + client *Client +} + +// ListProjectSnippetsOptions represents the available ListSnippets() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/project_snippets.html#list-snippets +type ListProjectSnippetsOptions ListOptions + +// ListSnippets gets a list of project snippets. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/project_snippets.html#list-snippets +func (s *ProjectSnippetsService) ListSnippets(pid interface{}, opt *ListProjectSnippetsOptions, options ...OptionFunc) ([]*Snippet, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/snippets", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var ps []*Snippet + resp, err := s.client.Do(req, &ps) + if err != nil { + return nil, resp, err + } + + return ps, resp, err +} + +// GetSnippet gets a single project snippet +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/project_snippets.html#single-snippet +func (s *ProjectSnippetsService) GetSnippet(pid interface{}, snippet int, options ...OptionFunc) (*Snippet, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/snippets/%d", url.QueryEscape(project), snippet) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + ps := new(Snippet) + resp, err := s.client.Do(req, ps) + if err != nil { + return nil, resp, err + } + + return ps, resp, err +} + +// CreateProjectSnippetOptions represents the available CreateSnippet() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/project_snippets.html#create-new-snippet +type CreateProjectSnippetOptions struct { + Title *string `url:"title,omitempty" json:"title,omitempty"` + FileName *string `url:"file_name,omitempty" json:"file_name,omitempty"` + Description *string `url:"description,omitempty" json:"description,omitempty"` + Code *string `url:"code,omitempty" json:"code,omitempty"` + Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"` +} + +// CreateSnippet creates a new project snippet. The user must have permission +// to create new snippets. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/project_snippets.html#create-new-snippet +func (s *ProjectSnippetsService) CreateSnippet(pid interface{}, opt *CreateProjectSnippetOptions, options ...OptionFunc) (*Snippet, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/snippets", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + ps := new(Snippet) + resp, err := s.client.Do(req, ps) + if err != nil { + return nil, resp, err + } + + return ps, resp, err +} + +// UpdateProjectSnippetOptions represents the available UpdateSnippet() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/project_snippets.html#update-snippet +type UpdateProjectSnippetOptions struct { + Title *string `url:"title,omitempty" json:"title,omitempty"` + FileName *string `url:"file_name,omitempty" json:"file_name,omitempty"` + Description *string `url:"description,omitempty" json:"description,omitempty"` + Code *string `url:"code,omitempty" json:"code,omitempty"` + Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"` +} + +// UpdateSnippet updates an existing project snippet. The user must have +// permission to change an existing snippet. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/project_snippets.html#update-snippet +func (s *ProjectSnippetsService) UpdateSnippet(pid interface{}, snippet int, opt *UpdateProjectSnippetOptions, options ...OptionFunc) (*Snippet, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/snippets/%d", url.QueryEscape(project), snippet) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + ps := new(Snippet) + resp, err := s.client.Do(req, ps) + if err != nil { + return nil, resp, err + } + + return ps, resp, err +} + +// DeleteSnippet deletes an existing project snippet. This is an idempotent +// function and deleting a non-existent snippet still returns a 200 OK status +// code. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/project_snippets.html#delete-snippet +func (s *ProjectSnippetsService) DeleteSnippet(pid interface{}, snippet int, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/snippets/%d", url.QueryEscape(project), snippet) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// SnippetContent returns the raw project snippet as plain text. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/project_snippets.html#snippet-content +func (s *ProjectSnippetsService) SnippetContent(pid interface{}, snippet int, options ...OptionFunc) ([]byte, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/snippets/%d/raw", url.QueryEscape(project), snippet) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + var b bytes.Buffer + resp, err := s.client.Do(req, &b) + if err != nil { + return nil, resp, err + } + + return b.Bytes(), resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/projects.go b/vendor/github.com/xanzy/go-gitlab/projects.go new file mode 100644 index 00000000..4a252a99 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/projects.go @@ -0,0 +1,997 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/url" + "os" + "time" +) + +// ProjectsService handles communication with the repositories related methods +// of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html +type ProjectsService struct { + client *Client +} + +// Project represents a GitLab project. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html +type Project struct { + ID int `json:"id"` + Description string `json:"description"` + DefaultBranch string `json:"default_branch"` + Public bool `json:"public"` + Visibility VisibilityValue `json:"visibility"` + SSHURLToRepo string `json:"ssh_url_to_repo"` + HTTPURLToRepo string `json:"http_url_to_repo"` + WebURL string `json:"web_url"` + TagList []string `json:"tag_list"` + Owner *User `json:"owner"` + Name string `json:"name"` + NameWithNamespace string `json:"name_with_namespace"` + Path string `json:"path"` + PathWithNamespace string `json:"path_with_namespace"` + IssuesEnabled bool `json:"issues_enabled"` + OpenIssuesCount int `json:"open_issues_count"` + MergeRequestsEnabled bool `json:"merge_requests_enabled"` + ApprovalsBeforeMerge int `json:"approvals_before_merge"` + JobsEnabled bool `json:"jobs_enabled"` + WikiEnabled bool `json:"wiki_enabled"` + SnippetsEnabled bool `json:"snippets_enabled"` + ContainerRegistryEnabled bool `json:"container_registry_enabled"` + CreatedAt *time.Time `json:"created_at,omitempty"` + LastActivityAt *time.Time `json:"last_activity_at,omitempty"` + CreatorID int `json:"creator_id"` + Namespace *ProjectNamespace `json:"namespace"` + ImportStatus string `json:"import_status"` + ImportError string `json:"import_error"` + Permissions *Permissions `json:"permissions"` + Archived bool `json:"archived"` + AvatarURL string `json:"avatar_url"` + SharedRunnersEnabled bool `json:"shared_runners_enabled"` + ForksCount int `json:"forks_count"` + StarCount int `json:"star_count"` + RunnersToken string `json:"runners_token"` + PublicJobs bool `json:"public_jobs"` + OnlyAllowMergeIfPipelineSucceeds bool `json:"only_allow_merge_if_pipeline_succeeds"` + OnlyAllowMergeIfAllDiscussionsAreResolved bool `json:"only_allow_merge_if_all_discussions_are_resolved"` + LFSEnabled bool `json:"lfs_enabled"` + RequestAccessEnabled bool `json:"request_access_enabled"` + MergeMethod string `json:"merge_method"` + ForkedFromProject *ForkParent `json:"forked_from_project"` + SharedWithGroups []struct { + GroupID int `json:"group_id"` + GroupName string `json:"group_name"` + GroupAccessLevel int `json:"group_access_level"` + } `json:"shared_with_groups"` + Statistics *ProjectStatistics `json:"statistics"` + Links *Links `json:"_links,omitempty"` + CIConfigPath *string `json:"ci_config_path"` +} + +// Repository represents a repository. +type Repository struct { + Name string `json:"name"` + Description string `json:"description"` + WebURL string `json:"web_url"` + AvatarURL string `json:"avatar_url"` + GitSSHURL string `json:"git_ssh_url"` + GitHTTPURL string `json:"git_http_url"` + Namespace string `json:"namespace"` + Visibility VisibilityValue `json:"visibility"` + PathWithNamespace string `json:"path_with_namespace"` + DefaultBranch string `json:"default_branch"` + Homepage string `json:"homepage"` + URL string `json:"url"` + SSHURL string `json:"ssh_url"` + HTTPURL string `json:"http_url"` +} + +// ProjectNamespace represents a project namespace. +type ProjectNamespace struct { + ID int `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + Kind string `json:"kind"` + FullPath string `json:"full_path"` +} + +// StorageStatistics represents a statistics record for a group or project. +type StorageStatistics struct { + StorageSize int64 `json:"storage_size"` + RepositorySize int64 `json:"repository_size"` + LfsObjectsSize int64 `json:"lfs_objects_size"` + JobArtifactsSize int64 `json:"job_artifacts_size"` +} + +// ProjectStatistics represents a statistics record for a project. +type ProjectStatistics struct { + StorageStatistics + CommitCount int `json:"commit_count"` +} + +// Permissions represents permissions. +type Permissions struct { + ProjectAccess *ProjectAccess `json:"project_access"` + GroupAccess *GroupAccess `json:"group_access"` +} + +// ProjectAccess represents project access. +type ProjectAccess struct { + AccessLevel AccessLevelValue `json:"access_level"` + NotificationLevel NotificationLevelValue `json:"notification_level"` +} + +// GroupAccess represents group access. +type GroupAccess struct { + AccessLevel AccessLevelValue `json:"access_level"` + NotificationLevel NotificationLevelValue `json:"notification_level"` +} + +// ForkParent represents the parent project when this is a fork. +type ForkParent struct { + HTTPURLToRepo string `json:"http_url_to_repo"` + ID int `json:"id"` + Name string `json:"name"` + NameWithNamespace string `json:"name_with_namespace"` + Path string `json:"path"` + PathWithNamespace string `json:"path_with_namespace"` + WebURL string `json:"web_url"` +} + +// Links represents a project web links for self, issues, merge_requests, +// repo_branches, labels, events, members. +type Links struct { + Self string `json:"self"` + Issues string `json:"issues"` + MergeRequests string `json:"merge_requests"` + RepoBranches string `json:"repo_branches"` + Labels string `json:"labels"` + Events string `json:"events"` + Members string `json:"members"` +} + +func (s Project) String() string { + return Stringify(s) +} + +// ListProjectsOptions represents the available ListProjects() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#list-projects +type ListProjectsOptions struct { + ListOptions + Archived *bool `url:"archived,omitempty" json:"archived,omitempty"` + OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` + Sort *string `url:"sort,omitempty" json:"sort,omitempty"` + Search *string `url:"search,omitempty" json:"search,omitempty"` + Simple *bool `url:"simple,omitempty" json:"simple,omitempty"` + Owned *bool `url:"owned,omitempty" json:"owned,omitempty"` + Membership *bool `url:"membership,omitempty" json:"membership,omitempty"` + Starred *bool `url:"starred,omitempty" json:"starred,omitempty"` + Statistics *bool `url:"statistics,omitempty" json:"statistics,omitempty"` + Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"` + WithIssuesEnabled *bool `url:"with_issues_enabled,omitempty" json:"with_issues_enabled,omitempty"` + WithMergeRequestsEnabled *bool `url:"with_merge_requests_enabled,omitempty" json:"with_merge_requests_enabled,omitempty"` +} + +// ListProjects gets a list of projects accessible by the authenticated user. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#list-projects +func (s *ProjectsService) ListProjects(opt *ListProjectsOptions, options ...OptionFunc) ([]*Project, *Response, error) { + req, err := s.client.NewRequest("GET", "projects", opt, options) + if err != nil { + return nil, nil, err + } + + var p []*Project + resp, err := s.client.Do(req, &p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// ListUserProjects gets a list of projects for the given user. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#list-user-projects +func (s *ProjectsService) ListUserProjects(uid interface{}, opt *ListProjectsOptions, options ...OptionFunc) ([]*Project, *Response, error) { + user, err := parseID(uid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("users/%s/projects", user) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var p []*Project + resp, err := s.client.Do(req, &p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// ProjectUser represents a GitLab project user. +type ProjectUser struct { + ID int `json:"id"` + Name string `json:"name"` + Username string `json:"username"` + State string `json:"state"` + AvatarURL string `json:"avatar_url"` + WebURL string `json:"web_url"` +} + +// ListProjectUserOptions represents the available ListProjectsUsers() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#get-project-users +type ListProjectUserOptions struct { + ListOptions + Search *string `url:"search,omitempty" json:"search,omitempty"` +} + +// ListProjectsUsers gets a list of users for the given project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#get-project-users +func (s *ProjectsService) ListProjectsUsers(pid interface{}, opt *ListProjectUserOptions, options ...OptionFunc) ([]*ProjectUser, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/users", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var p []*ProjectUser + resp, err := s.client.Do(req, &p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// GetProject gets a specific project, identified by project ID or +// NAMESPACE/PROJECT_NAME, which is owned by the authenticated user. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#get-single-project +func (s *ProjectsService) GetProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + p := new(Project) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// ProjectEvent represents a GitLab project event. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#get-project-events +type ProjectEvent struct { + Title interface{} `json:"title"` + ProjectID int `json:"project_id"` + ActionName string `json:"action_name"` + TargetID interface{} `json:"target_id"` + TargetType interface{} `json:"target_type"` + AuthorID int `json:"author_id"` + AuthorUsername string `json:"author_username"` + Data struct { + Before string `json:"before"` + After string `json:"after"` + Ref string `json:"ref"` + UserID int `json:"user_id"` + UserName string `json:"user_name"` + Repository *Repository `json:"repository"` + Commits []*Commit `json:"commits"` + TotalCommitsCount int `json:"total_commits_count"` + } `json:"data"` + TargetTitle interface{} `json:"target_title"` +} + +func (s ProjectEvent) String() string { + return Stringify(s) +} + +// GetProjectEventsOptions represents the available GetProjectEvents() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#get-project-events +type GetProjectEventsOptions ListOptions + +// GetProjectEvents gets the events for the specified project. Sorted from +// newest to latest. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#get-project-events +func (s *ProjectsService) GetProjectEvents(pid interface{}, opt *GetProjectEventsOptions, options ...OptionFunc) ([]*ProjectEvent, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/events", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var p []*ProjectEvent + resp, err := s.client.Do(req, &p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// CreateProjectOptions represents the available CreateProjects() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#create-project +type CreateProjectOptions struct { + Name *string `url:"name,omitempty" json:"name,omitempty"` + Path *string `url:"path,omitempty" json:"path,omitempty"` + DefaultBranch *string `url:"default_branch,omitempty" json:"default_branch,omitempty"` + NamespaceID *int `url:"namespace_id,omitempty" json:"namespace_id,omitempty"` + Description *string `url:"description,omitempty" json:"description,omitempty"` + IssuesEnabled *bool `url:"issues_enabled,omitempty" json:"issues_enabled,omitempty"` + MergeRequestsEnabled *bool `url:"merge_requests_enabled,omitempty" json:"merge_requests_enabled,omitempty"` + JobsEnabled *bool `url:"jobs_enabled,omitempty" json:"jobs_enabled,omitempty"` + WikiEnabled *bool `url:"wiki_enabled,omitempty" json:"wiki_enabled,omitempty"` + SnippetsEnabled *bool `url:"snippets_enabled,omitempty" json:"snippets_enabled,omitempty"` + ResolveOutdatedDiffDiscussions *bool `url:"resolve_outdated_diff_discussions,omitempty" json:"resolve_outdated_diff_discussions,omitempty"` + ContainerRegistryEnabled *bool `url:"container_registry_enabled,omitempty" json:"container_registry_enabled,omitempty"` + SharedRunnersEnabled *bool `url:"shared_runners_enabled,omitempty" json:"shared_runners_enabled,omitempty"` + Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"` + ImportURL *string `url:"import_url,omitempty" json:"import_url,omitempty"` + PublicJobs *bool `url:"public_jobs,omitempty" json:"public_jobs,omitempty"` + OnlyAllowMergeIfPipelineSucceeds *bool `url:"only_allow_merge_if_pipeline_succeeds,omitempty" json:"only_allow_merge_if_pipeline_succeeds,omitempty"` + OnlyAllowMergeIfAllDiscussionsAreResolved *bool `url:"only_allow_merge_if_all_discussions_are_resolved,omitempty" json:"only_allow_merge_if_all_discussions_are_resolved,omitempty"` + MergeMethod *MergeMethodValue `url:"merge_method,omitempty" json:"merge_method,omitempty"` + LFSEnabled *bool `url:"lfs_enabled,omitempty" json:"lfs_enabled,omitempty"` + RequestAccessEnabled *bool `url:"request_access_enabled,omitempty" json:"request_access_enabled,omitempty"` + TagList *[]string `url:"tag_list,omitempty" json:"tag_list,omitempty"` + PrintingMergeRequestLinkEnabled *bool `url:"printing_merge_request_link_enabled,omitempty" json:"printing_merge_request_link_enabled,omitempty"` + CIConfigPath *string `url:"ci_config_path,omitempty" json:"ci_config_path,omitempty"` +} + +// CreateProject creates a new project owned by the authenticated user. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#create-project +func (s *ProjectsService) CreateProject(opt *CreateProjectOptions, options ...OptionFunc) (*Project, *Response, error) { + req, err := s.client.NewRequest("POST", "projects", opt, options) + if err != nil { + return nil, nil, err + } + + p := new(Project) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// CreateProjectForUserOptions represents the available CreateProjectForUser() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#create-project-for-user +type CreateProjectForUserOptions CreateProjectOptions + +// CreateProjectForUser creates a new project owned by the specified user. +// Available only for admins. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#create-project-for-user +func (s *ProjectsService) CreateProjectForUser(user int, opt *CreateProjectForUserOptions, options ...OptionFunc) (*Project, *Response, error) { + u := fmt.Sprintf("projects/user/%d", user) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + p := new(Project) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// EditProjectOptions represents the available EditProject() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#edit-project +type EditProjectOptions CreateProjectOptions + +// EditProject updates an existing project. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#edit-project +func (s *ProjectsService) EditProject(pid interface{}, opt *EditProjectOptions, options ...OptionFunc) (*Project, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s", url.QueryEscape(project)) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + p := new(Project) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// ForkProject forks a project into the user namespace of the authenticated +// user. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#fork-project +func (s *ProjectsService) ForkProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/fork", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + p := new(Project) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// StarProject stars a given the project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#star-a-project +func (s *ProjectsService) StarProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/star", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + p := new(Project) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// UnstarProject unstars a given project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#unstar-a-project +func (s *ProjectsService) UnstarProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/unstar", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + p := new(Project) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// ArchiveProject archives the project if the user is either admin or the +// project owner of this project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#archive-a-project +func (s *ProjectsService) ArchiveProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/archive", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + p := new(Project) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// UnarchiveProject unarchives the project if the user is either admin or +// the project owner of this project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#unarchive-a-project +func (s *ProjectsService) UnarchiveProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/unarchive", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + p := new(Project) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// DeleteProject removes a project including all associated resources +// (issues, merge requests etc.) +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#remove-project +func (s *ProjectsService) DeleteProject(pid interface{}, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s", url.QueryEscape(project)) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// ShareWithGroupOptions represents options to share project with groups +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#share-project-with-group +type ShareWithGroupOptions struct { + GroupID *int `url:"group_id" json:"group_id"` + GroupAccess *AccessLevelValue `url:"group_access" json:"group_access"` + ExpiresAt *string `url:"expires_at" json:"expires_at"` +} + +// ShareProjectWithGroup allows to share a project with a group. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#share-project-with-group +func (s *ProjectsService) ShareProjectWithGroup(pid interface{}, opt *ShareWithGroupOptions, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/share", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// DeleteSharedProjectFromGroup allows to unshare a project from a group. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#delete-a-shared-project-link-within-a-group +func (s *ProjectsService) DeleteSharedProjectFromGroup(pid interface{}, groupID int, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/share/%d", url.QueryEscape(project), groupID) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// ProjectMember represents a project member. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#list-project-team-members +type ProjectMember struct { + ID int `json:"id"` + Username string `json:"username"` + Email string `json:"email"` + Name string `json:"name"` + State string `json:"state"` + CreatedAt *time.Time `json:"created_at"` + AccessLevel AccessLevelValue `json:"access_level"` +} + +// ProjectHook represents a project hook. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#list-project-hooks +type ProjectHook struct { + ID int `json:"id"` + URL string `json:"url"` + ProjectID int `json:"project_id"` + PushEvents bool `json:"push_events"` + IssuesEvents bool `json:"issues_events"` + ConfidentialIssuesEvents bool `json:"confidential_issues_events"` + MergeRequestsEvents bool `json:"merge_requests_events"` + TagPushEvents bool `json:"tag_push_events"` + NoteEvents bool `json:"note_events"` + JobEvents bool `json:"job_events"` + PipelineEvents bool `json:"pipeline_events"` + WikiPageEvents bool `json:"wiki_page_events"` + EnableSSLVerification bool `json:"enable_ssl_verification"` + CreatedAt *time.Time `json:"created_at"` +} + +// ListProjectHooksOptions represents the available ListProjectHooks() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#list-project-hooks +type ListProjectHooksOptions ListOptions + +// ListProjectHooks gets a list of project hooks. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#list-project-hooks +func (s *ProjectsService) ListProjectHooks(pid interface{}, opt *ListProjectHooksOptions, options ...OptionFunc) ([]*ProjectHook, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/hooks", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var ph []*ProjectHook + resp, err := s.client.Do(req, &ph) + if err != nil { + return nil, resp, err + } + + return ph, resp, err +} + +// GetProjectHook gets a specific hook for a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#get-project-hook +func (s *ProjectsService) GetProjectHook(pid interface{}, hook int, options ...OptionFunc) (*ProjectHook, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/hooks/%d", url.QueryEscape(project), hook) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + ph := new(ProjectHook) + resp, err := s.client.Do(req, ph) + if err != nil { + return nil, resp, err + } + + return ph, resp, err +} + +// AddProjectHookOptions represents the available AddProjectHook() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#add-project-hook +type AddProjectHookOptions struct { + URL *string `url:"url,omitempty" json:"url,omitempty"` + PushEvents *bool `url:"push_events,omitempty" json:"push_events,omitempty"` + IssuesEvents *bool `url:"issues_events,omitempty" json:"issues_events,omitempty"` + ConfidentialIssuesEvents *bool `url:"confidential_issues_events,omitempty" json:"confidential_issues_events,omitempty"` + MergeRequestsEvents *bool `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"` + TagPushEvents *bool `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"` + NoteEvents *bool `url:"note_events,omitempty" json:"note_events,omitempty"` + JobEvents *bool `url:"job_events,omitempty" json:"job_events,omitempty"` + PipelineEvents *bool `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"` + WikiPageEvents *bool `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"` + EnableSSLVerification *bool `url:"enable_ssl_verification,omitempty" json:"enable_ssl_verification,omitempty"` + Token *string `url:"token,omitempty" json:"token,omitempty"` +} + +// AddProjectHook adds a hook to a specified project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#add-project-hook +func (s *ProjectsService) AddProjectHook(pid interface{}, opt *AddProjectHookOptions, options ...OptionFunc) (*ProjectHook, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/hooks", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + ph := new(ProjectHook) + resp, err := s.client.Do(req, ph) + if err != nil { + return nil, resp, err + } + + return ph, resp, err +} + +// EditProjectHookOptions represents the available EditProjectHook() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#edit-project-hook +type EditProjectHookOptions struct { + URL *string `url:"url,omitempty" json:"url,omitempty"` + PushEvents *bool `url:"push_events,omitempty" json:"push_events,omitempty"` + IssuesEvents *bool `url:"issues_events,omitempty" json:"issues_events,omitempty"` + ConfidentialIssuesEvents *bool `url:"confidential_issues_events,omitempty" json:"confidential_issues_events,omitempty"` + MergeRequestsEvents *bool `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"` + TagPushEvents *bool `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"` + NoteEvents *bool `url:"note_events,omitempty" json:"note_events,omitempty"` + JobEvents *bool `url:"job_events,omitempty" json:"job_events,omitempty"` + PipelineEvents *bool `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"` + WikiPageEvents *bool `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"` + EnableSSLVerification *bool `url:"enable_ssl_verification,omitempty" json:"enable_ssl_verification,omitempty"` + Token *string `url:"token,omitempty" json:"token,omitempty"` +} + +// EditProjectHook edits a hook for a specified project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#edit-project-hook +func (s *ProjectsService) EditProjectHook(pid interface{}, hook int, opt *EditProjectHookOptions, options ...OptionFunc) (*ProjectHook, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/hooks/%d", url.QueryEscape(project), hook) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + ph := new(ProjectHook) + resp, err := s.client.Do(req, ph) + if err != nil { + return nil, resp, err + } + + return ph, resp, err +} + +// DeleteProjectHook removes a hook from a project. This is an idempotent +// method and can be called multiple times. Either the hook is available or not. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#delete-project-hook +func (s *ProjectsService) DeleteProjectHook(pid interface{}, hook int, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/hooks/%d", url.QueryEscape(project), hook) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// ProjectForkRelation represents a project fork relationship. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#admin-fork-relation +type ProjectForkRelation struct { + ID int `json:"id"` + ForkedToProjectID int `json:"forked_to_project_id"` + ForkedFromProjectID int `json:"forked_from_project_id"` + CreatedAt *time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at"` +} + +// CreateProjectForkRelation creates a forked from/to relation between +// existing projects. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#create-a-forked-fromto-relation-between-existing-projects. +func (s *ProjectsService) CreateProjectForkRelation(pid int, fork int, options ...OptionFunc) (*ProjectForkRelation, *Response, error) { + u := fmt.Sprintf("projects/%d/fork/%d", pid, fork) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + pfr := new(ProjectForkRelation) + resp, err := s.client.Do(req, pfr) + if err != nil { + return nil, resp, err + } + + return pfr, resp, err +} + +// DeleteProjectForkRelation deletes an existing forked from relationship. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#delete-an-existing-forked-from-relationship +func (s *ProjectsService) DeleteProjectForkRelation(pid int, options ...OptionFunc) (*Response, error) { + u := fmt.Sprintf("projects/%d/fork", pid) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// ProjectFile represents an uploaded project file +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#upload-a-file +type ProjectFile struct { + Alt string `json:"alt"` + URL string `json:"url"` + Markdown string `json:"markdown"` +} + +// UploadFile upload a file from disk +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#upload-a-file +func (s *ProjectsService) UploadFile(pid interface{}, file string, options ...OptionFunc) (*ProjectFile, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/uploads", url.QueryEscape(project)) + + f, err := os.Open(file) + if err != nil { + return nil, nil, err + } + defer f.Close() + + b := &bytes.Buffer{} + w := multipart.NewWriter(b) + + fw, err := w.CreateFormFile("file", file) + if err != nil { + return nil, nil, err + } + + _, err = io.Copy(fw, f) + if err != nil { + return nil, nil, err + } + w.Close() + + req, err := s.client.NewRequest("", u, nil, options) + if err != nil { + return nil, nil, err + } + + req.Body = ioutil.NopCloser(b) + req.ContentLength = int64(b.Len()) + req.Header.Set("Content-Type", w.FormDataContentType()) + req.Method = "POST" + + uf := &ProjectFile{} + resp, err := s.client.Do(req, uf) + if err != nil { + return nil, resp, err + } + + return uf, resp, nil +} + +// ListProjectForks gets a list of project forks. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/projects.html#list-forks-of-a-project +func (s *ProjectsService) ListProjectForks(pid interface{}, opt *ListProjectsOptions, options ...OptionFunc) ([]*Project, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/forks", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var forks []*Project + resp, err := s.client.Do(req, &forks) + if err != nil { + return nil, resp, err + } + + return forks, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/protected_branches.go b/vendor/github.com/xanzy/go-gitlab/protected_branches.go new file mode 100644 index 00000000..0a56241e --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/protected_branches.go @@ -0,0 +1,165 @@ +// +// Copyright 2017, Sander van Harmelen, Michael Lihs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" +) + +// ProtectedBranchesService handles communication with the protected branch +// related methods of the GitLab API. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/protected_branches.html#protected-branches-api +type ProtectedBranchesService struct { + client *Client +} + +// BranchAccessDescription represents the access description for a protected +// branch. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/protected_branches.html#protected-branches-api +type BranchAccessDescription struct { + AccessLevel AccessLevelValue `json:"access_level"` + AccessLevelDescription string `json:"access_level_description"` +} + +// ProtectedBranch represents a protected branch. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/protected_branches.html#list-protected-branches +type ProtectedBranch struct { + Name string `json:"name"` + PushAccessLevels []*BranchAccessDescription `json:"push_access_levels"` + MergeAccessLevels []*BranchAccessDescription `json:"merge_access_levels"` +} + +// ListProtectedBranchesOptions represents the available ListProtectedBranches() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/protected_branches.html#list-protected-branches +type ListProtectedBranchesOptions ListOptions + +// ListProtectedBranches gets a list of protected branches from a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/protected_branches.html#list-protected-branches +func (s *ProtectedBranchesService) ListProtectedBranches(pid interface{}, opt *ListProtectedBranchesOptions, options ...OptionFunc) ([]*ProtectedBranch, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/protected_branches", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var p []*ProtectedBranch + resp, err := s.client.Do(req, &p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// GetProtectedBranch gets a single protected branch or wildcard protected branch. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/protected_branches.html#get-a-single-protected-branch-or-wildcard-protected-branch +func (s *ProtectedBranchesService) GetProtectedBranch(pid interface{}, branch string, options ...OptionFunc) (*ProtectedBranch, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/protected_branches/%s", url.QueryEscape(project), branch) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + p := new(ProtectedBranch) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// ProtectRepositoryBranchesOptions represents the available +// ProtectRepositoryBranches() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/protected_branches.html#protect-repository-branches +type ProtectRepositoryBranchesOptions struct { + Name *string `url:"name,omitempty" json:"name,omitempty"` + PushAccessLevel *AccessLevelValue `url:"push_access_level,omitempty" json:"push_access_level,omitempty"` + MergeAccessLevel *AccessLevelValue `url:"merge_access_level,omitempty" json:"merge_access_level,omitempty"` +} + +// ProtectRepositoryBranches protects a single repository branch or several +// project repository branches using a wildcard protected branch. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/protected_branches.html#protect-repository-branches +func (s *ProtectedBranchesService) ProtectRepositoryBranches(pid interface{}, opt *ProtectRepositoryBranchesOptions, options ...OptionFunc) (*ProtectedBranch, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/protected_branches", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + p := new(ProtectedBranch) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// UnprotectRepositoryBranches unprotects the given protected branch or wildcard +// protected branch. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/protected_branches.html#unprotect-repository-branches +func (s *ProtectedBranchesService) UnprotectRepositoryBranches(pid interface{}, branch string, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/protected_branches/%s", url.QueryEscape(project), branch) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/xanzy/go-gitlab/repositories.go b/vendor/github.com/xanzy/go-gitlab/repositories.go new file mode 100644 index 00000000..7d109974 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/repositories.go @@ -0,0 +1,260 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "bytes" + "fmt" + "net/url" +) + +// RepositoriesService handles communication with the repositories related +// methods of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/repositories.html +type RepositoriesService struct { + client *Client +} + +// TreeNode represents a GitLab repository file or directory. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/repositories.html +type TreeNode struct { + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Path string `json:"path"` + Mode string `json:"mode"` +} + +func (t TreeNode) String() string { + return Stringify(t) +} + +// ListTreeOptions represents the available ListTree() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/repositories.html#list-repository-tree +type ListTreeOptions struct { + ListOptions + Path *string `url:"path,omitempty" json:"path,omitempty"` + Ref *string `url:"ref,omitempty" json:"ref,omitempty"` + Recursive *bool `url:"recursive,omitempty" json:"recursive,omitempty"` +} + +// ListTree gets a list of repository files and directories in a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/repositories.html#list-repository-tree +func (s *RepositoriesService) ListTree(pid interface{}, opt *ListTreeOptions, options ...OptionFunc) ([]*TreeNode, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/tree", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var t []*TreeNode + resp, err := s.client.Do(req, &t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// RawFileContent gets the raw file contents for a file by commit SHA and path +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/repositories.html#raw-file-content +func (s *RepositoriesService) RawFileContent(pid interface{}, sha string, options ...OptionFunc) ([]byte, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/blobs/%s", url.QueryEscape(project), sha) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + var b bytes.Buffer + resp, err := s.client.Do(req, &b) + if err != nil { + return nil, resp, err + } + + return b.Bytes(), resp, err +} + +// RawBlobContent gets the raw file contents for a blob by blob SHA. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/repositories.html#raw-blob-content +func (s *RepositoriesService) RawBlobContent(pid interface{}, sha string, options ...OptionFunc) ([]byte, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/blobs/%s/raw", url.QueryEscape(project), sha) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + var b bytes.Buffer + resp, err := s.client.Do(req, &b) + if err != nil { + return nil, resp, err + } + + return b.Bytes(), resp, err +} + +// ArchiveOptions represents the available Archive() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/repositories.html#get-file-archive +type ArchiveOptions struct { + SHA *string `url:"sha,omitempty" json:"sha,omitempty"` +} + +// Archive gets an archive of the repository. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/repositories.html#get-file-archive +func (s *RepositoriesService) Archive(pid interface{}, opt *ArchiveOptions, options ...OptionFunc) ([]byte, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/archive", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var b bytes.Buffer + resp, err := s.client.Do(req, &b) + if err != nil { + return nil, resp, err + } + + return b.Bytes(), resp, err +} + +// Compare represents the result of a comparison of branches, tags or commits. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/repositories.html#compare-branches-tags-or-commits +type Compare struct { + Commit *Commit `json:"commit"` + Commits []*Commit `json:"commits"` + Diffs []*Diff `json:"diffs"` + CompareTimeout bool `json:"compare_timeout"` + CompareSameRef bool `json:"compare_same_ref"` +} + +func (c Compare) String() string { + return Stringify(c) +} + +// CompareOptions represents the available Compare() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/repositories.html#compare-branches-tags-or-commits +type CompareOptions struct { + From *string `url:"from,omitempty" json:"from,omitempty"` + To *string `url:"to,omitempty" json:"to,omitempty"` +} + +// Compare compares branches, tags or commits. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/repositories.html#compare-branches-tags-or-commits +func (s *RepositoriesService) Compare(pid interface{}, opt *CompareOptions, options ...OptionFunc) (*Compare, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/compare", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + c := new(Compare) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// Contributor represents a GitLap contributor. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/repositories.html#contributors +type Contributor struct { + Name string `json:"name,omitempty"` + Email string `json:"email,omitempty"` + Commits int `json:"commits,omitempty"` + Additions int `json:"additions,omitempty"` + Deletions int `json:"deletions,omitempty"` +} + +func (c Contributor) String() string { + return Stringify(c) +} + +// ListContributorsOptions represents the available ListContributorsOptions() +// options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/repositories.html#contributors +type ListContributorsOptions ListOptions + +// Contributors gets the repository contributors list. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/repositories.html#contributors +func (s *RepositoriesService) Contributors(pid interface{}, opt *ListContributorsOptions, options ...OptionFunc) ([]*Contributor, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/contributors", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var c []*Contributor + resp, err := s.client.Do(req, &c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/repository_files.go b/vendor/github.com/xanzy/go-gitlab/repository_files.go new file mode 100644 index 00000000..473128a3 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/repository_files.go @@ -0,0 +1,235 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "bytes" + "fmt" + "net/url" +) + +// RepositoryFilesService handles communication with the repository files +// related methods of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html +type RepositoryFilesService struct { + client *Client +} + +// File represents a GitLab repository file. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html +type File struct { + FileName string `json:"file_name"` + FilePath string `json:"file_path"` + Size int `json:"size"` + Encoding string `json:"encoding"` + Content string `json:"content"` + Ref string `json:"ref"` + BlobID string `json:"blob_id"` + CommitID string `json:"commit_id"` +} + +func (r File) String() string { + return Stringify(r) +} + +// GetFileOptions represents the available GetFile() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository +type GetFileOptions struct { + Ref *string `url:"ref,omitempty" json:"ref,omitempty"` +} + +// GetFile allows you to receive information about a file in repository like +// name, size, content. Note that file content is Base64 encoded. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository +func (s *RepositoryFilesService) GetFile(pid interface{}, fileName string, opt *GetFileOptions, options ...OptionFunc) (*File, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/files/%s", url.QueryEscape(project), url.QueryEscape(fileName)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + f := new(File) + resp, err := s.client.Do(req, f) + if err != nil { + return nil, resp, err + } + + return f, resp, err +} + +// GetRawFileOptions represents the available GetRawFile() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/repository_files.html#get-raw-file-from-repository +type GetRawFileOptions struct { + Ref *string `url:"ref,omitempty" json:"ref,omitempty"` +} + +// GetRawFile allows you to receive the raw file in repository. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/repository_files.html#get-raw-file-from-repository +func (s *RepositoryFilesService) GetRawFile(pid interface{}, fileName string, opt *GetRawFileOptions, options ...OptionFunc) ([]byte, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/files/%s/raw", url.QueryEscape(project), url.QueryEscape(fileName)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var f bytes.Buffer + resp, err := s.client.Do(req, &f) + if err != nil { + return nil, resp, err + } + + return f.Bytes(), resp, err +} + +// FileInfo represents file details of a GitLab repository file. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html +type FileInfo struct { + FilePath string `json:"file_path"` + Branch string `json:"branch"` +} + +func (r FileInfo) String() string { + return Stringify(r) +} + +// CreateFileOptions represents the available CreateFile() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/repository_files.html#create-new-file-in-repository +type CreateFileOptions struct { + Branch *string `url:"branch,omitempty" json:"branch,omitempty"` + Encoding *string `url:"encoding,omitempty" json:"encoding,omitempty"` + AuthorEmail *string `url:"author_email,omitempty" json:"author_email,omitempty"` + AuthorName *string `url:"author_name,omitempty" json:"author_name,omitempty"` + Content *string `url:"content,omitempty" json:"content,omitempty"` + CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"` +} + +// CreateFile creates a new file in a repository. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/repository_files.html#create-new-file-in-repository +func (s *RepositoryFilesService) CreateFile(pid interface{}, fileName string, opt *CreateFileOptions, options ...OptionFunc) (*FileInfo, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/files/%s", url.QueryEscape(project), url.QueryEscape(fileName)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + f := new(FileInfo) + resp, err := s.client.Do(req, f) + if err != nil { + return nil, resp, err + } + + return f, resp, err +} + +// UpdateFileOptions represents the available UpdateFile() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/repository_files.html#update-existing-file-in-repository +type UpdateFileOptions struct { + Branch *string `url:"branch,omitempty" json:"branch,omitempty"` + Encoding *string `url:"encoding,omitempty" json:"encoding,omitempty"` + AuthorEmail *string `url:"author_email,omitempty" json:"author_email,omitempty"` + AuthorName *string `url:"author_name,omitempty" json:"author_name,omitempty"` + Content *string `url:"content,omitempty" json:"content,omitempty"` + CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"` + LastCommitID *string `url:"last_commit_id,omitempty" json:"last_commit_id,omitempty"` +} + +// UpdateFile updates an existing file in a repository +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/repository_files.html#update-existing-file-in-repository +func (s *RepositoryFilesService) UpdateFile(pid interface{}, fileName string, opt *UpdateFileOptions, options ...OptionFunc) (*FileInfo, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/files/%s", url.QueryEscape(project), url.QueryEscape(fileName)) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + f := new(FileInfo) + resp, err := s.client.Do(req, f) + if err != nil { + return nil, resp, err + } + + return f, resp, err +} + +// DeleteFileOptions represents the available DeleteFile() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/repository_files.html#delete-existing-file-in-repository +type DeleteFileOptions struct { + Branch *string `url:"branch,omitempty" json:"branch,omitempty"` + AuthorEmail *string `url:"author_email,omitempty" json:"author_email,omitempty"` + AuthorName *string `url:"author_name,omitempty" json:"author_name,omitempty"` + CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"` +} + +// DeleteFile deletes an existing file in a repository +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/repository_files.html#delete-existing-file-in-repository +func (s *RepositoryFilesService) DeleteFile(pid interface{}, fileName string, opt *DeleteFileOptions, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/repository/files/%s", url.QueryEscape(project), url.QueryEscape(fileName)) + + req, err := s.client.NewRequest("DELETE", u, opt, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/xanzy/go-gitlab/runners.go b/vendor/github.com/xanzy/go-gitlab/runners.go new file mode 100644 index 00000000..4ffef038 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/runners.go @@ -0,0 +1,326 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" + "time" +) + +// RunnersService handles communication with the runner related methods of the +// GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/runners.html +type RunnersService struct { + client *Client +} + +// Runner represents a GitLab CI Runner. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/runners.html +type Runner struct { + ID int `json:"id"` + Description string `json:"description"` + Active bool `json:"active"` + IsShared bool `json:"is_shared"` + Name string `json:"name"` + Online bool `json:"online"` + Status string `json:"status"` +} + +// RunnerDetails represents the GitLab CI runner details. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/runners.html +type RunnerDetails struct { + Active bool `json:"active"` + Architecture string `json:"architecture"` + Description string `json:"description"` + ID int `json:"id"` + IsShared bool `json:"is_shared"` + ContactedAt *time.Time `json:"contacted_at,omitempty"` + Name string `json:"name"` + Online bool `json:"online"` + Status string `json:"status"` + Platform string `json:"platform,omitempty"` + Projects []struct { + ID int `json:"id"` + Name string `json:"name"` + NameWithNamespace string `json:"name_with_namespace"` + Path string `json:"path"` + PathWithNamespace string `json:"path_with_namespace"` + } `json:"projects"` + Token string `json:"Token"` + Revision string `json:"revision,omitempty"` + TagList []string `json:"tag_list"` + Version string `json:"version,omitempty"` + AccessLevel string `json:"access_level"` +} + +// ListRunnersOptions represents the available ListRunners() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/runners.html#list-owned-runners +type ListRunnersOptions struct { + ListOptions + Scope *string `url:"scope,omitempty" json:"scope,omitempty"` +} + +// ListRunners gets a list of runners accessible by the authenticated user. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/runners.html#list-owned-runners +func (s *RunnersService) ListRunners(opt *ListRunnersOptions, options ...OptionFunc) ([]*Runner, *Response, error) { + req, err := s.client.NewRequest("GET", "runners", opt, options) + if err != nil { + return nil, nil, err + } + + var rs []*Runner + resp, err := s.client.Do(req, &rs) + if err != nil { + return nil, resp, err + } + + return rs, resp, err +} + +// ListAllRunners gets a list of all runners in the GitLab instance. Access is +// restricted to users with admin privileges. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/runners.html#list-all-runners +func (s *RunnersService) ListAllRunners(opt *ListRunnersOptions, options ...OptionFunc) ([]*Runner, *Response, error) { + req, err := s.client.NewRequest("GET", "runners/all", opt, options) + if err != nil { + return nil, nil, err + } + + var rs []*Runner + resp, err := s.client.Do(req, &rs) + if err != nil { + return nil, resp, err + } + + return rs, resp, err +} + +// GetRunnerDetails returns details for given runner. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/runners.html#get-runner-39-s-details +func (s *RunnersService) GetRunnerDetails(rid interface{}, options ...OptionFunc) (*RunnerDetails, *Response, error) { + runner, err := parseID(rid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("runners/%s", runner) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + var rs *RunnerDetails + resp, err := s.client.Do(req, &rs) + if err != nil { + return nil, resp, err + } + + return rs, resp, err +} + +// UpdateRunnerDetailsOptions represents the available UpdateRunnerDetails() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/runners.html#update-runner-39-s-details +type UpdateRunnerDetailsOptions struct { + Description *string `url:"description,omitempty" json:"description,omitempty"` + Active *bool `url:"active,omitempty" json:"active,omitempty"` + TagList []string `url:"tag_list[],omitempty" json:"tag_list,omitempty"` + RunUntagged *bool `url:"run_untagged,omitempty" json:"run_untagged,omitempty"` + Locked *bool `url:"locked,omitempty" json:"locked,omitempty"` + AccessLevel *string `url:"access_level,omitempty" json:"access_level,omitempty"` +} + +// UpdateRunnerDetails updates details for a given runner. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/runners.html#update-runner-39-s-details +func (s *RunnersService) UpdateRunnerDetails(rid interface{}, opt *UpdateRunnerDetailsOptions, options ...OptionFunc) (*RunnerDetails, *Response, error) { + runner, err := parseID(rid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("runners/%s", runner) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + var rs *RunnerDetails + resp, err := s.client.Do(req, &rs) + if err != nil { + return nil, resp, err + } + + return rs, resp, err +} + +// RemoveRunner removes a runner. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/runners.html#remove-a-runner +func (s *RunnersService) RemoveRunner(rid interface{}, options ...OptionFunc) (*Response, error) { + runner, err := parseID(rid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("runners/%s", runner) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// ListRunnerJobsOptions represents the available ListRunnerJobs() +// options. Status can be one of: running, success, failed, canceled. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/runners.html#list-runner-39-s-jobs +type ListRunnerJobsOptions struct { + ListOptions + Status *string `url:"status,omitempty" json:"status,omitempty"` +} + +// ListRunnerJobs gets a list of jobs that are being processed or were processed by specified Runner. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/runners.html#list-runner-39-s-jobs +func (s *RunnersService) ListRunnerJobs(rid interface{}, opt *ListRunnerJobsOptions, options ...OptionFunc) ([]*Job, *Response, error) { + runner, err := parseID(rid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("runners/%s/jobs", runner) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var rs []*Job + resp, err := s.client.Do(req, &rs) + if err != nil { + return nil, resp, err + } + + return rs, resp, err +} + +// ListProjectRunnersOptions represents the available ListProjectRunners() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/runners.html#list-project-s-runners +type ListProjectRunnersOptions ListRunnersOptions + +// ListProjectRunners gets a list of runners accessible by the authenticated user. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/runners.html#list-project-s-runners +func (s *RunnersService) ListProjectRunners(pid interface{}, opt *ListProjectRunnersOptions, options ...OptionFunc) ([]*Runner, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/runners", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var rs []*Runner + resp, err := s.client.Do(req, &rs) + if err != nil { + return nil, resp, err + } + + return rs, resp, err +} + +// EnableProjectRunnerOptions represents the available EnableProjectRunner() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/runners.html#enable-a-runner-in-project +type EnableProjectRunnerOptions struct { + RunnerID int `json:"runner_id"` +} + +// EnableProjectRunner enables an available specific runner in the project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/runners.html#enable-a-runner-in-project +func (s *RunnersService) EnableProjectRunner(pid interface{}, opt *EnableProjectRunnerOptions, options ...OptionFunc) (*Runner, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/runners", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + var r *Runner + resp, err := s.client.Do(req, &r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} + +// DisableProjectRunner disables a specific runner from project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/runners.html#disable-a-runner-from-project +func (s *RunnersService) DisableProjectRunner(pid interface{}, rid interface{}, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + runner, err := parseID(rid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/runners/%s", url.QueryEscape(project), url.QueryEscape(runner)) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/xanzy/go-gitlab/search.go b/vendor/github.com/xanzy/go-gitlab/search.go new file mode 100644 index 00000000..99c2f020 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/search.go @@ -0,0 +1,326 @@ +// +// Copyright 2018, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" +) + +// SearchService handles communication with the search related methods of the +// GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/search.html +type SearchService struct { + client *Client +} + +// SearchOptions represents the available options for all search methods. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/search.html +type SearchOptions ListOptions + +type searchOptions struct { + SearchOptions + Scope string `url:"scope" json:"scope"` + Search string `url:"search" json:"search"` +} + +// Projects searches the expression within projects +// +// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-projects +func (s *SearchService) Projects(query string, opt *SearchOptions, options ...OptionFunc) ([]*Project, *Response, error) { + var ps []*Project + resp, err := s.search("projects", query, &ps, opt, options...) + return ps, resp, err +} + +// ProjectsByGroup searches the expression within projects for +// the specified group +// +// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#group-search-api +func (s *SearchService) ProjectsByGroup(gid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Project, *Response, error) { + var ps []*Project + resp, err := s.searchByGroup(gid, "projects", query, &ps, opt, options...) + return ps, resp, err +} + +// Issues searches the expression within issues +// +// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-issues +func (s *SearchService) Issues(query string, opt *SearchOptions, options ...OptionFunc) ([]*Issue, *Response, error) { + var is []*Issue + resp, err := s.search("issues", query, &is, opt, options...) + return is, resp, err +} + +// IssuesByGroup searches the expression within issues for +// the specified group +// +// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-issues +func (s *SearchService) IssuesByGroup(gid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Issue, *Response, error) { + var is []*Issue + resp, err := s.searchByGroup(gid, "issues", query, &is, opt, options...) + return is, resp, err +} + +// IssuesByProject searches the expression within issues for +// the specified project +// +// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-issues +func (s *SearchService) IssuesByProject(pid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Issue, *Response, error) { + var is []*Issue + resp, err := s.searchByProject(pid, "issues", query, &is, opt, options...) + return is, resp, err +} + +// MergeRequests searches the expression within merge requests +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/search.html#scope-merge_requests +func (s *SearchService) MergeRequests(query string, opt *SearchOptions, options ...OptionFunc) ([]*MergeRequest, *Response, error) { + var ms []*MergeRequest + resp, err := s.search("merge_requests", query, &ms, opt, options...) + return ms, resp, err +} + +// MergeRequestsByGroup searches the expression within merge requests for +// the specified group +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/search.html#scope-merge_requests +func (s *SearchService) MergeRequestsByGroup(gid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*MergeRequest, *Response, error) { + var ms []*MergeRequest + resp, err := s.searchByGroup(gid, "merge_requests", query, &ms, opt, options...) + return ms, resp, err +} + +// MergeRequestsByProject searches the expression within merge requests for +// the specified project +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/search.html#scope-merge_requests +func (s *SearchService) MergeRequestsByProject(pid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*MergeRequest, *Response, error) { + var ms []*MergeRequest + resp, err := s.searchByProject(pid, "merge_requests", query, &ms, opt, options...) + return ms, resp, err +} + +// Milestones searches the expression within milestones +// +// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-milestones +func (s *SearchService) Milestones(query string, opt *SearchOptions, options ...OptionFunc) ([]*Milestone, *Response, error) { + var ms []*Milestone + resp, err := s.search("milestones", query, &ms, opt, options...) + return ms, resp, err +} + +// MilestonesByGroup searches the expression within milestones for +// the specified group +// +// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-milestones +func (s *SearchService) MilestonesByGroup(gid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Milestone, *Response, error) { + var ms []*Milestone + resp, err := s.searchByGroup(gid, "milestones", query, &ms, opt, options...) + return ms, resp, err +} + +// MilestonesByProject searches the expression within milestones for +// the specified project +// +// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-milestones +func (s *SearchService) MilestonesByProject(pid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Milestone, *Response, error) { + var ms []*Milestone + resp, err := s.searchByProject(pid, "milestones", query, &ms, opt, options...) + return ms, resp, err +} + +// SnippetTitles searches the expression within snippet titles +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/search.html#scope-snippet_titles +func (s *SearchService) SnippetTitles(query string, opt *SearchOptions, options ...OptionFunc) ([]*Snippet, *Response, error) { + var ss []*Snippet + resp, err := s.search("snippet_titles", query, &ss, opt, options...) + return ss, resp, err +} + +// SnippetBlobs searches the expression within snippet blobs +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/search.html#scope-snippet_blobs +func (s *SearchService) SnippetBlobs(query string, opt *SearchOptions, options ...OptionFunc) ([]*Snippet, *Response, error) { + var ss []*Snippet + resp, err := s.search("snippet_blobs", query, &ss, opt, options...) + return ss, resp, err +} + +// NotesByProject searches the expression within notes for the specified +// project +// +// GitLab API docs: // https://docs.gitlab.com/ce/api/search.html#scope-notes +func (s *SearchService) NotesByProject(pid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Note, *Response, error) { + var ns []*Note + resp, err := s.searchByProject(pid, "notes", query, &ns, opt, options...) + return ns, resp, err +} + +// WikiBlobs searches the expression within all wiki blobs +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/search.html#scope-wiki_blobs +func (s *SearchService) WikiBlobs(query string, opt *SearchOptions, options ...OptionFunc) ([]*Wiki, *Response, error) { + var ws []*Wiki + resp, err := s.search("wiki_blobs", query, &ws, opt, options...) + return ws, resp, err +} + +// WikiBlobsByGroup searches the expression within wiki blobs for +// specified group +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/search.html#scope-wiki_blobs +func (s *SearchService) WikiBlobsByGroup(gid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Wiki, *Response, error) { + var ws []*Wiki + resp, err := s.searchByGroup(gid, "wiki_blobs", query, &ws, opt, options...) + return ws, resp, err +} + +// WikiBlobsByProject searches the expression within wiki blobs for +// the specified project +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/search.html#scope-wiki_blobs +func (s *SearchService) WikiBlobsByProject(pid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Wiki, *Response, error) { + var ws []*Wiki + resp, err := s.searchByProject(pid, "wiki_blobs", query, &ws, opt, options...) + return ws, resp, err +} + +// Commits searches the expression within all commits +// +// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-commits +func (s *SearchService) Commits(query string, opt *SearchOptions, options ...OptionFunc) ([]*Commit, *Response, error) { + var cs []*Commit + resp, err := s.search("commits", query, &cs, opt, options...) + return cs, resp, err +} + +// CommitsByGroup searches the expression within commits for the specified +// group +// +// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-commits +func (s *SearchService) CommitsByGroup(gid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Commit, *Response, error) { + var cs []*Commit + resp, err := s.searchByGroup(gid, "commits", query, &cs, opt, options...) + return cs, resp, err +} + +// CommitsByProject searches the expression within commits for the +// specified project +// +// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-commits +func (s *SearchService) CommitsByProject(pid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Commit, *Response, error) { + var cs []*Commit + resp, err := s.searchByProject(pid, "commits", query, &cs, opt, options...) + return cs, resp, err +} + +// Blob represents a single blob. +type Blob struct { + Basename string `json:"basename"` + Data string `json:"data"` + Filename string `json:"filename"` + ID int `json:"id"` + Ref string `json:"ref"` + Startline int `json:"startline"` + ProjectID int `json:"project_id"` +} + +// Blobs searches the expression within all blobs +// +// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-blobs +func (s *SearchService) Blobs(query string, opt *SearchOptions, options ...OptionFunc) ([]*Blob, *Response, error) { + var bs []*Blob + resp, err := s.search("blobs", query, &bs, opt, options...) + return bs, resp, err +} + +// BlobsByGroup searches the expression within blobs for the specified +// group +// +// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-blobs +func (s *SearchService) BlobsByGroup(gid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Blob, *Response, error) { + var bs []*Blob + resp, err := s.searchByGroup(gid, "blobs", query, &bs, opt, options...) + return bs, resp, err +} + +// BlobsByProject searches the expression within blobs for the specified +// project +// +// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-blobs +func (s *SearchService) BlobsByProject(pid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Blob, *Response, error) { + var bs []*Blob + resp, err := s.searchByProject(pid, "blobs", query, &bs, opt, options...) + return bs, resp, err +} + +func (s *SearchService) search(scope, query string, result interface{}, opt *SearchOptions, options ...OptionFunc) (*Response, error) { + opts := &searchOptions{SearchOptions: *opt, Scope: scope, Search: query} + + req, err := s.client.NewRequest("GET", "search", opts, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, result) +} + +func (s *SearchService) searchByGroup(gid interface{}, scope, query string, result interface{}, opt *SearchOptions, options ...OptionFunc) (*Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("groups/%s/-/search", url.QueryEscape(group)) + + opts := &searchOptions{SearchOptions: *opt, Scope: scope, Search: query} + + req, err := s.client.NewRequest("GET", u, opts, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, result) +} + +func (s *SearchService) searchByProject(pid interface{}, scope, query string, result interface{}, opt *SearchOptions, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/-/search", url.QueryEscape(project)) + + opts := &searchOptions{SearchOptions: *opt, Scope: scope, Search: query} + + req, err := s.client.NewRequest("GET", u, opts, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, result) +} diff --git a/vendor/github.com/xanzy/go-gitlab/services.go b/vendor/github.com/xanzy/go-gitlab/services.go new file mode 100644 index 00000000..e93ae600 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/services.go @@ -0,0 +1,515 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" + "time" +) + +// ServicesService handles communication with the services related methods of +// the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/services.html +type ServicesService struct { + client *Client +} + +// Service represents a GitLab service. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/services.html +type Service struct { + ID int `json:"id"` + Title string `json:"title"` + CreatedAt *time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at"` + Active bool `json:"active"` + PushEvents bool `json:"push_events"` + IssuesEvents bool `json:"issues_events"` + ConfidentialIssuesEvents bool `json:"confidential_issues_events"` + MergeRequestsEvents bool `json:"merge_requests_events"` + TagPushEvents bool `json:"tag_push_events"` + NoteEvents bool `json:"note_events"` + PipelineEvents bool `json:"pipeline_events"` + JobEvents bool `json:"job_events"` +} + +// SetGitLabCIServiceOptions represents the available SetGitLabCIService() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#edit-gitlab-ci-service +type SetGitLabCIServiceOptions struct { + Token *string `url:"token,omitempty" json:"token,omitempty"` + ProjectURL *string `url:"project_url,omitempty" json:"project_url,omitempty"` +} + +// SetGitLabCIService sets GitLab CI service for a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#edit-gitlab-ci-service +func (s *ServicesService) SetGitLabCIService(pid interface{}, opt *SetGitLabCIServiceOptions, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/services/gitlab-ci", url.QueryEscape(project)) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// DeleteGitLabCIService deletes GitLab CI service settings for a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#delete-gitlab-ci-service +func (s *ServicesService) DeleteGitLabCIService(pid interface{}, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/services/gitlab-ci", url.QueryEscape(project)) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// SetHipChatServiceOptions represents the available SetHipChatService() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#edit-hipchat-service +type SetHipChatServiceOptions struct { + Token *string `url:"token,omitempty" json:"token,omitempty" ` + Room *string `url:"room,omitempty" json:"room,omitempty"` +} + +// SetHipChatService sets HipChat service for a project +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#edit-hipchat-service +func (s *ServicesService) SetHipChatService(pid interface{}, opt *SetHipChatServiceOptions, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/services/hipchat", url.QueryEscape(project)) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// DeleteHipChatService deletes HipChat service for project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#delete-hipchat-service +func (s *ServicesService) DeleteHipChatService(pid interface{}, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/services/hipchat", url.QueryEscape(project)) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// DroneCIService represents Drone CI service settings. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#drone-ci +type DroneCIService struct { + Service + Properties *DroneCIServiceProperties `json:"properties"` +} + +// DroneCIServiceProperties represents Drone CI specific properties. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#drone-ci +type DroneCIServiceProperties struct { + Token string `json:"token"` + DroneURL string `json:"drone_url"` + EnableSSLVerification bool `json:"enable_ssl_verification"` +} + +// GetDroneCIService gets Drone CI service settings for a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#get-drone-ci-service-settings +func (s *ServicesService) GetDroneCIService(pid interface{}, options ...OptionFunc) (*DroneCIService, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/services/drone-ci", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + svc := new(DroneCIService) + resp, err := s.client.Do(req, svc) + if err != nil { + return nil, resp, err + } + + return svc, resp, err +} + +// SetDroneCIServiceOptions represents the available SetDroneCIService() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#createedit-drone-ci-service +type SetDroneCIServiceOptions struct { + Token *string `url:"token" json:"token" ` + DroneURL *string `url:"drone_url" json:"drone_url"` + EnableSSLVerification *bool `url:"enable_ssl_verification,omitempty" json:"enable_ssl_verification,omitempty"` +} + +// SetDroneCIService sets Drone CI service for a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#createedit-drone-ci-service +func (s *ServicesService) SetDroneCIService(pid interface{}, opt *SetDroneCIServiceOptions, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/services/drone-ci", url.QueryEscape(project)) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// DeleteDroneCIService deletes Drone CI service settings for a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#delete-drone-ci-service +func (s *ServicesService) DeleteDroneCIService(pid interface{}, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/services/drone-ci", url.QueryEscape(project)) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// SlackService represents Slack service settings. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#slack +type SlackService struct { + Service + Properties *SlackServiceProperties `json:"properties"` +} + +// SlackServiceProperties represents Slack specific properties. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#slack +type SlackServiceProperties struct { + NotifyOnlyBrokenPipelines bool `json:"notify_only_broken_pipelines"` +} + +// GetSlackService gets Slack service settings for a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#get-slack-service-settings +func (s *ServicesService) GetSlackService(pid interface{}, options ...OptionFunc) (*SlackService, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/services/slack", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + svc := new(SlackService) + resp, err := s.client.Do(req, svc) + if err != nil { + return nil, resp, err + } + + return svc, resp, err +} + +// SetSlackServiceOptions represents the available SetSlackService() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#edit-slack-service +type SetSlackServiceOptions struct { + WebHook *string `url:"webhook,omitempty" json:"webhook,omitempty" ` + Username *string `url:"username,omitempty" json:"username,omitempty" ` + Channel *string `url:"channel,omitempty" json:"channel,omitempty"` +} + +// SetSlackService sets Slack service for a project +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#edit-slack-service +func (s *ServicesService) SetSlackService(pid interface{}, opt *SetSlackServiceOptions, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/services/slack", url.QueryEscape(project)) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// DeleteSlackService deletes Slack service for project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#delete-slack-service +func (s *ServicesService) DeleteSlackService(pid interface{}, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/services/slack", url.QueryEscape(project)) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// JiraService represents Jira service settings. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#jira +type JiraService struct { + Service + Properties *JiraServiceProperties `json:"properties"` +} + +// JiraServiceProperties represents Jira specific properties. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#jira +type JiraServiceProperties struct { + URL *string `url:"url,omitempty" json:"url,omitempty"` + ProjectKey *string `url:"project_key,omitempty" json:"project_key,omitempty" ` + Username *string `url:"username,omitempty" json:"username,omitempty" ` + Password *string `url:"password,omitempty" json:"password,omitempty" ` + JiraIssueTransitionID *string `url:"jira_issue_transition_id,omitempty" json:"jira_issue_transition_id,omitempty"` +} + +// GetJiraService gets Jira service settings for a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#get-jira-service-settings +func (s *ServicesService) GetJiraService(pid interface{}, options ...OptionFunc) (*JiraService, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/services/jira", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + svc := new(JiraService) + resp, err := s.client.Do(req, svc) + if err != nil { + return nil, resp, err + } + + return svc, resp, err +} + +// SetJiraServiceOptions represents the available SetJiraService() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#edit-jira-service +type SetJiraServiceOptions JiraServiceProperties + +// SetJiraService sets Jira service for a project +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#edit-jira-service +func (s *ServicesService) SetJiraService(pid interface{}, opt *SetJiraServiceOptions, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/services/jira", url.QueryEscape(project)) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// DeleteJiraService deletes Jira service for project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#delete-jira-service +func (s *ServicesService) DeleteJiraService(pid interface{}, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/services/jira", url.QueryEscape(project)) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// JenkinsCIService represents Jenkins CI service settings. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/services.html#jenkins-ci +type JenkinsCIService struct { + Service + Properties *JenkinsCIServiceProperties `json:"properties"` +} + +// JenkinsCIServiceProperties represents Jenkins CI specific properties. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/services.html#jenkins-ci +type JenkinsCIServiceProperties struct { + URL *string `url:"jenkins_url,omitempty" json:"jenkins_url,omitempty"` + ProjectName *string `url:"project_name,omitempty" json:"project_name,omitempty"` + Username *string `url:"username,omitempty" json:"username,omitempty"` +} + +// GetJenkinsCIService gets Jenkins CI service settings for a project. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/services.html#get-jenkins-ci-service-settings +func (s *ServicesService) GetJenkinsCIService(pid interface{}, options ...OptionFunc) (*JenkinsCIService, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/services/jenkins", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + svc := new(JenkinsCIService) + resp, err := s.client.Do(req, svc) + if err != nil { + return nil, resp, err + } + + return svc, resp, err +} + +// SetJenkinsCIServiceOptions represents the available SetJenkinsCIService() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/services.html#jenkins-ci +type SetJenkinsCIServiceOptions struct { + URL *string `url:"jenkins_url,omitempty" json:"jenkins_url,omitempty"` + ProjectName *string `url:"project_name,omitempty" json:"project_name,omitempty"` + Username *string `url:"username,omitempty" json:"username,omitempty"` + Password *string `url:"password,omitempty" json:"password,omitempty"` +} + +// SetJenkinsCIService sets Jenkins service for a project +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/services.html#create-edit-jenkins-ci-service +func (s *ServicesService) SetJenkinsCIService(pid interface{}, opt *SetJenkinsCIServiceOptions, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/services/jenkins", url.QueryEscape(project)) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// DeleteJenkinsCIService deletes Jenkins CI service for project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/services.html#delete-jira-service +func (s *ServicesService) DeleteJenkinsCIService(pid interface{}, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/services/jenkins", url.QueryEscape(project)) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/xanzy/go-gitlab/session.go b/vendor/github.com/xanzy/go-gitlab/session.go new file mode 100644 index 00000000..f89fdbe5 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/session.go @@ -0,0 +1,78 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import "time" + +// SessionService handles communication with the session related methods of +// the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/session.html +type SessionService struct { + client *Client +} + +// Session represents a GitLab session. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/session.html#session +type Session struct { + ID int `json:"id"` + Username string `json:"username"` + Email string `json:"email"` + Name string `json:"name"` + PrivateToken string `json:"private_token"` + Blocked bool `json:"blocked"` + CreatedAt *time.Time `json:"created_at"` + Bio interface{} `json:"bio"` + Skype string `json:"skype"` + Linkedin string `json:"linkedin"` + Twitter string `json:"twitter"` + WebsiteURL string `json:"website_url"` + DarkScheme bool `json:"dark_scheme"` + ThemeID int `json:"theme_id"` + IsAdmin bool `json:"is_admin"` + CanCreateGroup bool `json:"can_create_group"` + CanCreateTeam bool `json:"can_create_team"` + CanCreateProject bool `json:"can_create_project"` +} + +// GetSessionOptions represents the available Session() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/session.html#session +type GetSessionOptions struct { + Login *string `url:"login,omitempty" json:"login,omitempty"` + Email *string `url:"email,omitempty" json:"email,omitempty"` + Password *string `url:"password,omitempty" json:"password,omitempty"` +} + +// GetSession logs in to get private token. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/session.html#session +func (s *SessionService) GetSession(opt *GetSessionOptions, options ...OptionFunc) (*Session, *Response, error) { + req, err := s.client.NewRequest("POST", "session", opt, options) + if err != nil { + return nil, nil, err + } + + session := new(Session) + resp, err := s.client.Do(req, session) + if err != nil { + return nil, resp, err + } + + return session, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/settings.go b/vendor/github.com/xanzy/go-gitlab/settings.go new file mode 100644 index 00000000..fde9910f --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/settings.go @@ -0,0 +1,265 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import "time" + +// SettingsService handles communication with the application SettingsService +// related methods of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/settings.html +type SettingsService struct { + client *Client +} + +// Settings represents the GitLab application settings. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/settings.html +type Settings struct { + ID int `json:"id"` + CreatedAt *time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at"` + AdminNotificationEmail string `json:"admin_notification_email"` + AfterSignOutPath string `json:"after_sign_out_path"` + AfterSignUpText string `json:"after_sign_up_text"` + AkismetAPIKey string `json:"akismet_api_key"` + AkismetEnabled bool `json:"akismet_enabled"` + CircuitbreakerAccessRetries int `json:"circuitbreaker_access_retries"` + CircuitbreakerBackoffThreshold int `json:"circuitbreaker_backoff_threshold"` + CircuitbreakerFailureCountThreshold int `json:"circuitbreaker_failure_count_threshold"` + CircuitbreakerFailureResetTime int `json:"circuitbreaker_failure_reset_time"` + CircuitbreakerFailureWaitTime int `json:"circuitbreaker_failure_wait_time"` + CircuitbreakerStorageTimeout int `json:"circuitbreaker_storage_timeout"` + ClientsideSentryDSN string `json:"clientside_sentry_dsn"` + ClientsideSentryEnabled bool `json:"clientside_sentry_enabled"` + ContainerRegistryTokenExpireDelay int `json:"container_registry_token_expire_delay"` + DefaultArtifactsExpireIn string `json:"default_artifacts_expire_in"` + DefaultBranchProtection int `json:"default_branch_protection"` + DefaultGroupVisibility string `json:"default_group_visibility"` + DefaultProjectVisibility string `json:"default_project_visibility"` + DefaultProjectsLimit int `json:"default_projects_limit"` + DefaultSnippetVisibility string `json:"default_snippet_visibility"` + DisabledOauthSignInSources []string `json:"disabled_oauth_sign_in_sources"` + DomainBlacklistEnabled bool `json:"domain_blacklist_enabled"` + DomainBlacklist []string `json:"domain_blacklist"` + DomainWhitelist []string `json:"domain_whitelist"` + DSAKeyRestriction int `json:"dsa_key_restriction"` + ECDSAKeyRestriction int `json:"ecdsa_key_restriction"` + Ed25519KeyRestriction int `json:"ed25519_key_restriction"` + EmailAuthorInBody bool `json:"email_author_in_body"` + EnabledGitAccessProtocol string `json:"enabled_git_access_protocol"` + GravatarEnabled bool `json:"gravatar_enabled"` + HelpPageHideCommercialContent bool `json:"help_page_hide_commercial_content"` + HelpPageSupportURL string `json:"help_page_support_url"` + HomePageURL string `json:"home_page_url"` + HousekeepingBitmapsEnabled bool `json:"housekeeping_bitmaps_enabled"` + HousekeepingEnabled bool `json:"housekeeping_enabled"` + HousekeepingFullRepackPeriod int `json:"housekeeping_full_repack_period"` + HousekeepingGcPeriod int `json:"housekeeping_gc_period"` + HousekeepingIncrementalRepackPeriod int `json:"housekeeping_incremental_repack_period"` + HTMLEmailsEnabled bool `json:"html_emails_enabled"` + ImportSources []string `json:"import_sources"` + KodingEnabled bool `json:"koding_enabled"` + KodingURL string `json:"koding_url"` + MaxArtifactsSize int `json:"max_artifacts_size"` + MaxAttachmentSize int `json:"max_attachment_size"` + MaxPagesSize int `json:"max_pages_size"` + MetricsEnabled bool `json:"metrics_enabled"` + MetricsHost string `json:"metrics_host"` + MetricsMethodCallThreshold int `json:"metrics_method_call_threshold"` + MetricsPacketSize int `json:"metrics_packet_size"` + MetricsPoolSize int `json:"metrics_pool_size"` + MetricsPort int `json:"metrics_port"` + MetricsSampleInterval int `json:"metrics_sample_interval"` + MetricsTimeout int `json:"metrics_timeout"` + PasswordAuthenticationEnabledForWeb bool `json:"password_authentication_enabled_for_web"` + PasswordAuthenticationEnabledForGit bool `json:"password_authentication_enabled_for_git"` + PerformanceBarAllowedGroupID string `json:"performance_bar_allowed_group_id"` + PerformanceBarEnabled bool `json:"performance_bar_enabled"` + PlantumlEnabled bool `json:"plantuml_enabled"` + PlantumlURL string `json:"plantuml_url"` + PollingIntervalMultiplier float64 `json:"polling_interval_multiplier"` + ProjectExportEnabled bool `json:"project_export_enabled"` + PrometheusMetricsEnabled bool `json:"prometheus_metrics_enabled"` + RecaptchaEnabled bool `json:"recaptcha_enabled"` + RecaptchaPrivateKey string `json:"recaptcha_private_key"` + RecaptchaSiteKey string `json:"recaptcha_site_key"` + RepositoryChecksEnabled bool `json:"repository_checks_enabled"` + RepositoryStorages []string `json:"repository_storages"` + RequireTwoFactorAuthentication bool `json:"require_two_factor_authentication"` + RestrictedVisibilityLevels []VisibilityValue `json:"restricted_visibility_levels"` + RsaKeyRestriction int `json:"rsa_key_restriction"` + SendUserConfirmationEmail bool `json:"send_user_confirmation_email"` + SentryDSN string `json:"sentry_dsn"` + SentryEnabled bool `json:"sentry_enabled"` + SessionExpireDelay int `json:"session_expire_delay"` + SharedRunnersEnabled bool `json:"shared_runners_enabled"` + SharedRunnersText string `json:"shared_runners_text"` + SidekiqThrottlingEnabled bool `json:"sidekiq_throttling_enabled"` + SidekiqThrottlingFactor float64 `json:"sidekiq_throttling_factor"` + SidekiqThrottlingQueues []string `json:"sidekiq_throttling_queues"` + SignInText string `json:"sign_in_text"` + SignupEnabled bool `json:"signup_enabled"` + TerminalMaxSessionTime int `json:"terminal_max_session_time"` + TwoFactorGracePeriod int `json:"two_factor_grace_period"` + UniqueIPsLimitEnabled bool `json:"unique_ips_limit_enabled"` + UniqueIPsLimitPerUser int `json:"unique_ips_limit_per_user"` + UniqueIPsLimitTimeWindow int `json:"unique_ips_limit_time_window"` + UsagePingEnabled bool `json:"usage_ping_enabled"` + UserDefaultExternal bool `json:"user_default_external"` + UserOauthApplications bool `json:"user_oauth_applications"` + VersionCheckEnabled bool `json:"version_check_enabled"` +} + +func (s Settings) String() string { + return Stringify(s) +} + +// GetSettings gets the current application settings. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/settings.html#get-current-application.settings +func (s *SettingsService) GetSettings(options ...OptionFunc) (*Settings, *Response, error) { + req, err := s.client.NewRequest("GET", "application/settings", nil, options) + if err != nil { + return nil, nil, err + } + + as := new(Settings) + resp, err := s.client.Do(req, as) + if err != nil { + return nil, resp, err + } + + return as, resp, err +} + +// UpdateSettingsOptions represents the available UpdateSettings() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/settings.html#change-application.settings +type UpdateSettingsOptions struct { + AdminNotificationEmail *string `url:"admin_notification_email,omitempty" json:"admin_notification_email,omitempty"` + AfterSignOutPath *string `url:"after_sign_out_path,omitempty" json:"after_sign_out_path,omitempty"` + AfterSignUpText *string `url:"after_sign_up_text,omitempty" json:"after_sign_up_text,omitempty"` + AkismetAPIKey *string `url:"akismet_api_key,omitempty" json:"akismet_api_key,omitempty"` + AkismetEnabled *bool `url:"akismet_enabled,omitempty" json:"akismet_enabled,omitempty"` + CircuitbreakerAccessRetries *int `url:"circuitbreaker_access_retries,omitempty" json:"circuitbreaker_access_retries,omitempty"` + CircuitbreakerBackoffThreshold *int `url:"circuitbreaker_backoff_threshold,omitempty" json:"circuitbreaker_backoff_threshold,omitempty"` + CircuitbreakerFailureCountThreshold *int `url:"circuitbreaker_failure_count_threshold,omitempty" json:"circuitbreaker_failure_count_threshold,omitempty"` + CircuitbreakerFailureResetTime *int `url:"circuitbreaker_failure_reset_time,omitempty" json:"circuitbreaker_failure_reset_time,omitempty"` + CircuitbreakerFailureWaitTime *int `url:"circuitbreaker_failure_wait_time,omitempty" json:"circuitbreaker_failure_wait_time,omitempty"` + CircuitbreakerStorageTimeout *int `url:"circuitbreaker_storage_timeout,omitempty" json:"circuitbreaker_storage_timeout,omitempty"` + ClientsideSentryDSN *string `url:"clientside_sentry_dsn,omitempty" json:"clientside_sentry_dsn,omitempty"` + ClientsideSentryEnabled *bool `url:"clientside_sentry_enabled,omitempty" json:"clientside_sentry_enabled,omitempty"` + ContainerRegistryTokenExpireDelay *int `url:"container_registry_token_expire_delay,omitempty" json:"container_registry_token_expire_delay,omitempty"` + DefaultArtifactsExpireIn *string `url:"default_artifacts_expire_in,omitempty" json:"default_artifacts_expire_in,omitempty"` + DefaultBranchProtection *int `url:"default_branch_protection,omitempty" json:"default_branch_protection,omitempty"` + DefaultGroupVisibility *string `url:"default_group_visibility,omitempty" json:"default_group_visibility,omitempty"` + DefaultProjectVisibility *string `url:"default_project_visibility,omitempty" json:"default_project_visibility,omitempty"` + DefaultProjectsLimit *int `url:"default_projects_limit,omitempty" json:"default_projects_limit,omitempty"` + DefaultSnippetVisibility *string `url:"default_snippet_visibility,omitempty" json:"default_snippet_visibility,omitempty"` + DisabledOauthSignInSources []string `url:"disabled_oauth_sign_in_sources,omitempty" json:"disabled_oauth_sign_in_sources,omitempty"` + DomainBlacklistEnabled *bool `url:"domain_blacklist_enabled,omitempty" json:"domain_blacklist_enabled,omitempty"` + DomainBlacklist []string `url:"domain_blacklist,omitempty" json:"domain_blacklist,omitempty"` + DomainWhitelist []string `url:"domain_whitelist,omitempty" json:"domain_whitelist,omitempty"` + DSAKeyRestriction *int `url:"dsa_key_restriction,omitempty" json:"dsa_key_restriction,omitempty"` + ECDSAKeyRestriction *int `url:"ecdsa_key_restriction,omitempty" json:"ecdsa_key_restriction,omitempty"` + Ed25519KeyRestriction *int `url:"ed25519_key_restriction,omitempty" json:"ed25519_key_restriction,omitempty"` + EmailAuthorInBody *bool `url:"email_author_in_body,omitempty" json:"email_author_in_body,omitempty"` + EnabledGitAccessProtocol *string `url:"enabled_git_access_protocol,omitempty" json:"enabled_git_access_protocol,omitempty"` + GravatarEnabled *bool `url:"gravatar_enabled,omitempty" json:"gravatar_enabled,omitempty"` + HelpPageHideCommercialContent *bool `url:"help_page_hide_commercial_content,omitempty" json:"help_page_hide_commercial_content,omitempty"` + HelpPageSupportURL *string `url:"help_page_support_url,omitempty" json:"help_page_support_url,omitempty"` + HomePageURL *string `url:"home_page_url,omitempty" json:"home_page_url,omitempty"` + HousekeepingBitmapsEnabled *bool `url:"housekeeping_bitmaps_enabled,omitempty" json:"housekeeping_bitmaps_enabled,omitempty"` + HousekeepingEnabled *bool `url:"housekeeping_enabled,omitempty" json:"housekeeping_enabled,omitempty"` + HousekeepingFullRepackPeriod *int `url:"housekeeping_full_repack_period,omitempty" json:"housekeeping_full_repack_period,omitempty"` + HousekeepingGcPeriod *int `url:"housekeeping_gc_period,omitempty" json:"housekeeping_gc_period,omitempty"` + HousekeepingIncrementalRepackPeriod *int `url:"housekeeping_incremental_repack_period,omitempty" json:"housekeeping_incremental_repack_period,omitempty"` + HTMLEmailsEnabled *bool `url:"html_emails_enabled,omitempty" json:"html_emails_enabled,omitempty"` + ImportSources []string `url:"import_sources,omitempty" json:"import_sources,omitempty"` + KodingEnabled *bool `url:"koding_enabled,omitempty" json:"koding_enabled,omitempty"` + KodingURL *string `url:"koding_url,omitempty" json:"koding_url,omitempty"` + MaxArtifactsSize *int `url:"max_artifacts_size,omitempty" json:"max_artifacts_size,omitempty"` + MaxAttachmentSize *int `url:"max_attachment_size,omitempty" json:"max_attachment_size,omitempty"` + MaxPagesSize *int `url:"max_pages_size,omitempty" json:"max_pages_size,omitempty"` + MetricsEnabled *bool `url:"metrics_enabled,omitempty" json:"metrics_enabled,omitempty"` + MetricsHost *string `url:"metrics_host,omitempty" json:"metrics_host,omitempty"` + MetricsMethodCallThreshold *int `url:"metrics_method_call_threshold,omitempty" json:"metrics_method_call_threshold,omitempty"` + MetricsPacketSize *int `url:"metrics_packet_size,omitempty" json:"metrics_packet_size,omitempty"` + MetricsPoolSize *int `url:"metrics_pool_size,omitempty" json:"metrics_pool_size,omitempty"` + MetricsPort *int `url:"metrics_port,omitempty" json:"metrics_port,omitempty"` + MetricsSampleInterval *int `url:"metrics_sample_interval,omitempty" json:"metrics_sample_interval,omitempty"` + MetricsTimeout *int `url:"metrics_timeout,omitempty" json:"metrics_timeout,omitempty"` + PasswordAuthenticationEnabledForWeb *bool `url:"password_authentication_enabled_for_web,omitempty" json:"password_authentication_enabled_for_web,omitempty"` + PasswordAuthenticationEnabledForGit *bool `url:"password_authentication_enabled_for_git,omitempty" json:"password_authentication_enabled_for_git,omitempty"` + PerformanceBarAllowedGroupID *string `url:"performance_bar_allowed_group_id,omitempty" json:"performance_bar_allowed_group_id,omitempty"` + PerformanceBarEnabled *bool `url:"performance_bar_enabled,omitempty" json:"performance_bar_enabled,omitempty"` + PlantumlEnabled *bool `url:"plantuml_enabled,omitempty" json:"plantuml_enabled,omitempty"` + PlantumlURL *string `url:"plantuml_url,omitempty" json:"plantuml_url,omitempty"` + PollingIntervalMultiplier *float64 `url:"polling_interval_multiplier,omitempty" json:"polling_interval_multiplier,omitempty"` + ProjectExportEnabled *bool `url:"project_export_enabled,omitempty" json:"project_export_enabled,omitempty"` + PrometheusMetricsEnabled *bool `url:"prometheus_metrics_enabled,omitempty" json:"prometheus_metrics_enabled,omitempty"` + RecaptchaEnabled *bool `url:"recaptcha_enabled,omitempty" json:"recaptcha_enabled,omitempty"` + RecaptchaPrivateKey *string `url:"recaptcha_private_key,omitempty" json:"recaptcha_private_key,omitempty"` + RecaptchaSiteKey *string `url:"recaptcha_site_key,omitempty" json:"recaptcha_site_key,omitempty"` + RepositoryChecksEnabled *bool `url:"repository_checks_enabled,omitempty" json:"repository_checks_enabled,omitempty"` + RepositoryStorages []string `url:"repository_storages,omitempty" json:"repository_storages,omitempty"` + RequireTwoFactorAuthentication *bool `url:"require_two_factor_authentication,omitempty" json:"require_two_factor_authentication,omitempty"` + RestrictedVisibilityLevels []VisibilityValue `url:"restricted_visibility_levels,omitempty" json:"restricted_visibility_levels,omitempty"` + RsaKeyRestriction *int `url:"rsa_key_restriction,omitempty" json:"rsa_key_restriction,omitempty"` + SendUserConfirmationEmail *bool `url:"send_user_confirmation_email,omitempty" json:"send_user_confirmation_email,omitempty"` + SentryDSN *string `url:"sentry_dsn,omitempty" json:"sentry_dsn,omitempty"` + SentryEnabled *bool `url:"sentry_enabled,omitempty" json:"sentry_enabled,omitempty"` + SessionExpireDelay *int `url:"session_expire_delay,omitempty" json:"session_expire_delay,omitempty"` + SharedRunnersEnabled *bool `url:"shared_runners_enabled,omitempty" json:"shared_runners_enabled,omitempty"` + SharedRunnersText *string `url:"shared_runners_text,omitempty" json:"shared_runners_text,omitempty"` + SidekiqThrottlingEnabled *bool `url:"sidekiq_throttling_enabled,omitempty" json:"sidekiq_throttling_enabled,omitempty"` + SidekiqThrottlingFactor *float64 `url:"sidekiq_throttling_factor,omitempty" json:"sidekiq_throttling_factor,omitempty"` + SidekiqThrottlingQueues []string `url:"sidekiq_throttling_queues,omitempty" json:"sidekiq_throttling_queues,omitempty"` + SignInText *string `url:"sign_in_text,omitempty" json:"sign_in_text,omitempty"` + SignupEnabled *bool `url:"signup_enabled,omitempty" json:"signup_enabled,omitempty"` + TerminalMaxSessionTime *int `url:"terminal_max_session_time,omitempty" json:"terminal_max_session_time,omitempty"` + TwoFactorGracePeriod *int `url:"two_factor_grace_period,omitempty" json:"two_factor_grace_period,omitempty"` + UniqueIPsLimitEnabled *bool `url:"unique_ips_limit_enabled,omitempty" json:"unique_ips_limit_enabled,omitempty"` + UniqueIPsLimitPerUser *int `url:"unique_ips_limit_per_user,omitempty" json:"unique_ips_limit_per_user,omitempty"` + UniqueIPsLimitTimeWindow *int `url:"unique_ips_limit_time_window,omitempty" json:"unique_ips_limit_time_window,omitempty"` + UsagePingEnabled *bool `url:"usage_ping_enabled,omitempty" json:"usage_ping_enabled,omitempty"` + UserDefaultExternal *bool `url:"user_default_external,omitempty" json:"user_default_external,omitempty"` + UserOauthApplications *bool `url:"user_oauth_applications,omitempty" json:"user_oauth_applications,omitempty"` + VersionCheckEnabled *bool `url:"version_check_enabled,omitempty" json:"version_check_enabled,omitempty"` +} + +// UpdateSettings updates the application settings. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/settings.html#change-application.settings +func (s *SettingsService) UpdateSettings(opt *UpdateSettingsOptions, options ...OptionFunc) (*Settings, *Response, error) { + req, err := s.client.NewRequest("PUT", "application/settings", opt, options) + if err != nil { + return nil, nil, err + } + + as := new(Settings) + resp, err := s.client.Do(req, as) + if err != nil { + return nil, resp, err + } + + return as, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/sidekiq_metrics.go b/vendor/github.com/xanzy/go-gitlab/sidekiq_metrics.go new file mode 100644 index 00000000..83e77024 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/sidekiq_metrics.go @@ -0,0 +1,154 @@ +// +// Copyright 2018, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import "time" + +// SidekiqService handles communication with the sidekiq service +// +// GitLab API docs: https://docs.gitlab.com/ce/api/sidekiq_metrics.html +type SidekiqService struct { + client *Client +} + +// QueueMetrics represents the GitLab sidekiq queue metrics. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/sidekiq_metrics.html#get-the-current-queue-metrics +type QueueMetrics struct { + Queues map[string]struct { + Backlog int `json:"backlog"` + Latency int `json:"latency"` + } `json:"queues"` +} + +// GetQueueMetrics lists information about all the registered queues, +// their backlog and their latency. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/sidekiq_metrics.html#get-the-current-queue-metrics +func (s *SidekiqService) GetQueueMetrics(options ...OptionFunc) (*QueueMetrics, *Response, error) { + req, err := s.client.NewRequest("GET", "/sidekiq/queue_metrics", nil, options) + if err != nil { + return nil, nil, err + } + + q := new(QueueMetrics) + resp, err := s.client.Do(req, q) + if err != nil { + return nil, resp, err + } + + return q, resp, err +} + +// ProcessMetrics represents the GitLab sidekiq process metrics. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/sidekiq_metrics.html#get-the-current-process-metrics +type ProcessMetrics struct { + Processes []struct { + Hostname string `json:"hostname"` + Pid int `json:"pid"` + Tag string `json:"tag"` + StartedAt *time.Time `json:"started_at"` + Queues []string `json:"queues"` + Labels []string `json:"labels"` + Concurrency int `json:"concurrency"` + Busy int `json:"busy"` + } `json:"processes"` +} + +// GetProcessMetrics lists information about all the Sidekiq workers registered +// to process your queues. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/sidekiq_metrics.html#get-the-current-process-metrics +func (s *SidekiqService) GetProcessMetrics(options ...OptionFunc) (*ProcessMetrics, *Response, error) { + req, err := s.client.NewRequest("GET", "/sidekiq/process_metrics", nil, options) + if err != nil { + return nil, nil, err + } + + p := new(ProcessMetrics) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// JobStats represents the GitLab sidekiq job stats. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/sidekiq_metrics.html#get-the-current-job-statistics +type JobStats struct { + Jobs struct { + Processed int `json:"processed"` + Failed int `json:"failed"` + Enqueued int `json:"enqueued"` + } `json:"jobs"` +} + +// GetJobStats list information about the jobs that Sidekiq has performed. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/sidekiq_metrics.html#get-the-current-job-statistics +func (s *SidekiqService) GetJobStats(options ...OptionFunc) (*JobStats, *Response, error) { + req, err := s.client.NewRequest("GET", "/sidekiq/job_stats", nil, options) + if err != nil { + return nil, nil, err + } + + j := new(JobStats) + resp, err := s.client.Do(req, j) + if err != nil { + return nil, resp, err + } + + return j, resp, err +} + +// CompoundMetrics represents the GitLab sidekiq compounded stats. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/sidekiq_metrics.html#get-a-compound-response-of-all-the-previously-mentioned-metrics +type CompoundMetrics struct { + QueueMetrics + ProcessMetrics + JobStats +} + +// GetCompoundMetrics lists all the currently available information about Sidekiq. +// Get a compound response of all the previously mentioned metrics +// +// GitLab API docs: https://docs.gitlab.com/ce/api/sidekiq_metrics.html#get-the-current-job-statistics +func (s *SidekiqService) GetCompoundMetrics(options ...OptionFunc) (*CompoundMetrics, *Response, error) { + req, err := s.client.NewRequest("GET", "/sidekiq/compound_metrics", nil, options) + if err != nil { + return nil, nil, err + } + + c := new(CompoundMetrics) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/snippets.go b/vendor/github.com/xanzy/go-gitlab/snippets.go new file mode 100644 index 00000000..be232c8c --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/snippets.go @@ -0,0 +1,230 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "bytes" + "fmt" + "time" +) + +// SnippetsService handles communication with the snippets +// related methods of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/snippets.html +type SnippetsService struct { + client *Client +} + +// Snippet represents a GitLab snippet. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/snippets.html +type Snippet struct { + ID int `json:"id"` + Title string `json:"title"` + FileName string `json:"file_name"` + Description string `json:"description"` + Author struct { + ID int `json:"id"` + Username string `json:"username"` + Email string `json:"email"` + Name string `json:"name"` + State string `json:"state"` + CreatedAt *time.Time `json:"created_at"` + } `json:"author"` + UpdatedAt *time.Time `json:"updated_at"` + CreatedAt *time.Time `json:"created_at"` + WebURL string `json:"web_url"` + RawURL string `json:"raw_url"` +} + +func (s Snippet) String() string { + return Stringify(s) +} + +// ListSnippetsOptions represents the available ListSnippets() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/snippets.html#list-snippets +type ListSnippetsOptions ListOptions + +// ListSnippets gets a list of snippets. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/snippets.html#list-snippets +func (s *SnippetsService) ListSnippets(opt *ListSnippetsOptions, options ...OptionFunc) ([]*Snippet, *Response, error) { + req, err := s.client.NewRequest("GET", "snippets", opt, options) + if err != nil { + return nil, nil, err + } + + var ps []*Snippet + resp, err := s.client.Do(req, &ps) + if err != nil { + return nil, resp, err + } + + return ps, resp, err +} + +// GetSnippet gets a single snippet +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/snippets.html#single-snippet +func (s *SnippetsService) GetSnippet(snippet int, options ...OptionFunc) (*Snippet, *Response, error) { + u := fmt.Sprintf("snippets/%d", snippet) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + ps := new(Snippet) + resp, err := s.client.Do(req, ps) + if err != nil { + return nil, resp, err + } + + return ps, resp, err +} + +// CreateSnippetOptions represents the available CreateSnippet() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/snippets.html#create-new-snippet +type CreateSnippetOptions struct { + Title *string `url:"title,omitempty" json:"title,omitempty"` + FileName *string `url:"file_name,omitempty" json:"file_name,omitempty"` + Description *string `url:"description,omitempty" json:"description,omitempty"` + Content *string `url:"content,omitempty" json:"content,omitempty"` + Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"` +} + +// CreateSnippet creates a new snippet. The user must have permission +// to create new snippets. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/snippets.html#create-new-snippet +func (s *SnippetsService) CreateSnippet(opt *CreateSnippetOptions, options ...OptionFunc) (*Snippet, *Response, error) { + req, err := s.client.NewRequest("POST", "snippets", opt, options) + if err != nil { + return nil, nil, err + } + + ps := new(Snippet) + resp, err := s.client.Do(req, ps) + if err != nil { + return nil, resp, err + } + + return ps, resp, err +} + +// UpdateSnippetOptions represents the available UpdateSnippet() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/snippets.html#update-snippet +type UpdateSnippetOptions struct { + Title *string `url:"title,omitempty" json:"title,omitempty"` + FileName *string `url:"file_name,omitempty" json:"file_name,omitempty"` + Description *string `url:"description,omitempty" json:"description,omitempty"` + Content *string `url:"content,omitempty" json:"content,omitempty"` + Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"` +} + +// UpdateSnippet updates an existing snippet. The user must have +// permission to change an existing snippet. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/snippets.html#update-snippet +func (s *SnippetsService) UpdateSnippet(snippet int, opt *UpdateSnippetOptions, options ...OptionFunc) (*Snippet, *Response, error) { + u := fmt.Sprintf("snippets/%d", snippet) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + ps := new(Snippet) + resp, err := s.client.Do(req, ps) + if err != nil { + return nil, resp, err + } + + return ps, resp, err +} + +// DeleteSnippet deletes an existing snippet. This is an idempotent +// function and deleting a non-existent snippet still returns a 200 OK status +// code. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/snippets.html#delete-snippet +func (s *SnippetsService) DeleteSnippet(snippet int, options ...OptionFunc) (*Response, error) { + u := fmt.Sprintf("snippets/%d", snippet) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// SnippetContent returns the raw snippet as plain text. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/snippets.html#snippet-content +func (s *SnippetsService) SnippetContent(snippet int, options ...OptionFunc) ([]byte, *Response, error) { + u := fmt.Sprintf("snippets/%d/raw", snippet) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + var b bytes.Buffer + resp, err := s.client.Do(req, &b) + if err != nil { + return nil, resp, err + } + + return b.Bytes(), resp, err +} + +// ExploreSnippetsOptions represents the available ExploreSnippets() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/snippets.html#explore-all-public-snippets +type ExploreSnippetsOptions ListOptions + +// ExploreSnippets gets the list of public snippets. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/snippets.html#explore-all-public-snippets +func (s *SnippetsService) ExploreSnippets(opt *ExploreSnippetsOptions, options ...OptionFunc) ([]*Snippet, *Response, error) { + req, err := s.client.NewRequest("GET", "snippets/public", nil, options) + if err != nil { + return nil, nil, err + } + + var ps []*Snippet + resp, err := s.client.Do(req, &ps) + if err != nil { + return nil, resp, err + } + + return ps, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/strings.go b/vendor/github.com/xanzy/go-gitlab/strings.go new file mode 100644 index 00000000..aeefb6b8 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/strings.go @@ -0,0 +1,94 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "bytes" + "fmt" + + "reflect" +) + +// Stringify attempts to create a reasonable string representation of types in +// the GitHub library. It does things like resolve pointers to their values +// and omits struct fields with nil values. +func Stringify(message interface{}) string { + var buf bytes.Buffer + v := reflect.ValueOf(message) + stringifyValue(&buf, v) + return buf.String() +} + +// stringifyValue was heavily inspired by the goprotobuf library. +func stringifyValue(buf *bytes.Buffer, val reflect.Value) { + if val.Kind() == reflect.Ptr && val.IsNil() { + buf.WriteString("") + return + } + + v := reflect.Indirect(val) + + switch v.Kind() { + case reflect.String: + fmt.Fprintf(buf, `"%s"`, v) + case reflect.Slice: + buf.WriteByte('[') + for i := 0; i < v.Len(); i++ { + if i > 0 { + buf.WriteByte(' ') + } + + stringifyValue(buf, v.Index(i)) + } + + buf.WriteByte(']') + return + case reflect.Struct: + if v.Type().Name() != "" { + buf.WriteString(v.Type().String()) + } + + buf.WriteByte('{') + + var sep bool + for i := 0; i < v.NumField(); i++ { + fv := v.Field(i) + if fv.Kind() == reflect.Ptr && fv.IsNil() { + continue + } + if fv.Kind() == reflect.Slice && fv.IsNil() { + continue + } + + if sep { + buf.WriteString(", ") + } else { + sep = true + } + + buf.WriteString(v.Type().Field(i).Name) + buf.WriteByte(':') + stringifyValue(buf, fv) + } + + buf.WriteByte('}') + default: + if v.CanInterface() { + fmt.Fprint(buf, v.Interface()) + } + } +} diff --git a/vendor/github.com/xanzy/go-gitlab/system_hooks.go b/vendor/github.com/xanzy/go-gitlab/system_hooks.go new file mode 100644 index 00000000..d5209d4f --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/system_hooks.go @@ -0,0 +1,143 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "time" +) + +// SystemHooksService handles communication with the system hooks related +// methods of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/system_hooks.html +type SystemHooksService struct { + client *Client +} + +// Hook represents a GitLap system hook. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/system_hooks.html +type Hook struct { + ID int `json:"id"` + URL string `json:"url"` + CreatedAt *time.Time `json:"created_at"` +} + +func (h Hook) String() string { + return Stringify(h) +} + +// ListHooks gets a list of system hooks. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/system_hooks.html#list-system-hooks +func (s *SystemHooksService) ListHooks(options ...OptionFunc) ([]*Hook, *Response, error) { + req, err := s.client.NewRequest("GET", "hooks", nil, options) + if err != nil { + return nil, nil, err + } + + var h []*Hook + resp, err := s.client.Do(req, &h) + if err != nil { + return nil, resp, err + } + + return h, resp, err +} + +// AddHookOptions represents the available AddHook() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/system_hooks.html#add-new-system-hook-hook +type AddHookOptions struct { + URL *string `url:"url,omitempty" json:"url,omitempty"` +} + +// AddHook adds a new system hook hook. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/system_hooks.html#add-new-system-hook-hook +func (s *SystemHooksService) AddHook(opt *AddHookOptions, options ...OptionFunc) (*Hook, *Response, error) { + req, err := s.client.NewRequest("POST", "hooks", opt, options) + if err != nil { + return nil, nil, err + } + + h := new(Hook) + resp, err := s.client.Do(req, h) + if err != nil { + return nil, resp, err + } + + return h, resp, err +} + +// HookEvent represents an event trigger by a GitLab system hook. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/system_hooks.html +type HookEvent struct { + EventName string `json:"event_name"` + Name string `json:"name"` + Path string `json:"path"` + ProjectID int `json:"project_id"` + OwnerName string `json:"owner_name"` + OwnerEmail string `json:"owner_email"` +} + +func (h HookEvent) String() string { + return Stringify(h) +} + +// TestHook tests a system hook. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/system_hooks.html#test-system-hook +func (s *SystemHooksService) TestHook(hook int, options ...OptionFunc) (*HookEvent, *Response, error) { + u := fmt.Sprintf("hooks/%d", hook) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + h := new(HookEvent) + resp, err := s.client.Do(req, h) + if err != nil { + return nil, resp, err + } + + return h, resp, err +} + +// DeleteHook deletes a system hook. This is an idempotent API function and +// returns 200 OK even if the hook is not available. If the hook is deleted it +// is also returned as JSON. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/system_hooks.html#delete-system-hook +func (s *SystemHooksService) DeleteHook(hook int, options ...OptionFunc) (*Response, error) { + u := fmt.Sprintf("hooks/%d", hook) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/xanzy/go-gitlab/tags.go b/vendor/github.com/xanzy/go-gitlab/tags.go new file mode 100644 index 00000000..b726410e --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/tags.go @@ -0,0 +1,232 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/url" +) + +// TagsService handles communication with the tags related methods +// of the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/tags.html +type TagsService struct { + client *Client +} + +// Tag represents a GitLab tag. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/tags.html +type Tag struct { + Commit *Commit `json:"commit"` + Release *Release `json:"release"` + Name string `json:"name"` + Message string `json:"message"` +} + +// Release represents a GitLab version release. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/tags.html +type Release struct { + TagName string `json:"tag_name"` + Description string `json:"description"` +} + +func (t Tag) String() string { + return Stringify(t) +} + +// ListTagsOptions represents the available ListTags() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/tags.html#list-project-repository-tags +type ListTagsOptions ListOptions + +// ListTags gets a list of tags from a project, sorted by name in reverse +// alphabetical order. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/tags.html#list-project-repository-tags +func (s *TagsService) ListTags(pid interface{}, opt *ListTagsOptions, options ...OptionFunc) ([]*Tag, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/tags", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var t []*Tag + resp, err := s.client.Do(req, &t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// GetTag a specific repository tag determined by its name. It returns 200 together +// with the tag information if the tag exists. It returns 404 if the tag does not exist. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/tags.html#get-a-single-repository-tag +func (s *TagsService) GetTag(pid interface{}, tag string, options ...OptionFunc) (*Tag, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/tags/%s", url.QueryEscape(project), tag) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + var t *Tag + resp, err := s.client.Do(req, &t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// CreateTagOptions represents the available CreateTag() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/tags.html#create-a-new-tag +type CreateTagOptions struct { + TagName *string `url:"tag_name,omitempty" json:"tag_name,omitempty"` + Ref *string `url:"ref,omitempty" json:"ref,omitempty"` + Message *string `url:"message,omitempty" json:"message,omitempty"` + ReleaseDescription *string `url:"release_description:omitempty" json:"release_description,omitempty"` +} + +// CreateTag creates a new tag in the repository that points to the supplied ref. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/tags.html#create-a-new-tag +func (s *TagsService) CreateTag(pid interface{}, opt *CreateTagOptions, options ...OptionFunc) (*Tag, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/tags", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + t := new(Tag) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// DeleteTag deletes a tag of a repository with given name. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/tags.html#delete-a-tag +func (s *TagsService) DeleteTag(pid interface{}, tag string, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/repository/tags/%s", url.QueryEscape(project), tag) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// CreateReleaseOptions represents the available CreateRelease() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/tags.html#create-a-new-release +type CreateReleaseOptions struct { + Description *string `url:"description:omitempty" json:"description,omitempty"` +} + +// CreateRelease Add release notes to the existing git tag. +// If there already exists a release for the given tag, status code 409 is returned. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/tags.html#create-a-new-release +func (s *TagsService) CreateRelease(pid interface{}, tag string, opt *CreateReleaseOptions, options ...OptionFunc) (*Release, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/tags/%s/release", url.QueryEscape(project), tag) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + r := new(Release) + resp, err := s.client.Do(req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} + +// UpdateReleaseOptions represents the available UpdateRelease() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/tags.html#update-a-release +type UpdateReleaseOptions struct { + Description *string `url:"description:omitempty" json:"description,omitempty"` +} + +// UpdateRelease Updates the release notes of a given release. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/tags.html#update-a-release +func (s *TagsService) UpdateRelease(pid interface{}, tag string, opt *UpdateReleaseOptions, options ...OptionFunc) (*Release, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/repository/tags/%s/release", url.QueryEscape(project), tag) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + r := new(Release) + resp, err := s.client.Do(req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/time_stats.go b/vendor/github.com/xanzy/go-gitlab/time_stats.go new file mode 100644 index 00000000..5e3bff90 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/time_stats.go @@ -0,0 +1,163 @@ +package gitlab + +import ( + "fmt" + "net/url" +) + +// timeStatsService handles communication with the time tracking related +// methods of the GitLab API. +// +// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html +type timeStatsService struct { + client *Client +} + +// TimeStats represents the time estimates and time spent for an issue. +// +// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html +type TimeStats struct { + HumanTimeEstimate string `json:"human_time_estimate"` + HumanTotalTimeSpent string `json:"human_total_time_spent"` + TimeEstimate int `json:"time_estimate"` + TotalTimeSpent int `json:"total_time_spent"` +} + +func (t TimeStats) String() string { + return Stringify(t) +} + +// SetTimeEstimateOptions represents the available SetTimeEstimate() +// options. +// +// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html +type SetTimeEstimateOptions struct { + Duration *string `url:"duration,omitempty" json:"duration,omitempty"` +} + +// setTimeEstimate sets the time estimate for a single project issue. +// +// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html +func (s *timeStatsService) setTimeEstimate(pid interface{}, entity string, issue int, opt *SetTimeEstimateOptions, options ...OptionFunc) (*TimeStats, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/%s/%d/time_estimate", url.QueryEscape(project), entity, issue) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + t := new(TimeStats) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// resetTimeEstimate resets the time estimate for a single project issue. +// +// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html +func (s *timeStatsService) resetTimeEstimate(pid interface{}, entity string, issue int, options ...OptionFunc) (*TimeStats, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/%s/%d/reset_time_estimate", url.QueryEscape(project), entity, issue) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + t := new(TimeStats) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// AddSpentTimeOptions represents the available AddSpentTime() options. +// +// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html +type AddSpentTimeOptions struct { + Duration *string `url:"duration,omitempty" json:"duration,omitempty"` +} + +// addSpentTime adds spent time for a single project issue. +// +// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html +func (s *timeStatsService) addSpentTime(pid interface{}, entity string, issue int, opt *AddSpentTimeOptions, options ...OptionFunc) (*TimeStats, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/%s/%d/add_spent_time", url.QueryEscape(project), entity, issue) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + t := new(TimeStats) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// resetSpentTime resets the spent time for a single project issue. +// +// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html +func (s *timeStatsService) resetSpentTime(pid interface{}, entity string, issue int, options ...OptionFunc) (*TimeStats, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/%s/%d/reset_spent_time", url.QueryEscape(project), entity, issue) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, nil, err + } + + t := new(TimeStats) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// getTimeSpent gets the spent time for a single project issue. +// +// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html +func (s *timeStatsService) getTimeSpent(pid interface{}, entity string, issue int, options ...OptionFunc) (*TimeStats, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/%s/%d/time_stats", url.QueryEscape(project), entity, issue) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + t := new(TimeStats) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/todos.go b/vendor/github.com/xanzy/go-gitlab/todos.go new file mode 100644 index 00000000..db58eddb --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/todos.go @@ -0,0 +1,175 @@ +package gitlab + +import "time" +import "fmt" + +// TodosService handles communication with the todos related methods of +// the Gitlab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/todos.html +type TodosService struct { + client *Client +} + +// TodoAction represents the available actions that can be performed on a todo. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/todos.html +type TodoAction string + +// The available todo actions. +const ( + TodoAssigned TodoAction = "assigned" + TodoMentioned TodoAction = "mentioned" + TodoBuildFailed TodoAction = "build_failed" + TodoMarked TodoAction = "marked" + TodoApprovalRequired TodoAction = "approval_required" + TodoDirectlyAddressed TodoAction = "directly_addressed" +) + +// TodoTarget represents a todo target of type Issue or MergeRequest +type TodoTarget struct { + // TODO: replace both Assignee and Author structs with v4 User struct + Assignee struct { + Name string `json:"name"` + Username string `json:"username"` + ID int `json:"id"` + State string `json:"state"` + AvatarURL string `json:"avatar_url"` + WebURL string `json:"web_url"` + } `json:"assignee"` + Author struct { + Name string `json:"name"` + Username string `json:"username"` + ID int `json:"id"` + State string `json:"state"` + AvatarURL string `json:"avatar_url"` + WebURL string `json:"web_url"` + } `json:"author"` + CreatedAt *time.Time `json:"created_at"` + Description string `json:"description"` + Downvotes int `json:"downvotes"` + ID int `json:"id"` + IID int `json:"iid"` + Labels []string `json:"labels"` + Milestone Milestone `json:"milestone"` + ProjectID int `json:"project_id"` + State string `json:"state"` + Subscribed bool `json:"subscribed"` + Title string `json:"title"` + UpdatedAt *time.Time `json:"updated_at"` + Upvotes int `json:"upvotes"` + UserNotesCount int `json:"user_notes_count"` + WebURL string `json:"web_url"` + + // Only available for type Issue + Confidential bool `json:"confidential"` + DueDate string `json:"due_date"` + Weight int `json:"weight"` + + // Only available for type MergeRequest + ApprovalsBeforeMerge bool `json:"approvals_before_merge"` + ForceRemoveSourceBranch bool `json:"force_remove_source_branch"` + MergeCommitSha string `json:"merge_commit_sha"` + MergeWhenPipelineSucceeds bool `json:"merge_when_pipeline_succeeds"` + MergeStatus string `json:"merge_status"` + Sha string `json:"sha"` + ShouldRemoveSourceBranch bool `json:"should_remove_source_branch"` + SourceBranch string `json:"source_branch"` + SourceProjectID int `json:"source_project_id"` + Squash bool `json:"squash"` + TargetBranch string `json:"target_branch"` + TargetProjectID int `json:"target_project_id"` + WorkInProgress bool `json:"work_in_progress"` +} + +// Todo represents a GitLab todo. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/todos.html +type Todo struct { + ID int `json:"id"` + Project struct { + ID int `json:"id"` + HTTPURLToRepo string `json:"http_url_to_repo"` + WebURL string `json:"web_url"` + Name string `json:"name"` + NameWithNamespace string `json:"name_with_namespace"` + Path string `json:"path"` + PathWithNamespace string `json:"path_with_namespace"` + } `json:"project"` + Author struct { + ID int `json:"id"` + Name string `json:"name"` + Username string `json:"username"` + State string `json:"state"` + AvatarURL string `json:"avatar_url"` + WebURL string `json:"web_url"` + } `json:"author"` + ActionName TodoAction `json:"action_name"` + TargetType string `json:"target_type"` + Target TodoTarget `json:"target"` + TargetURL string `json:"target_url"` + Body string `json:"body"` + State string `json:"state"` + CreatedAt *time.Time `json:"created_at"` +} + +func (t Todo) String() string { + return Stringify(t) +} + +// ListTodosOptions represents the available ListTodos() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/todos.html#get-a-list-of-todos +type ListTodosOptions struct { + Action *TodoAction `url:"action,omitempty" json:"action,omitempty"` + AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"` + ProjectID *int `url:"project_id,omitempty" json:"project_id,omitempty"` + State *string `url:"state,omitempty" json:"state,omitempty"` + Type *string `url:"type,omitempty" json:"type,omitempty"` +} + +// ListTodos lists all todos created by authenticated user. +// When no filter is applied, it returns all pending todos for the current user. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/todos.html#get-a-list-of-todos +func (s *TodosService) ListTodos(opt *ListTodosOptions, options ...OptionFunc) ([]*Todo, *Response, error) { + req, err := s.client.NewRequest("GET", "todos", opt, options) + if err != nil { + return nil, nil, err + } + + var t []*Todo + resp, err := s.client.Do(req, &t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// MarkTodoAsDone marks a single pending todo given by its ID for the current user as done. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/todos.html#mark-a-todo-as-done +func (s *TodosService) MarkTodoAsDone(id int, options ...OptionFunc) (*Response, error) { + u := fmt.Sprintf("todos/%d/mark_as_done", id) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// MarkAllTodosAsDone marks all pending todos for the current user as done. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/todos.html#mark-all-todos-as-done +func (s *TodosService) MarkAllTodosAsDone(options ...OptionFunc) (*Response, error) { + req, err := s.client.NewRequest("POST", "todos/mark_as_done", nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/xanzy/go-gitlab/users.go b/vendor/github.com/xanzy/go-gitlab/users.go new file mode 100644 index 00000000..a619ab03 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/users.go @@ -0,0 +1,767 @@ +// +// Copyright 2017, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "errors" + "fmt" + "time" +) + +// UsersService handles communication with the user related methods of +// the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html +type UsersService struct { + client *Client +} + +// User represents a GitLab user. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html +type User struct { + ID int `json:"id"` + Username string `json:"username"` + Email string `json:"email"` + Name string `json:"name"` + State string `json:"state"` + CreatedAt *time.Time `json:"created_at"` + Bio string `json:"bio"` + Location string `json:"location"` + Skype string `json:"skype"` + Linkedin string `json:"linkedin"` + Twitter string `json:"twitter"` + WebsiteURL string `json:"website_url"` + Organization string `json:"organization"` + ExternUID string `json:"extern_uid"` + Provider string `json:"provider"` + ThemeID int `json:"theme_id"` + LastActivityOn *ISOTime `json:"last_activity_on"` + ColorSchemeID int `json:"color_scheme_id"` + IsAdmin bool `json:"is_admin"` + AvatarURL string `json:"avatar_url"` + CanCreateGroup bool `json:"can_create_group"` + CanCreateProject bool `json:"can_create_project"` + ProjectsLimit int `json:"projects_limit"` + CurrentSignInAt *time.Time `json:"current_sign_in_at"` + LastSignInAt *time.Time `json:"last_sign_in_at"` + TwoFactorEnabled bool `json:"two_factor_enabled"` + Identities []*UserIdentity `json:"identities"` + External bool `json:"external"` +} + +// UserIdentity represents a user identity. +type UserIdentity struct { + Provider string `json:"provider"` + ExternUID string `json:"extern_uid"` +} + +// ListUsersOptions represents the available ListUsers() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#list-users +type ListUsersOptions struct { + ListOptions + Active *bool `url:"active,omitempty" json:"active,omitempty"` + Blocked *bool `url:"blocked,omitempty" json:"blocked,omitempty"` + + // The options below are only available for admins. + Search *string `url:"search,omitempty" json:"search,omitempty"` + Username *string `url:"username,omitempty" json:"username,omitempty"` + ExternalUID *string `url:"extern_uid,omitempty" json:"extern_uid,omitempty"` + Provider *string `url:"provider,omitempty" json:"provider,omitempty"` + CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"` + CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"` + OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` + Sort *string `url:"sort,omitempty" json:"sort,omitempty"` +} + +// ListUsers gets a list of users. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#list-users +func (s *UsersService) ListUsers(opt *ListUsersOptions, options ...OptionFunc) ([]*User, *Response, error) { + req, err := s.client.NewRequest("GET", "users", opt, options) + if err != nil { + return nil, nil, err + } + + var usr []*User + resp, err := s.client.Do(req, &usr) + if err != nil { + return nil, resp, err + } + + return usr, resp, err +} + +// GetUser gets a single user. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#single-user +func (s *UsersService) GetUser(user int, options ...OptionFunc) (*User, *Response, error) { + u := fmt.Sprintf("users/%d", user) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + usr := new(User) + resp, err := s.client.Do(req, usr) + if err != nil { + return nil, resp, err + } + + return usr, resp, err +} + +// CreateUserOptions represents the available CreateUser() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#user-creation +type CreateUserOptions struct { + Email *string `url:"email,omitempty" json:"email,omitempty"` + Password *string `url:"password,omitempty" json:"password,omitempty"` + ResetPassword *bool `url:"reset_password,omitempty" json:"reset_password,omitempty"` + Username *string `url:"username,omitempty" json:"username,omitempty"` + Name *string `url:"name,omitempty" json:"name,omitempty"` + Skype *string `url:"skype,omitempty" json:"skype,omitempty"` + Linkedin *string `url:"linkedin,omitempty" json:"linkedin,omitempty"` + Twitter *string `url:"twitter,omitempty" json:"twitter,omitempty"` + WebsiteURL *string `url:"website_url,omitempty" json:"website_url,omitempty"` + Organization *string `url:"organization,omitempty" json:"organization,omitempty"` + ProjectsLimit *int `url:"projects_limit,omitempty" json:"projects_limit,omitempty"` + ExternUID *string `url:"extern_uid,omitempty" json:"extern_uid,omitempty"` + Provider *string `url:"provider,omitempty" json:"provider,omitempty"` + Bio *string `url:"bio,omitempty" json:"bio,omitempty"` + Location *string `url:"location,omitempty" json:"location,omitempty"` + Admin *bool `url:"admin,omitempty" json:"admin,omitempty"` + CanCreateGroup *bool `url:"can_create_group,omitempty" json:"can_create_group,omitempty"` + SkipConfirmation *bool `url:"skip_confirmation,omitempty" json:"skip_confirmation,omitempty"` + External *bool `url:"external,omitempty" json:"external,omitempty"` +} + +// CreateUser creates a new user. Note only administrators can create new users. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#user-creation +func (s *UsersService) CreateUser(opt *CreateUserOptions, options ...OptionFunc) (*User, *Response, error) { + req, err := s.client.NewRequest("POST", "users", opt, options) + if err != nil { + return nil, nil, err + } + + usr := new(User) + resp, err := s.client.Do(req, usr) + if err != nil { + return nil, resp, err + } + + return usr, resp, err +} + +// ModifyUserOptions represents the available ModifyUser() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#user-modification +type ModifyUserOptions struct { + Email *string `url:"email,omitempty" json:"email,omitempty"` + Password *string `url:"password,omitempty" json:"password,omitempty"` + Username *string `url:"username,omitempty" json:"username,omitempty"` + Name *string `url:"name,omitempty" json:"name,omitempty"` + Skype *string `url:"skype,omitempty" json:"skype,omitempty"` + Linkedin *string `url:"linkedin,omitempty" json:"linkedin,omitempty"` + Twitter *string `url:"twitter,omitempty" json:"twitter,omitempty"` + WebsiteURL *string `url:"website_url,omitempty" json:"website_url,omitempty"` + Organization *string `url:"organization,omitempty" json:"organization,omitempty"` + ProjectsLimit *int `url:"projects_limit,omitempty" json:"projects_limit,omitempty"` + ExternUID *string `url:"extern_uid,omitempty" json:"extern_uid,omitempty"` + Provider *string `url:"provider,omitempty" json:"provider,omitempty"` + Bio *string `url:"bio,omitempty" json:"bio,omitempty"` + Location *string `url:"location,omitempty" json:"location,omitempty"` + Admin *bool `url:"admin,omitempty" json:"admin,omitempty"` + CanCreateGroup *bool `url:"can_create_group,omitempty" json:"can_create_group,omitempty"` + SkipReconfirmation *bool `url:"skip_reconfirmation,omitempty" json:"skip_reconfirmation,omitempty"` + External *bool `url:"external,omitempty" json:"external,omitempty"` +} + +// ModifyUser modifies an existing user. Only administrators can change attributes +// of a user. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#user-modification +func (s *UsersService) ModifyUser(user int, opt *ModifyUserOptions, options ...OptionFunc) (*User, *Response, error) { + u := fmt.Sprintf("users/%d", user) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + usr := new(User) + resp, err := s.client.Do(req, usr) + if err != nil { + return nil, resp, err + } + + return usr, resp, err +} + +// DeleteUser deletes a user. Available only for administrators. This is an +// idempotent function, calling this function for a non-existent user id still +// returns a status code 200 OK. The JSON response differs if the user was +// actually deleted or not. In the former the user is returned and in the +// latter not. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#user-deletion +func (s *UsersService) DeleteUser(user int, options ...OptionFunc) (*Response, error) { + u := fmt.Sprintf("users/%d", user) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// CurrentUser gets currently authenticated user. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#current-user +func (s *UsersService) CurrentUser(options ...OptionFunc) (*User, *Response, error) { + req, err := s.client.NewRequest("GET", "user", nil, options) + if err != nil { + return nil, nil, err + } + + usr := new(User) + resp, err := s.client.Do(req, usr) + if err != nil { + return nil, resp, err + } + + return usr, resp, err +} + +// SSHKey represents a SSH key. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#list-ssh-keys +type SSHKey struct { + ID int `json:"id"` + Title string `json:"title"` + Key string `json:"key"` + CreatedAt *time.Time `json:"created_at"` +} + +// ListSSHKeys gets a list of currently authenticated user's SSH keys. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#list-ssh-keys +func (s *UsersService) ListSSHKeys(options ...OptionFunc) ([]*SSHKey, *Response, error) { + req, err := s.client.NewRequest("GET", "user/keys", nil, options) + if err != nil { + return nil, nil, err + } + + var k []*SSHKey + resp, err := s.client.Do(req, &k) + if err != nil { + return nil, resp, err + } + + return k, resp, err +} + +// ListSSHKeysForUserOptions represents the available ListSSHKeysForUser() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/users.html#list-ssh-keys-for-user +type ListSSHKeysForUserOptions ListOptions + +// ListSSHKeysForUser gets a list of a specified user's SSH keys. Available +// only for admin +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/users.html#list-ssh-keys-for-user +func (s *UsersService) ListSSHKeysForUser(user int, opt *ListSSHKeysForUserOptions, options ...OptionFunc) ([]*SSHKey, *Response, error) { + u := fmt.Sprintf("users/%d/keys", user) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var k []*SSHKey + resp, err := s.client.Do(req, &k) + if err != nil { + return nil, resp, err + } + + return k, resp, err +} + +// GetSSHKey gets a single key. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#single-ssh-key +func (s *UsersService) GetSSHKey(key int, options ...OptionFunc) (*SSHKey, *Response, error) { + u := fmt.Sprintf("user/keys/%d", key) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + k := new(SSHKey) + resp, err := s.client.Do(req, k) + if err != nil { + return nil, resp, err + } + + return k, resp, err +} + +// AddSSHKeyOptions represents the available AddSSHKey() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#add-ssh-key +type AddSSHKeyOptions struct { + Title *string `url:"title,omitempty" json:"title,omitempty"` + Key *string `url:"key,omitempty" json:"key,omitempty"` +} + +// AddSSHKey creates a new key owned by the currently authenticated user. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#add-ssh-key +func (s *UsersService) AddSSHKey(opt *AddSSHKeyOptions, options ...OptionFunc) (*SSHKey, *Response, error) { + req, err := s.client.NewRequest("POST", "user/keys", opt, options) + if err != nil { + return nil, nil, err + } + + k := new(SSHKey) + resp, err := s.client.Do(req, k) + if err != nil { + return nil, resp, err + } + + return k, resp, err +} + +// AddSSHKeyForUser creates new key owned by specified user. Available only for +// admin. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#add-ssh-key-for-user +func (s *UsersService) AddSSHKeyForUser(user int, opt *AddSSHKeyOptions, options ...OptionFunc) (*SSHKey, *Response, error) { + u := fmt.Sprintf("users/%d/keys", user) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + k := new(SSHKey) + resp, err := s.client.Do(req, k) + if err != nil { + return nil, resp, err + } + + return k, resp, err +} + +// DeleteSSHKey deletes key owned by currently authenticated user. This is an +// idempotent function and calling it on a key that is already deleted or not +// available results in 200 OK. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/users.html#delete-ssh-key-for-current-owner +func (s *UsersService) DeleteSSHKey(key int, options ...OptionFunc) (*Response, error) { + u := fmt.Sprintf("user/keys/%d", key) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// DeleteSSHKeyForUser deletes key owned by a specified user. Available only +// for admin. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/users.html#delete-ssh-key-for-given-user +func (s *UsersService) DeleteSSHKeyForUser(user, key int, options ...OptionFunc) (*Response, error) { + u := fmt.Sprintf("users/%d/keys/%d", user, key) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// BlockUser blocks the specified user. Available only for admin. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#block-user +func (s *UsersService) BlockUser(user int, options ...OptionFunc) error { + u := fmt.Sprintf("users/%d/block", user) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return err + } + + switch resp.StatusCode { + case 201: + return nil + case 403: + return errors.New("Cannot block a user that is already blocked by LDAP synchronization") + case 404: + return errors.New("User does not exist") + default: + return fmt.Errorf("Received unexpected result code: %d", resp.StatusCode) + } +} + +// UnblockUser unblocks the specified user. Available only for admin. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#unblock-user +func (s *UsersService) UnblockUser(user int, options ...OptionFunc) error { + u := fmt.Sprintf("users/%d/unblock", user) + + req, err := s.client.NewRequest("POST", u, nil, options) + if err != nil { + return err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return err + } + + switch resp.StatusCode { + case 201: + return nil + case 403: + return errors.New("Cannot unblock a user that is blocked by LDAP synchronization") + case 404: + return errors.New("User does not exist") + default: + return fmt.Errorf("Received unexpected result code: %d", resp.StatusCode) + } +} + +// Email represents an Email. +// +// GitLab API docs: https://doc.gitlab.com/ce/api/users.html#list-emails +type Email struct { + ID int `json:"id"` + Email string `json:"email"` +} + +// ListEmails gets a list of currently authenticated user's Emails. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#list-emails +func (s *UsersService) ListEmails(options ...OptionFunc) ([]*Email, *Response, error) { + req, err := s.client.NewRequest("GET", "user/emails", nil, options) + if err != nil { + return nil, nil, err + } + + var e []*Email + resp, err := s.client.Do(req, &e) + if err != nil { + return nil, resp, err + } + + return e, resp, err +} + +// ListEmailsForUserOptions represents the available ListEmailsForUser() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/users.html#list-emails-for-user +type ListEmailsForUserOptions ListOptions + +// ListEmailsForUser gets a list of a specified user's Emails. Available +// only for admin +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/users.html#list-emails-for-user +func (s *UsersService) ListEmailsForUser(user int, opt *ListEmailsForUserOptions, options ...OptionFunc) ([]*Email, *Response, error) { + u := fmt.Sprintf("users/%d/emails", user) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var e []*Email + resp, err := s.client.Do(req, &e) + if err != nil { + return nil, resp, err + } + + return e, resp, err +} + +// GetEmail gets a single email. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#single-email +func (s *UsersService) GetEmail(email int, options ...OptionFunc) (*Email, *Response, error) { + u := fmt.Sprintf("user/emails/%d", email) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + e := new(Email) + resp, err := s.client.Do(req, e) + if err != nil { + return nil, resp, err + } + + return e, resp, err +} + +// AddEmailOptions represents the available AddEmail() options. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#add-email +type AddEmailOptions struct { + Email *string `url:"email,omitempty" json:"email,omitempty"` +} + +// AddEmail creates a new email owned by the currently authenticated user. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#add-email +func (s *UsersService) AddEmail(opt *AddEmailOptions, options ...OptionFunc) (*Email, *Response, error) { + req, err := s.client.NewRequest("POST", "user/emails", opt, options) + if err != nil { + return nil, nil, err + } + + e := new(Email) + resp, err := s.client.Do(req, e) + if err != nil { + return nil, resp, err + } + + return e, resp, err +} + +// AddEmailForUser creates new email owned by specified user. Available only for +// admin. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#add-email-for-user +func (s *UsersService) AddEmailForUser(user int, opt *AddEmailOptions, options ...OptionFunc) (*Email, *Response, error) { + u := fmt.Sprintf("users/%d/emails", user) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + e := new(Email) + resp, err := s.client.Do(req, e) + if err != nil { + return nil, resp, err + } + + return e, resp, err +} + +// DeleteEmail deletes email owned by currently authenticated user. This is an +// idempotent function and calling it on a key that is already deleted or not +// available results in 200 OK. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/users.html#delete-email-for-current-owner +func (s *UsersService) DeleteEmail(email int, options ...OptionFunc) (*Response, error) { + u := fmt.Sprintf("user/emails/%d", email) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// DeleteEmailForUser deletes email owned by a specified user. Available only +// for admin. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/users.html#delete-email-for-given-user +func (s *UsersService) DeleteEmailForUser(user, email int, options ...OptionFunc) (*Response, error) { + u := fmt.Sprintf("users/%d/emails/%d", user, email) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// ImpersonationToken represents an impersonation token. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/users.html#get-all-impersonation-tokens-of-a-user +type ImpersonationToken struct { + ID int `json:"id"` + Name string `json:"name"` + Active bool `json:"active"` + Token string `json:"token"` + Scopes []string `json:"scopes"` + Revoked bool `json:"revoked"` + CreatedAt *time.Time `json:"created_at"` + ExpiresAt *ISOTime `json:"expires_at"` +} + +// GetAllImpersonationTokensOptions represents the available +// GetAllImpersonationTokens() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/users.html#get-all-impersonation-tokens-of-a-user +type GetAllImpersonationTokensOptions struct { + ListOptions + State *string `url:"state,omitempty" json:"state,omitempty"` +} + +// GetAllImpersonationTokens retrieves all impersonation tokens of a user. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/users.html#get-all-impersonation-tokens-of-a-user +func (s *UsersService) GetAllImpersonationTokens(user int, opt *GetAllImpersonationTokensOptions, options ...OptionFunc) ([]*ImpersonationToken, *Response, error) { + u := fmt.Sprintf("users/%d/impersonation_tokens", user) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var ts []*ImpersonationToken + resp, err := s.client.Do(req, &ts) + if err != nil { + return nil, resp, err + } + + return ts, resp, err +} + +// GetImpersonationToken retrieves an impersonation token of a user. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/users.html#get-an-impersonation-token-of-a-user +func (s *UsersService) GetImpersonationToken(user, token int, options ...OptionFunc) (*ImpersonationToken, *Response, error) { + u := fmt.Sprintf("users/%d/impersonation_tokens/%d", user, token) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + t := new(ImpersonationToken) + resp, err := s.client.Do(req, &t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// CreateImpersonationTokenOptions represents the available +// CreateImpersonationToken() options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/users.html#create-an-impersonation-token +type CreateImpersonationTokenOptions struct { + Name *string `url:"name,omitempty" json:"name,omitempty"` + Scopes *[]string `url:"scopes,omitempty" json:"scopes,omitempty"` + ExpiresAt *time.Time `url:"expires_at,omitempty" json:"expires_at,omitempty"` +} + +// CreateImpersonationToken creates an impersonation token. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/users.html#create-an-impersonation-token +func (s *UsersService) CreateImpersonationToken(user int, opt *CreateImpersonationTokenOptions, options ...OptionFunc) (*ImpersonationToken, *Response, error) { + u := fmt.Sprintf("users/%d/impersonation_tokens", user) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + t := new(ImpersonationToken) + resp, err := s.client.Do(req, &t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// RevokeImpersonationToken revokes an impersonation token. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/users.html#revoke-an-impersonation-token +func (s *UsersService) RevokeImpersonationToken(user, token int, options ...OptionFunc) (*Response, error) { + u := fmt.Sprintf("users/%d/impersonation_tokens/%d", user, token) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// UserActivity represents an entry in the user/activities response +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/users.html#get-user-activities-admin-only +type UserActivity struct { + Username string `json:"username"` + LastActivityOn *ISOTime `json:"last_activity_on"` +} + +// GetUserActivitiesOptions represents the options for GetUserActivities +// +// GitLap API docs: +// https://docs.gitlab.com/ce/api/users.html#get-user-activities-admin-only +type GetUserActivitiesOptions struct { + From *ISOTime `url:"from,omitempty" json:"from,omitempty"` +} + +// GetUserActivities retrieves user activities (admin only) +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/users.html#get-user-activities-admin-only +func (s *UsersService) GetUserActivities(opt *GetUserActivitiesOptions, options ...OptionFunc) ([]*UserActivity, *Response, error) { + req, err := s.client.NewRequest("GET", "user/activities", opt, options) + if err != nil { + return nil, nil, err + } + + var t []*UserActivity + resp, err := s.client.Do(req, &t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/validate.go b/vendor/github.com/xanzy/go-gitlab/validate.go new file mode 100644 index 00000000..a88e1884 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/validate.go @@ -0,0 +1,40 @@ +package gitlab + +// ValidateService handles communication with the validation related methods of +// the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/lint.html +type ValidateService struct { + client *Client +} + +// LintResult represents the linting results. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/lint.html +type LintResult struct { + Status string `json:"status"` + Errors []string `json:"errors"` +} + +// Lint validates .gitlab-ci.yml content. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/lint.html +func (s *ValidateService) Lint(content string, options ...OptionFunc) (*LintResult, *Response, error) { + var opts struct { + Content string `url:"content,omitempty" json:"content,omitempty"` + } + opts.Content = content + + req, err := s.client.NewRequest("POST", "ci/lint", &opts, options) + if err != nil { + return nil, nil, err + } + + l := new(LintResult) + resp, err := s.client.Do(req, l) + if err != nil { + return nil, resp, err + } + + return l, resp, nil +} diff --git a/vendor/github.com/xanzy/go-gitlab/version.go b/vendor/github.com/xanzy/go-gitlab/version.go new file mode 100644 index 00000000..f1a3a7f5 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/version.go @@ -0,0 +1,56 @@ +// +// Copyright 2017, Andrea Funto' +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +// VersionService handles communication with the GitLab server instance to +// retrieve its version information via the GitLab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/version.md +type VersionService struct { + client *Client +} + +// Version represents a GitLab instance version. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/version.md +type Version struct { + Version string `json:"version"` + Revision string `json:"revision"` +} + +func (s Version) String() string { + return Stringify(s) +} + +// GetVersion gets a GitLab server instance version; it is only available to +// authenticated users. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/version.md +func (s *VersionService) GetVersion() (*Version, *Response, error) { + req, err := s.client.NewRequest("GET", "version", nil, nil) + if err != nil { + return nil, nil, err + } + + v := new(Version) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} diff --git a/vendor/github.com/xanzy/go-gitlab/wikis.go b/vendor/github.com/xanzy/go-gitlab/wikis.go new file mode 100644 index 00000000..72889851 --- /dev/null +++ b/vendor/github.com/xanzy/go-gitlab/wikis.go @@ -0,0 +1,204 @@ +// Copyright 2017, Stany MARCEL +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gitlab + +import ( + "fmt" + "net/url" +) + +// WikisService handles communication with the wikis related methods of +// the Gitlab API. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/wikis.html +type WikisService struct { + client *Client +} + +// WikiFormat represents the available wiki formats. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/wikis.html +type WikiFormat string + +// The available wiki formats. +const ( + WikiFormatMarkdown WikiFormat = "markdown" + WikiFormatRFoc WikiFormat = "rdoc" + WikiFormatASCIIDoc WikiFormat = "asciidoc" +) + +// Wiki represents a GitLab wiki. +// +// GitLab API docs: https://docs.gitlab.com/ce/api/wikis.html +type Wiki struct { + Content string `json:"content"` + Format WikiFormat `json:"format"` + Slug string `json:"slug"` + Title string `json:"title"` +} + +func (w Wiki) String() string { + return Stringify(w) +} + +// ListWikisOptions represents the available ListWikis options. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/wikis.html#list-wiki-pages +type ListWikisOptions struct { + WithContent *bool `url:"with_content,omitempty" json:"with_content,omitempty"` +} + +// ListWikis lists all pages of the wiki of the given project id. +// When with_content is set, it also returns the content of the pages. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/wikis.html#list-wiki-pages +func (s *WikisService) ListWikis(pid interface{}, opt *ListWikisOptions, options ...OptionFunc) ([]*Wiki, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/wikis", url.QueryEscape(project)) + + req, err := s.client.NewRequest("GET", u, opt, options) + if err != nil { + return nil, nil, err + } + + var w []*Wiki + resp, err := s.client.Do(req, &w) + if err != nil { + return nil, resp, err + } + + return w, resp, err +} + +// GetWikiPage gets a wiki page for a given project. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/wikis.html#get-a-wiki-page +func (s *WikisService) GetWikiPage(pid interface{}, slug string, options ...OptionFunc) (*Wiki, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/wikis/%s", url.QueryEscape(project), url.QueryEscape(slug)) + + req, err := s.client.NewRequest("GET", u, nil, options) + if err != nil { + return nil, nil, err + } + + var w *Wiki + resp, err := s.client.Do(req, &w) + if err != nil { + return nil, resp, err + } + + return w, resp, err +} + +// CreateWikiPageOptions represents options to CreateWikiPage. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/wikis.html#create-a-new-wiki-page +type CreateWikiPageOptions struct { + Content *string `url:"content" json:"content"` + Title *string `url:"title" json:"title"` + Format *string `url:"format,omitempty" json:"format,omitempty"` +} + +// CreateWikiPage creates a new wiki page for the given repository with +// the given title, slug, and content. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/wikis.html#create-a-new-wiki-page +func (s *WikisService) CreateWikiPage(pid interface{}, opt *CreateWikiPageOptions, options ...OptionFunc) (*Wiki, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/wikis", url.QueryEscape(project)) + + req, err := s.client.NewRequest("POST", u, opt, options) + if err != nil { + return nil, nil, err + } + + w := new(Wiki) + resp, err := s.client.Do(req, w) + if err != nil { + return nil, resp, err + } + + return w, resp, err +} + +// EditWikiPageOptions represents options to EditWikiPage. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/wikis.html#edit-an-existing-wiki-page +type EditWikiPageOptions struct { + Content *string `url:"content" json:"content"` + Title *string `url:"title" json:"title"` + Format *string `url:"format,omitempty" json:"format,omitempty"` +} + +// EditWikiPage Updates an existing wiki page. At least one parameter is +// required to update the wiki page. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/wikis.html#edit-an-existing-wiki-page +func (s *WikisService) EditWikiPage(pid interface{}, slug string, opt *EditWikiPageOptions, options ...OptionFunc) (*Wiki, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("projects/%s/wikis/%s", url.QueryEscape(project), url.QueryEscape(slug)) + + req, err := s.client.NewRequest("PUT", u, opt, options) + if err != nil { + return nil, nil, err + } + + w := new(Wiki) + resp, err := s.client.Do(req, w) + if err != nil { + return nil, resp, err + } + + return w, resp, err +} + +// DeleteWikiPage deletes a wiki page with a given slug. +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/wikis.html#delete-a-wiki-page +func (s *WikisService) DeleteWikiPage(pid interface{}, slug string, options ...OptionFunc) (*Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, err + } + u := fmt.Sprintf("projects/%s/wikis/%s", url.QueryEscape(project), url.QueryEscape(slug)) + + req, err := s.client.NewRequest("DELETE", u, nil, options) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/wtf.go b/wtf.go index c258177d..949d5ffc 100644 --- a/wtf.go +++ b/wtf.go @@ -18,6 +18,7 @@ import ( "github.com/senorprogrammer/wtf/gcal" "github.com/senorprogrammer/wtf/git" "github.com/senorprogrammer/wtf/github" + "github.com/senorprogrammer/wtf/gitlab" "github.com/senorprogrammer/wtf/help" "github.com/senorprogrammer/wtf/ipinfo" "github.com/senorprogrammer/wtf/jira" @@ -182,6 +183,8 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { Widgets = append(Widgets, git.NewWidget(app, pages)) case "github": Widgets = append(Widgets, github.NewWidget(app, pages)) + case "gitlab": + Widgets = append(Widgets, gitlab.NewWidget(app, pages)) case "ipinfo": Widgets = append(Widgets, ipinfo.NewWidget()) case "jira": @@ -222,6 +225,7 @@ func makeWidgets(app *tview.Application, pages *tview.Pages) { gcal.Config = Config git.Config = Config github.Config = Config + gitlab.Config = Config ipinfo.Config = Config jira.Config = Config newrelic.Config = Config From d38eb622afead5e9024b6c80afb8b2d44fb5280e Mon Sep 17 00:00:00 2001 From: Mark Old Date: Fri, 8 Jun 2018 13:32:04 -0700 Subject: [PATCH 02/23] add docs for gitlab --- _site/content/posts/modules/gitlab.md | 89 ++++++++ _site/static/imgs/modules/gitlab.png | Bin 0 -> 57615 bytes .../hyde-hyde/layouts/partials/sidebar.html | 1 + docs/404.html | 1 + docs/categories/index.html | 1 + docs/imgs/modules/gitlab.png | Bin 0 -> 57615 bytes docs/index.html | 1 + docs/index.xml | 17 +- .../posts/configuration/attributes/index.html | 1 + docs/posts/configuration/index.html | 1 + docs/posts/configuration/iterm2/index.html | 1 + docs/posts/glossary/index.html | 1 + docs/posts/index.html | 8 + docs/posts/index.xml | 17 +- docs/posts/installation/index.html | 1 + docs/posts/modules/bamboohr/index.html | 1 + docs/posts/modules/clocks/index.html | 1 + docs/posts/modules/cmdrunner/index.html | 1 + .../cryptocurrencies/bittrex/index.html | 1 + .../cryptocurrencies/cryptolive/index.html | 1 + docs/posts/modules/gcal/index.html | 1 + docs/posts/modules/git/index.html | 1 + docs/posts/modules/github/index.html | 1 + docs/posts/modules/gitlab/index.html | 203 ++++++++++++++++++ docs/posts/modules/index.html | 1 + docs/posts/modules/ipinfo/index.html | 1 + docs/posts/modules/jira/index.html | 1 + docs/posts/modules/newrelic/index.html | 1 + docs/posts/modules/opsgenie/index.html | 1 + docs/posts/modules/power/index.html | 1 + docs/posts/modules/prettyweather/index.html | 1 + docs/posts/modules/security/index.html | 1 + docs/posts/modules/textfile/index.html | 1 + docs/posts/modules/todo/index.html | 1 + docs/posts/modules/weather/index.html | 1 + docs/posts/overview/index.html | 1 + docs/sitemap.xml | 9 +- docs/tags/index.html | 1 + 38 files changed, 369 insertions(+), 4 deletions(-) create mode 100644 _site/content/posts/modules/gitlab.md create mode 100644 _site/static/imgs/modules/gitlab.png create mode 100644 docs/imgs/modules/gitlab.png create mode 100644 docs/posts/modules/gitlab/index.html diff --git a/_site/content/posts/modules/gitlab.md b/_site/content/posts/modules/gitlab.md new file mode 100644 index 00000000..917c9b02 --- /dev/null +++ b/_site/content/posts/modules/gitlab.md @@ -0,0 +1,89 @@ +--- +title: "Gitlab" +date: 2018-06-08T13:14:11-07:00 +draft: false +--- + +Displays information about your projects hosted on Gitlab: + + +#### Open Approval Requests + +All open merge requests that are requesting your approval. + +#### Open Merge Requests + +All open merge requests created by you. + +gitlab screenshot + +## Source Code + +```bash +wtf/gitlab/ +``` + +## Required ENV Variables + +Key: `WTF_GITLAB_TOKEN`
+Action: A Gitlab personal access token. Requires at least `api` access. + +## Keyboard Commands + +Key: `/`
+Action: Open/close the widget's help window. + +Key: `h`
+Action: Show the previous project. + +Key: `l`
+Action: Show the next project. + +Key: `←`
+Action: Show the previous project. + +Key: `→`
+Action: Show the next project. + +## Configuration + +```yaml +gitlab: + enabled: true + position: + top: 2 + left: 3 + height: 2 + width: 2 + refreshInterval: 300 + projects: + tasks: "gitlab-org/release" + gitlab-ce: "gitlab-org" + username: "senorprogrammer" +``` + +### Attributes + +`enabled`
+Determines whether or not this module is executed and if its data displayed onscreen.
+Values: `true`, `false`. + +`position`
+Defines where in the grid this module's widget will be displayed.
+ +`refreshInterval`
+How often, in seconds, this module will update its data.
+Values: A positive integer, `0..n`. + +`domain`
+_Optional_. Your Gitlab corporate domain.
+Values: A valid URI. + +`projects`
+A list of key/value pairs each describing a Gitlab project to fetch data +for.
+Key: The name of the project.
+Value: The namespace of the project. + +`username`
+Your Gitlab username. Used to figure out which requests require your approval diff --git a/_site/static/imgs/modules/gitlab.png b/_site/static/imgs/modules/gitlab.png new file mode 100644 index 0000000000000000000000000000000000000000..6e806a4183470ce449f0266c695da7f93d08ea64 GIT binary patch literal 57615 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 z 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 + diff --git a/docs/404.html b/docs/404.html index a6f51b08..1116a3f3 100644 --- a/docs/404.html +++ b/docs/404.html @@ -70,6 +70,7 @@ + diff --git a/docs/categories/index.html b/docs/categories/index.html index b52bb86e..5f82b96c 100644 --- a/docs/categories/index.html +++ b/docs/categories/index.html @@ -72,6 +72,7 @@ + diff --git a/docs/imgs/modules/gitlab.png b/docs/imgs/modules/gitlab.png new file mode 100644 index 0000000000000000000000000000000000000000..6e806a4183470ce449f0266c695da7f93d08ea64 GIT binary patch literal 57615 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 z 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 + diff --git a/docs/index.xml b/docs/index.xml index a86b5ce2..24c88a39 100644 --- a/docs/index.xml +++ b/docs/index.xml @@ -6,11 +6,26 @@ Recent content on WTF - A Terminal Dashboard Hugo -- gohugo.io en-us - Mon, 04 Jun 2018 20:06:40 -0700 + Fri, 08 Jun 2018 13:14:11 -0700 + + Gitlab + https://wtfutil.com/posts/modules/gitlab/ + Fri, 08 Jun 2018 13:14:11 -0700 + + https://wtfutil.com/posts/modules/gitlab/ + Displays information about your projects hosted on Gitlab: +Open Approval Requests All open merge requests that are requesting your approval. +Open Merge Requests All open merge requests created by you. +Source Code wtf/gitlab/ Required ENV Variables Key: WTF_GITLAB_TOKEN Action: A Gitlab personal access token. Requires at least api access. +Keyboard Commands Key: / Action: Open/close the widget&rsquo;s help window. +Key: h Action: Show the previous project. +Key: l Action: Show the next project. + + Bittrex https://wtfutil.com/posts/modules/cryptocurrencies/bittrex/ diff --git a/docs/posts/configuration/attributes/index.html b/docs/posts/configuration/attributes/index.html index a5354fce..a234b153 100644 --- a/docs/posts/configuration/attributes/index.html +++ b/docs/posts/configuration/attributes/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/configuration/index.html b/docs/posts/configuration/index.html index ec410d85..be506a80 100644 --- a/docs/posts/configuration/index.html +++ b/docs/posts/configuration/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/configuration/iterm2/index.html b/docs/posts/configuration/iterm2/index.html index dcb4d6c6..d26019da 100644 --- a/docs/posts/configuration/iterm2/index.html +++ b/docs/posts/configuration/iterm2/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/glossary/index.html b/docs/posts/glossary/index.html index 9a4f7187..ac4c33bc 100644 --- a/docs/posts/glossary/index.html +++ b/docs/posts/glossary/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/index.html b/docs/posts/index.html index ae588754..53ee2da5 100644 --- a/docs/posts/index.html +++ b/docs/posts/index.html @@ -72,6 +72,7 @@ + @@ -99,6 +100,13 @@

Posts

  • + + Gitlab + + + + +
  • Bittrex diff --git a/docs/posts/index.xml b/docs/posts/index.xml index 2dc5ba0f..5a805397 100644 --- a/docs/posts/index.xml +++ b/docs/posts/index.xml @@ -6,11 +6,26 @@ Recent content in Posts on WTF - A Terminal Dashboard Hugo -- gohugo.io en-us - Mon, 04 Jun 2018 20:06:40 -0700 + Fri, 08 Jun 2018 13:14:11 -0700 + + Gitlab + https://wtfutil.com/posts/modules/gitlab/ + Fri, 08 Jun 2018 13:14:11 -0700 + + https://wtfutil.com/posts/modules/gitlab/ + Displays information about your projects hosted on Gitlab: +Open Approval Requests All open merge requests that are requesting your approval. +Open Merge Requests All open merge requests created by you. +Source Code wtf/gitlab/ Required ENV Variables Key: WTF_GITLAB_TOKEN Action: A Gitlab personal access token. Requires at least api access. +Keyboard Commands Key: / Action: Open/close the widget&rsquo;s help window. +Key: h Action: Show the previous project. +Key: l Action: Show the next project. + + Bittrex https://wtfutil.com/posts/modules/cryptocurrencies/bittrex/ diff --git a/docs/posts/installation/index.html b/docs/posts/installation/index.html index 1237a416..d60d40fe 100644 --- a/docs/posts/installation/index.html +++ b/docs/posts/installation/index.html @@ -70,6 +70,7 @@
  • + diff --git a/docs/posts/modules/bamboohr/index.html b/docs/posts/modules/bamboohr/index.html index 240a8181..c899d2a2 100644 --- a/docs/posts/modules/bamboohr/index.html +++ b/docs/posts/modules/bamboohr/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/modules/clocks/index.html b/docs/posts/modules/clocks/index.html index 04333353..d72cba91 100644 --- a/docs/posts/modules/clocks/index.html +++ b/docs/posts/modules/clocks/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/modules/cmdrunner/index.html b/docs/posts/modules/cmdrunner/index.html index 1ec0afca..19ddb2e7 100644 --- a/docs/posts/modules/cmdrunner/index.html +++ b/docs/posts/modules/cmdrunner/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/modules/cryptocurrencies/bittrex/index.html b/docs/posts/modules/cryptocurrencies/bittrex/index.html index f9f033eb..e35a1e81 100644 --- a/docs/posts/modules/cryptocurrencies/bittrex/index.html +++ b/docs/posts/modules/cryptocurrencies/bittrex/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/modules/cryptocurrencies/cryptolive/index.html b/docs/posts/modules/cryptocurrencies/cryptolive/index.html index 832af988..82e90b66 100644 --- a/docs/posts/modules/cryptocurrencies/cryptolive/index.html +++ b/docs/posts/modules/cryptocurrencies/cryptolive/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/modules/gcal/index.html b/docs/posts/modules/gcal/index.html index 7e6f4a4b..9ce5f87b 100644 --- a/docs/posts/modules/gcal/index.html +++ b/docs/posts/modules/gcal/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/modules/git/index.html b/docs/posts/modules/git/index.html index e958c4f1..1b88ee7b 100644 --- a/docs/posts/modules/git/index.html +++ b/docs/posts/modules/git/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/modules/github/index.html b/docs/posts/modules/github/index.html index eb556adb..24f15d91 100644 --- a/docs/posts/modules/github/index.html +++ b/docs/posts/modules/github/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/modules/gitlab/index.html b/docs/posts/modules/gitlab/index.html new file mode 100644 index 00000000..dfec84ee --- /dev/null +++ b/docs/posts/modules/gitlab/index.html @@ -0,0 +1,203 @@ + + + + + + + + + + + + +Gitlab | WTF - A Terminal Dashboard + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +

    Gitlab

    + +
    + +
    + + + +

    Displays information about your projects hosted on Gitlab:

    + +

    Open Approval Requests

    + +

    All open merge requests that are requesting your approval.

    + +

    Open Merge Requests

    + +

    All open merge requests created by you.

    + +

    gitlab screenshot

    + +

    Source Code

    +
    wtf/gitlab/
    +

    Required ENV Variables

    + +

    Key: WTF_GITLAB_TOKEN
    +Action: A Gitlab personal access token. Requires at least api access.

    + +

    Keyboard Commands

    + +

    Key: /
    +Action: Open/close the widget’s help window.

    + +

    Key: h
    +Action: Show the previous project.

    + +

    Key: l
    +Action: Show the next project.

    + +

    Key:
    +Action: Show the previous project.

    + +

    Key:
    +Action: Show the next project.

    + +

    Configuration

    +
    gitlab:
    +  enabled: true
    +  position:
    +    top: 2
    +    left: 3
    +    height: 2
    +    width: 2
    +  refreshInterval: 300
    +  projects:
    +    tasks: "gitlab-org/release"
    +    gitlab-ce: "gitlab-org"
    +  username: "senorprogrammer"
    +

    Attributes

    + +

    enabled
    +Determines whether or not this module is executed and if its data displayed onscreen.
    +Values: true, false.

    + +

    position
    +Defines where in the grid this module’s widget will be displayed.

    + +

    refreshInterval
    +How often, in seconds, this module will update its data.
    +Values: A positive integer, 0..n.

    + +

    domain
    +Optional. Your Gitlab corporate domain.
    +Values: A valid URI.

    + +

    projects
    +A list of key/value pairs each describing a Gitlab project to fetch data +for.
    +Key: The name of the project.
    +Value: The namespace of the project.

    + +

    username
    +Your Gitlab username. Used to figure out which requests require your approval

    + +
    + + +
    + + + + diff --git a/docs/posts/modules/index.html b/docs/posts/modules/index.html index fad43ff2..7593f6c8 100644 --- a/docs/posts/modules/index.html +++ b/docs/posts/modules/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/modules/ipinfo/index.html b/docs/posts/modules/ipinfo/index.html index f3cd4865..75bddff5 100644 --- a/docs/posts/modules/ipinfo/index.html +++ b/docs/posts/modules/ipinfo/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/modules/jira/index.html b/docs/posts/modules/jira/index.html index 4a60fb7a..a0fe2031 100644 --- a/docs/posts/modules/jira/index.html +++ b/docs/posts/modules/jira/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/modules/newrelic/index.html b/docs/posts/modules/newrelic/index.html index eac8a8db..579f9e6b 100644 --- a/docs/posts/modules/newrelic/index.html +++ b/docs/posts/modules/newrelic/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/modules/opsgenie/index.html b/docs/posts/modules/opsgenie/index.html index dd2eef1a..4c84483b 100644 --- a/docs/posts/modules/opsgenie/index.html +++ b/docs/posts/modules/opsgenie/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/modules/power/index.html b/docs/posts/modules/power/index.html index 7cbb1d72..f26431e8 100644 --- a/docs/posts/modules/power/index.html +++ b/docs/posts/modules/power/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/modules/prettyweather/index.html b/docs/posts/modules/prettyweather/index.html index d6bd7e91..14d18b24 100644 --- a/docs/posts/modules/prettyweather/index.html +++ b/docs/posts/modules/prettyweather/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/modules/security/index.html b/docs/posts/modules/security/index.html index 04437186..f4d3a710 100644 --- a/docs/posts/modules/security/index.html +++ b/docs/posts/modules/security/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/modules/textfile/index.html b/docs/posts/modules/textfile/index.html index c18d9229..74d61c44 100644 --- a/docs/posts/modules/textfile/index.html +++ b/docs/posts/modules/textfile/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/modules/todo/index.html b/docs/posts/modules/todo/index.html index 9ffa4bc6..57e22e08 100644 --- a/docs/posts/modules/todo/index.html +++ b/docs/posts/modules/todo/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/modules/weather/index.html b/docs/posts/modules/weather/index.html index f22a2a21..d640e763 100644 --- a/docs/posts/modules/weather/index.html +++ b/docs/posts/modules/weather/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/posts/overview/index.html b/docs/posts/overview/index.html index beef7287..bc59f931 100644 --- a/docs/posts/overview/index.html +++ b/docs/posts/overview/index.html @@ -70,6 +70,7 @@ + diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 64670468..e4f727c5 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -2,6 +2,11 @@ + + https://wtfutil.com/posts/modules/gitlab/ + 2018-06-08T13:14:11-07:00 + + https://wtfutil.com/posts/modules/cryptocurrencies/bittrex/ 2018-06-04T20:06:40-07:00 @@ -134,7 +139,7 @@ https://wtfutil.com/posts/ - 2018-06-04T20:06:40-07:00 + 2018-06-08T13:14:11-07:00 0 @@ -145,7 +150,7 @@ https://wtfutil.com/ - 2018-06-04T20:06:40-07:00 + 2018-06-08T13:14:11-07:00 0 diff --git a/docs/tags/index.html b/docs/tags/index.html index 68425b68..b567b5ee 100644 --- a/docs/tags/index.html +++ b/docs/tags/index.html @@ -72,6 +72,7 @@ + From cd983a8e8fb26e908fc56e5dd354e848e1f4e7ba Mon Sep 17 00:00:00 2001 From: FengYa Date: Fri, 8 Jun 2018 13:44:38 +0800 Subject: [PATCH 03/23] create another module to use another ipinfo api --- ipinfohigherlimit/widget.go | 141 ++++++++++++++++++++++++++++++++++++ wtf.go | 4 + 2 files changed, 145 insertions(+) create mode 100644 ipinfohigherlimit/widget.go diff --git a/ipinfohigherlimit/widget.go b/ipinfohigherlimit/widget.go new file mode 100644 index 00000000..3fdabb3f --- /dev/null +++ b/ipinfohigherlimit/widget.go @@ -0,0 +1,141 @@ +package ipinfohigherlimit + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "text/template" + + "bytes" + + "github.com/olebedev/config" + "github.com/senorprogrammer/wtf/wtf" +) + +// Config is a pointer to the global config object +var Config *config.Config + +// Widget widget struct +type Widget struct { + wtf.TextWidget + result string + colors struct { + name, value string + } +} + +type ipinfo struct { + Query string `json:"query"` + ISP string `json:"isp"` + AS string `json:"as"` + City string `json:"city"` + Region string `json:"region"` + Country string `json:"country"` + CountryCode string `json:"countryCode"` + Latitude float64 `json:"lat"` + Longitude float64 `json:"lon"` + PostalCode string `json:"zip"` + Organization string `json:"org"` + Timezone string `json:"timezone"` +} + +// NewWidget constructor +func NewWidget() *Widget { + widget := Widget{ + TextWidget: wtf.NewTextWidget(" IPInfo ", "ipinfohigherlimit", false), + } + + widget.View.SetWrap(false) + + widget.config() + + return &widget +} + +// Refresh refresh the module +func (widget *Widget) Refresh() { + if widget.Disabled() { + return + } + + widget.UpdateRefreshedAt() + widget.ipinfo() + widget.View.Clear() + + widget.View.SetText(widget.result) +} + +//this method reads the config and calls ipinfo for ip information +func (widget *Widget) ipinfo() { + client := &http.Client{} + req, err := http.NewRequest("GET", "http://ip-api.com/json", nil) + if err != nil { + widget.result = fmt.Sprintf("%s", err.Error()) + return + } + req.Header.Set("User-Agent", "curl") + response, err := client.Do(req) + if err != nil { + widget.result = fmt.Sprintf("%s", err.Error()) + return + } + defer response.Body.Close() + if err != nil { + widget.result = fmt.Sprintf("%s", err.Error()) + return + } + var info ipinfo + err = json.NewDecoder(response.Body).Decode(&info) + if err != nil { + widget.result = fmt.Sprintf("%s", err.Error()) + return + } + + widget.setResult(&info) +} + +// read module configs +func (widget *Widget) config() { + nameColor, valueColor := Config.UString("wtf.mods.ipinfo.colors.name", "red"), Config.UString("wtf.mods.ipinfo.colors.value", "white") + widget.colors.name = nameColor + widget.colors.value = valueColor +} + +func (widget *Widget) setResult(info *ipinfo) { + resultTemplate, _ := template.New("ipinfo_result").Parse( + formatableText("IP Address", "Ip") + + formatableText("ISP", "ISP") + + formatableText("AS", "AS") + + formatableText("City", "City") + + formatableText("Region", "Region") + + formatableText("Country", "Country") + + formatableText("Coordinates", "Coordinates") + + formatableText("Postal Code", "PostalCode") + + formatableText("Organization", "Organization") + + formatableText("Timezone", "Timezone"), + ) + + resultBuffer := new(bytes.Buffer) + + resultTemplate.Execute(resultBuffer, map[string]string{ + "nameColor": widget.colors.name, + "valueColor": widget.colors.value, + "Ip": info.Query, + "ISP": info.ISP, + "AS": info.AS, + "City": info.City, + "Region": info.Region, + "Country": info.Country, + "Coordinates": strconv.FormatFloat(info.Latitude, 'f', 6, 64) + "," + strconv.FormatFloat(info.Longitude, 'f', 6, 64), + "PostalCode": info.PostalCode, + "Organization": info.Organization, + "Timezone": info.Timezone, + }) + + widget.result = resultBuffer.String() +} + +func formatableText(key, value string) string { + return fmt.Sprintf(" [{{.nameColor}}]%s: [{{.valueColor}}]{{.%s}}\n", key, value) +} diff --git a/wtf.go b/wtf.go index 2c801fa2..ac185b2a 100644 --- a/wtf.go +++ b/wtf.go @@ -22,6 +22,7 @@ import ( "github.com/senorprogrammer/wtf/gitlab" "github.com/senorprogrammer/wtf/help" "github.com/senorprogrammer/wtf/ipinfo" + "github.com/senorprogrammer/wtf/ipinfohigherlimit" "github.com/senorprogrammer/wtf/jira" "github.com/senorprogrammer/wtf/newrelic" "github.com/senorprogrammer/wtf/opsgenie" @@ -188,6 +189,8 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { Widgets = append(Widgets, gitlab.NewWidget(app, pages)) case "ipinfo": Widgets = append(Widgets, ipinfo.NewWidget()) + case "ipinfohigherlimit": + Widgets = append(Widgets, ipinfohigherlimit.NewWidget()) case "jira": Widgets = append(Widgets, jira.NewWidget()) case "newrelic": @@ -229,6 +232,7 @@ func makeWidgets(app *tview.Application, pages *tview.Pages) { github.Config = Config gitlab.Config = Config ipinfo.Config = Config + ipinfohigherlimit.Config = Config jira.Config = Config newrelic.Config = Config opsgenie.Config = Config From 8cf363690d6ba23039b0a15d0810d040d6e6f535 Mon Sep 17 00:00:00 2001 From: FengYa Date: Fri, 8 Jun 2018 14:22:01 +0800 Subject: [PATCH 04/23] delete the useless clear operation --- ipinfohigherlimit/widget.go | 1 - 1 file changed, 1 deletion(-) diff --git a/ipinfohigherlimit/widget.go b/ipinfohigherlimit/widget.go index 3fdabb3f..32ed40e2 100644 --- a/ipinfohigherlimit/widget.go +++ b/ipinfohigherlimit/widget.go @@ -61,7 +61,6 @@ func (widget *Widget) Refresh() { widget.UpdateRefreshedAt() widget.ipinfo() - widget.View.Clear() widget.View.SetText(widget.result) } From f542164dff85e24d0982d76237e63a7b8c5fafd1 Mon Sep 17 00:00:00 2001 From: FengYa Date: Fri, 8 Jun 2018 14:43:58 +0800 Subject: [PATCH 05/23] delete useless check code --- ipinfohigherlimit/widget.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ipinfohigherlimit/widget.go b/ipinfohigherlimit/widget.go index 32ed40e2..e0106a15 100644 --- a/ipinfohigherlimit/widget.go +++ b/ipinfohigherlimit/widget.go @@ -55,13 +55,8 @@ func NewWidget() *Widget { // Refresh refresh the module func (widget *Widget) Refresh() { - if widget.Disabled() { - return - } - widget.UpdateRefreshedAt() widget.ipinfo() - widget.View.SetText(widget.result) } From 0ec2259609edf5bc27bd719dbc32634077a9f2b6 Mon Sep 17 00:00:00 2001 From: FengYa Date: Fri, 8 Jun 2018 14:56:57 +0800 Subject: [PATCH 06/23] delete useless repeat code --- ipinfohigherlimit/widget.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ipinfohigherlimit/widget.go b/ipinfohigherlimit/widget.go index e0106a15..9b781d0d 100644 --- a/ipinfohigherlimit/widget.go +++ b/ipinfohigherlimit/widget.go @@ -75,10 +75,6 @@ func (widget *Widget) ipinfo() { return } defer response.Body.Close() - if err != nil { - widget.result = fmt.Sprintf("%s", err.Error()) - return - } var info ipinfo err = json.NewDecoder(response.Body).Decode(&info) if err != nil { From aace78285d3de9622c37662edb3c9d87ceed6888 Mon Sep 17 00:00:00 2001 From: Andrew_Zol Date: Fri, 8 Jun 2018 20:27:39 +0300 Subject: [PATCH 07/23] Added gspreadsheets module --- .gitignore | 1 + gspreadsheets/client.go | 145 ++++++++++++++++++++++++++++++++++++++++ gspreadsheets/widget.go | 56 ++++++++++++++++ wtf.go | 2 + 4 files changed, 204 insertions(+) create mode 100644 gspreadsheets/client.go create mode 100644 gspreadsheets/widget.go diff --git a/.gitignore b/.gitignore index 7048d6ec..2d038a3c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ # Misc .DS_Store gcal/client_secret.json +gspreadsheets/client_secret.json #intellij idea .idea/ diff --git a/gspreadsheets/client.go b/gspreadsheets/client.go new file mode 100644 index 00000000..1cf16321 --- /dev/null +++ b/gspreadsheets/client.go @@ -0,0 +1,145 @@ +/* +* This butt-ugly code is direct from Google itself +* https://developers.google.com/sheets/api/quickstart/go + */ + +package gspreadsheets + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "os" + "os/user" + "path/filepath" + "strings" + + "github.com/senorprogrammer/wtf/wtf" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" + sheets "google.golang.org/api/sheets/v4" +) + +/* -------------------- Exported Functions -------------------- */ + +func Fetch() ([]*sheets.ValueRange, error) { + ctx := context.Background() + + secretPath, _ := wtf.ExpandHomeDir(Config.UString("wtf.mods.gspreadsheets.secretFile")) + + b, err := ioutil.ReadFile(secretPath) + if err != nil { + log.Fatalf("Unable to read secretPath. %v", err) + return nil, err + } + + config, err := google.ConfigFromJSON(b, "https://www.googleapis.com/auth/spreadsheets.readonly") + + if err != nil { + log.Fatalf("Unable to get config from JSON. %v", err) + return nil, err + } + client := getClient(ctx, config) + + srv, err := sheets.New(client) + if err != nil { + log.Fatalf("Unable to get create server. %v", err) + return nil, err + } + + cells := wtf.ToStrs(Config.UList("wtf.mods.gspreadsheets.cells.addresses")) + documentId := Config.UString("wtf.mods.gspreadsheets.sheetId") + addresses := strings.Join(cells[:], ";") + + responses := make([]*sheets.ValueRange, len(cells)) + + for i := 0; i < len(cells); i++ { + resp, err := srv.Spreadsheets.Values.Get(documentId, cells[i]).Do() + if err != nil { + log.Fatalf("Error fetching cells %s", addresses) + return nil, err + } + responses[i] = resp + } + + return responses, err +} + +/* -------------------- Unexported Functions -------------------- */ + +// getClient uses a Context and Config to retrieve a Token +// then generate a Client. It returns the generated Client. +func getClient(ctx context.Context, config *oauth2.Config) *http.Client { + cacheFile, err := tokenCacheFile() + if err != nil { + log.Fatalf("Unable to get path to cached credential file. %v", err) + } + tok, err := tokenFromFile(cacheFile) + if err != nil { + tok = getTokenFromWeb(config) + saveToken(cacheFile, tok) + } + return config.Client(ctx, tok) +} + +// getTokenFromWeb uses Config to request a Token. +// It returns the retrieved Token. +func getTokenFromWeb(config *oauth2.Config) *oauth2.Token { + authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline) + fmt.Printf("Go to the following link in your browser then type the "+ + "authorization code: \n%v\n", authURL) + + var code string + if _, err := fmt.Scan(&code); err != nil { + log.Fatalf("Unable to read authorization code %v", err) + } + + tok, err := config.Exchange(oauth2.NoContext, code) + if err != nil { + log.Fatalf("Unable to retrieve token from web %v", err) + } + return tok +} + +// tokenCacheFile generates credential file path/filename. +// It returns the generated credential path/filename. +func tokenCacheFile() (string, error) { + usr, err := user.Current() + if err != nil { + return "", err + } + tokenCacheDir := filepath.Join(usr.HomeDir, ".credentials") + os.MkdirAll(tokenCacheDir, 0700) + return filepath.Join(tokenCacheDir, + url.QueryEscape("spreadsheets-go-quickstart.json")), err +} + +// tokenFromFile retrieves a Token from a given file path. +// It returns the retrieved Token and any read error encountered. +func tokenFromFile(file string) (*oauth2.Token, error) { + f, err := os.Open(file) + if err != nil { + return nil, err + } + t := &oauth2.Token{} + err = json.NewDecoder(f).Decode(t) + defer f.Close() + return t, err +} + +// saveToken uses a file path to create a file and store the +// token in it. +func saveToken(file string, token *oauth2.Token) { + fmt.Printf("Saving credential file to: %s\n", file) + f, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + log.Fatalf("Unable to cache oauth token: %v", err) + } + defer f.Close() + + json.NewEncoder(f).Encode(token) +} diff --git a/gspreadsheets/widget.go b/gspreadsheets/widget.go new file mode 100644 index 00000000..e754ce74 --- /dev/null +++ b/gspreadsheets/widget.go @@ -0,0 +1,56 @@ +package gspreadsheets + +import ( + "fmt" + + "github.com/senorprogrammer/wtf/wtf" + "github.com/olebedev/config" + sheets "google.golang.org/api/sheets/v4" +) + +// Config is a pointer to the global config object +var Config *config.Config + +type Widget struct { + wtf.TextWidget +} + +func NewWidget() *Widget { + widget := Widget{ + TextWidget: wtf.NewTextWidget(" Google Spreadsheets ", "gspreadsheets", false), + } + + return &widget +} + +/* -------------------- Exported Functions -------------------- */ + +func (widget *Widget) Refresh() { + if widget.Disabled() { + return + } + + cells, _ := Fetch() + + widget.UpdateRefreshedAt() + + widget.View.SetText(fmt.Sprintf("%s", widget.contentFrom(cells))) +} + +/* -------------------- Unexported Functions -------------------- */ + +func (widget *Widget) contentFrom(valueRanges []*sheets.ValueRange) string { + if valueRanges == nil { + return "error 1" + } + + valuesColor := Config.UString("wtf.mods.gspreadsheets.colors.values", "green") + res := "" + + cells := wtf.ToStrs(Config.UList("wtf.mods.gspreadsheets.cells.names")) + for i := 0; i < len(valueRanges); i++ { + res = res + fmt.Sprintf("%s\t[%s]%s\n", cells[i], valuesColor, valueRanges[i].Values[0][0]) + } + + return res +} diff --git a/wtf.go b/wtf.go index ac185b2a..dc681155 100644 --- a/wtf.go +++ b/wtf.go @@ -17,6 +17,7 @@ import ( "github.com/senorprogrammer/wtf/cryptoexchanges/bittrex" "github.com/senorprogrammer/wtf/cryptoexchanges/cryptolive" "github.com/senorprogrammer/wtf/gcal" + "github.com/senorprogrammer/wtf/gspreadsheets" "github.com/senorprogrammer/wtf/git" "github.com/senorprogrammer/wtf/github" "github.com/senorprogrammer/wtf/gitlab" @@ -228,6 +229,7 @@ func makeWidgets(app *tview.Application, pages *tview.Pages) { cmdrunner.Config = Config cryptolive.Config = Config gcal.Config = Config + gspreadsheets.Config = Config git.Config = Config github.Config = Config gitlab.Config = Config From 26290fedb5941623772c0237f925c8f4002068e6 Mon Sep 17 00:00:00 2001 From: Andrew_Zol Date: Fri, 8 Jun 2018 20:31:44 +0300 Subject: [PATCH 08/23] Added updated config. --- _sample_configs/complex_config.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/_sample_configs/complex_config.yml b/_sample_configs/complex_config.yml index c21bdb72..e07aa209 100644 --- a/_sample_configs/complex_config.yml +++ b/_sample_configs/complex_config.yml @@ -79,6 +79,25 @@ wtf: refreshInterval: 300 secretFile: "~/.wtf/gcal/client_secret.json" withLocation: true + gspreadsheets: + enabled: true + secretFile: "~/.wtf/gspreadsheets/client_secret.json" + refreshInterval: "300" + sheetId: "id_of_google_spreadsheet" + colors: + values: "green" + cells: + names: + - "Cell 1 name" + - "Cell 2 name" + addresses: + - "A1" + - "A2" + position: + top: 0 + left: 0 + width: 1 + height: 1 git: commitCount: 5 enabled: true From bf2a27dc799da7a2f1a0f287ce3e7bcb75cc887e Mon Sep 17 00:00:00 2001 From: Bryan Austin Date: Fri, 8 Jun 2018 12:18:27 -0700 Subject: [PATCH 09/23] Allow use of project list in Jira module config For my own use case (and anyone in a similar situation), the Jira module is more useful if I can specify a list of projects to display issues from, rather than no project (which selects all projects) or one specific project. New supported syntax: ` project: ["PROJA", "PROJB"]` If this is merged, documentation for the Jira module should be updated accordingly. (Sorry, I would have done this myself but I'm not sure what the right place is - when grepping I find multiple places in the repo containing documentation strings and I'm not sure what the "master" location is) Specifying a single project (or no project) is still supported - behavior shouldn't change for anyone who doesn't change their config. --- jira/client.go | 22 +++++++++++++++++++--- jira/widget.go | 20 +++++++++++++++++++- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/jira/client.go b/jira/client.go index b7903018..226dd14c 100644 --- a/jira/client.go +++ b/jira/client.go @@ -12,11 +12,12 @@ import ( "strings" ) -func IssuesFor(username string, project string, jql string) (*SearchResult, error) { +func IssuesFor(username string, projects []string, jql string) (*SearchResult, error) { query := []string{} - if project != "" { - query = append(query, buildJql("project", project)) + var projQuery = getProjectQuery(projects) + if projQuery != "" { + query = append(query, projQuery) } if username != "" { @@ -88,3 +89,18 @@ func parseJson(obj interface{}, text io.Reader) { } } } + +func getProjectQuery(projects []string) string { + singleEmptyProject := len(projects) == 1 && len(projects[0]) == 0 + if len(projects) == 0 || singleEmptyProject { + return "" + } else if len(projects) == 1 { + return buildJql("project", projects[0]) + } + + quoted := make([]string, len(projects)) + for i := range projects { + quoted[i] = fmt.Sprintf("\"%s\"", projects[i]) + } + return fmt.Sprintf("project in (%s)", strings.Join(quoted, ", ")) +} diff --git a/jira/widget.go b/jira/widget.go index 126c629d..6ca9670e 100644 --- a/jira/widget.go +++ b/jira/widget.go @@ -25,7 +25,7 @@ func NewWidget() *Widget { /* -------------------- Exported Functions -------------------- */ func (widget *Widget) Refresh() { - searchResult, err := IssuesFor(Config.UString("wtf.mods.jira.username"), Config.UString("wtf.mods.jira.project", ""), Config.UString("wtf.mods.jira.jql", "")) + searchResult, err := IssuesFor(Config.UString("wtf.mods.jira.username"), getProjects(), Config.UString("wtf.mods.jira.jql", "")) widget.UpdateRefreshedAt() @@ -81,3 +81,21 @@ func (widget *Widget) issueTypeColor(issue *Issue) string { return color } + +func getProjects() []string { + // see if project is set to a single string + configPath := "wtf.mods.jira.project" + singleProject, err := Config.String(configPath) + if err == nil { + return []string{singleProject} + } + // else, assume list + projList := Config.UList(configPath) + var ret []string + for _, proj := range projList { + if str, ok := proj.(string); ok { + ret = append(ret, str) + } + } + return ret +} From d6b500e3464e39960e10efd1e1c21c92f338894b Mon Sep 17 00:00:00 2001 From: Bryan Austin Date: Fri, 8 Jun 2018 14:44:45 -0700 Subject: [PATCH 10/23] Fix newline in git module repo names breaking display After setting up the git module with multiple repos and switching between them, I observed some graphical wonkiness in the display: https://i.imgur.com/R3e7eij.png After adding some log statements, I tracked it down to the `GitRepo.Repository` field having a newline in it after it's set from a command execution's stdout. This change strips the repository path of spaces when assigning to the `Repository` field, which fixes the display issues. --- git/git_repo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/git_repo.go b/git/git_repo.go index 3f657732..5d3ba090 100644 --- a/git/git_repo.go +++ b/git/git_repo.go @@ -22,7 +22,7 @@ func NewGitRepo(repoPath string) *GitRepo { repo.Branch = repo.branch() repo.ChangedFiles = repo.changedFiles() repo.Commits = repo.commits() - repo.Repository = repo.repository() + repo.Repository = strings.TrimSpace(repo.repository()) return &repo } From 1b00c032bccaa42f9a881ec333b4c563310cc5bf Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Fri, 8 Jun 2018 15:34:31 -0700 Subject: [PATCH 11/23] Make Bargraph widget work with new module lazy-loading --- wtf.go | 4 +++- wtf/bargraph.go | 4 ++++ wtf/text_widget.go | 8 ++++---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/wtf.go b/wtf.go index dc681155..a1368c59 100644 --- a/wtf.go +++ b/wtf.go @@ -172,6 +172,8 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { switch widgetName { case "bamboohr": Widgets = append(Widgets, bamboohr.NewWidget()) + case "bargraph": + Widgets = append(Widgets, bargraph.NewWidget()) case "bittrex": Widgets = append(Widgets, bittrex.NewWidget()) case "clocks": @@ -223,7 +225,7 @@ func makeWidgets(app *tview.Application, pages *tview.Pages) { // Always in alphabetical order bamboohr.Config = Config - bargraph.Config = Config + bargraph.Config = Config bittrex.Config = Config clocks.Config = Config cmdrunner.Config = Config diff --git a/wtf/bargraph.go b/wtf/bargraph.go index 7c7da2ac..977f526f 100644 --- a/wtf/bargraph.go +++ b/wtf/bargraph.go @@ -54,6 +54,10 @@ func (widget *BarGraph) BorderColor() string { return Config.UString("wtf.colors.border.normal", "gray") } +func (widget *BarGraph) Disable() { + widget.enabled = false +} + func (widget *BarGraph) Disabled() bool { return !widget.Enabled() } diff --git a/wtf/text_widget.go b/wtf/text_widget.go index b62868ef..c1bef129 100644 --- a/wtf/text_widget.go +++ b/wtf/text_widget.go @@ -53,6 +53,10 @@ func (widget *TextWidget) BorderColor() string { return Config.UString("wtf.colors.border.normal", "gray") } +func (widget *TextWidget) Disable() { + widget.enabled = false +} + func (widget *TextWidget) Disabled() bool { return !widget.Enabled() } @@ -61,10 +65,6 @@ func (widget *TextWidget) Enabled() bool { return widget.enabled } -func (widget *TextWidget) Disable() { - widget.enabled = false -} - func (widget *TextWidget) Focusable() bool { return widget.enabled && widget.focusable } From 5ee5557878dd2c2f1169a111a25381e5fadf148c Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Fri, 8 Jun 2018 15:57:22 -0700 Subject: [PATCH 12/23] Make Google Spreadsheet widget work with new module lazy-loading --- gspreadsheets/widget.go | 6 +----- wtf.go | 6 ++++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/gspreadsheets/widget.go b/gspreadsheets/widget.go index e754ce74..680869cf 100644 --- a/gspreadsheets/widget.go +++ b/gspreadsheets/widget.go @@ -3,8 +3,8 @@ package gspreadsheets import ( "fmt" - "github.com/senorprogrammer/wtf/wtf" "github.com/olebedev/config" + "github.com/senorprogrammer/wtf/wtf" sheets "google.golang.org/api/sheets/v4" ) @@ -26,10 +26,6 @@ func NewWidget() *Widget { /* -------------------- Exported Functions -------------------- */ func (widget *Widget) Refresh() { - if widget.Disabled() { - return - } - cells, _ := Fetch() widget.UpdateRefreshedAt() diff --git a/wtf.go b/wtf.go index a1368c59..c788f38a 100644 --- a/wtf.go +++ b/wtf.go @@ -17,10 +17,10 @@ import ( "github.com/senorprogrammer/wtf/cryptoexchanges/bittrex" "github.com/senorprogrammer/wtf/cryptoexchanges/cryptolive" "github.com/senorprogrammer/wtf/gcal" - "github.com/senorprogrammer/wtf/gspreadsheets" "github.com/senorprogrammer/wtf/git" "github.com/senorprogrammer/wtf/github" "github.com/senorprogrammer/wtf/gitlab" + "github.com/senorprogrammer/wtf/gspreadsheets" "github.com/senorprogrammer/wtf/help" "github.com/senorprogrammer/wtf/ipinfo" "github.com/senorprogrammer/wtf/ipinfohigherlimit" @@ -190,6 +190,8 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { Widgets = append(Widgets, github.NewWidget(app, pages)) case "gitlab": Widgets = append(Widgets, gitlab.NewWidget(app, pages)) + case "gspreadsheets": + Widgets = append(Widgets, gspreadsheets.NewWidget()) case "ipinfo": Widgets = append(Widgets, ipinfo.NewWidget()) case "ipinfohigherlimit": @@ -231,10 +233,10 @@ func makeWidgets(app *tview.Application, pages *tview.Pages) { cmdrunner.Config = Config cryptolive.Config = Config gcal.Config = Config - gspreadsheets.Config = Config git.Config = Config github.Config = Config gitlab.Config = Config + gspreadsheets.Config = Config ipinfo.Config = Config ipinfohigherlimit.Config = Config jira.Config = Config From 9412907a34c336960e3811e7e520fe53b5192fa3 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Fri, 8 Jun 2018 16:15:25 -0700 Subject: [PATCH 13/23] Remove experimental tag from PrettyWeather --- README.md | 19 ------------------- _site/content/posts/modules/prettyweather.md | 2 -- _site/themes/hyde-hyde/layouts/index.html | 6 +++--- .../hyde-hyde/layouts/partials/sidebar.html | 2 +- docs/404.html | 2 +- docs/categories/index.html | 2 +- docs/index.html | 8 ++++---- docs/index.xml | 3 +-- .../posts/configuration/attributes/index.html | 2 +- docs/posts/configuration/index.html | 2 +- docs/posts/configuration/iterm2/index.html | 2 +- docs/posts/glossary/index.html | 2 +- docs/posts/index.html | 2 +- docs/posts/index.xml | 3 +-- docs/posts/installation/index.html | 2 +- docs/posts/modules/bamboohr/index.html | 2 +- docs/posts/modules/clocks/index.html | 2 +- docs/posts/modules/cmdrunner/index.html | 2 +- .../cryptocurrencies/bittrex/index.html | 2 +- .../cryptocurrencies/cryptolive/index.html | 2 +- docs/posts/modules/gcal/index.html | 2 +- docs/posts/modules/git/index.html | 2 +- docs/posts/modules/github/index.html | 2 +- docs/posts/modules/index.html | 2 +- docs/posts/modules/ipinfo/index.html | 2 +- docs/posts/modules/jira/index.html | 2 +- docs/posts/modules/newrelic/index.html | 2 +- docs/posts/modules/opsgenie/index.html | 2 +- docs/posts/modules/power/index.html | 2 +- docs/posts/modules/prettyweather/index.html | 4 +--- docs/posts/modules/security/index.html | 2 +- docs/posts/modules/textfile/index.html | 2 +- docs/posts/modules/todo/index.html | 2 +- docs/posts/modules/weather/index.html | 2 +- docs/posts/overview/index.html | 2 +- docs/tags/index.html | 2 +- 36 files changed, 39 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index 705c2be5..4fb8b7c7 100644 --- a/README.md +++ b/README.md @@ -43,25 +43,6 @@ documentation. Here's some short-cuts: * [Configuration](http://wtfutil.com/posts/configuration/) * [Module Documentation](http://wtfutil.com/posts/modules/) -And a "probably up-to-date" list of currently-implemented modules: - -* [BambooHR](http://wtfutil.com/posts/modules/bamboohr/) -* [World Clocks](http://wtfutil.com/posts/modules/clocks/) -* [Command Runner](http://wtfutil.com/posts/modules/cmdrunner/) -* [Google Calendar](http://wtfutil.com/posts/modules/gcal/) -* [Git](http://wtfutil.com/posts/modules/git/) -* [GitHub](http://wtfutil.com/posts/modules/github/) -* [IPInfo](http://wtfutil.com/posts/modules/ipinfo/) -* [Jira](http://wtfutil.com/posts/modules/jira/) -* [New Relic](http://wtfutil.com/posts/modules/newrelic/) -* [OpsGenie](http://wtfutil.com/posts/modules/opsgenie) -* [Power](http://wtfutil.com/posts/modules/power/) -* [PrettyWeather](http://wtfutil.com/posts/modules/prettyweather/)* -* [Security](http://wtfutil.com/posts/modules/security/) -* [Textfile](http://wtfutil.com/posts/modules/textfile/) -* [Todo List](http://wtfutil.com/posts/modules/todo/) -* [Weather](http://wtfutil.com/posts/modules/weather/) - *experimental ## Contributing diff --git a/_site/content/posts/modules/prettyweather.md b/_site/content/posts/modules/prettyweather.md index 1f748a32..66a78c1e 100644 --- a/_site/content/posts/modules/prettyweather.md +++ b/_site/content/posts/modules/prettyweather.md @@ -4,8 +4,6 @@ date: 2018-06-02T05:32:04-07:00 draft: false --- -**🔬 Experimental** - Displays weather information as ASCII art from [Wttr.in](http://wttr.in). diff --git a/_site/themes/hyde-hyde/layouts/index.html b/_site/themes/hyde-hyde/layouts/index.html index 888abc24..d6fed31f 100644 --- a/_site/themes/hyde-hyde/layouts/index.html +++ b/_site/themes/hyde-hyde/layouts/index.html @@ -15,7 +15,7 @@

    - Keep an eye on your OpsGenie schedules, Google Calendar, Git + Keep an eye on your OpsGenie schedules, Google Calendar, Git and Github repositories, and New Relic deployments.

    @@ -28,8 +28,8 @@

    Download Latest - - On Github + Source on Github + Chat on Gitter

    diff --git a/_site/themes/hyde-hyde/layouts/partials/sidebar.html b/_site/themes/hyde-hyde/layouts/partials/sidebar.html index 66b1ebd0..60f89ff6 100644 --- a/_site/themes/hyde-hyde/layouts/partials/sidebar.html +++ b/_site/themes/hyde-hyde/layouts/partials/sidebar.html @@ -36,7 +36,7 @@

    - + diff --git a/docs/404.html b/docs/404.html index 1116a3f3..a2bb3161 100644 --- a/docs/404.html +++ b/docs/404.html @@ -77,7 +77,7 @@ - + diff --git a/docs/categories/index.html b/docs/categories/index.html index 5f82b96c..e3148cab 100644 --- a/docs/categories/index.html +++ b/docs/categories/index.html @@ -79,7 +79,7 @@ - + diff --git a/docs/index.html b/docs/index.html index 9157c4ab..469e31cc 100644 --- a/docs/index.html +++ b/docs/index.html @@ -78,7 +78,7 @@ - + @@ -108,7 +108,7 @@

    - Keep an eye on your OpsGenie schedules, Google Calendar, Git + Keep an eye on your OpsGenie schedules, Google Calendar, Git and Github repositories, and New Relic deployments.

    @@ -121,8 +121,8 @@

    Download Latest - - On Github + Source on Github + Chat on Gitter

    diff --git a/docs/index.xml b/docs/index.xml index 24c88a39..783fffd9 100644 --- a/docs/index.xml +++ b/docs/index.xml @@ -68,8 +68,7 @@ position Defines where in the grid this module&rsquo;s widget will be displa Sat, 02 Jun 2018 05:32:04 -0700 https://wtfutil.com/posts/modules/prettyweather/ - 🔬 Experimental -Displays weather information as ASCII art from Wttr.in. + Displays weather information as ASCII art from Wttr.in. Source Code wtf/prettyweather/ Required ENV Variables None. Keyboard Commands None. Configuration prettyweather:enabled:truecity:&#34;tehran&#34;position:top:3left:5height:1width:1refreshInterval:300unit:&#34;c&#34;view:0 Attributes city Optional. It will grab the current location from your IP address if omitted. diff --git a/docs/posts/configuration/attributes/index.html b/docs/posts/configuration/attributes/index.html index a234b153..17bc4a6f 100644 --- a/docs/posts/configuration/attributes/index.html +++ b/docs/posts/configuration/attributes/index.html @@ -77,7 +77,7 @@

    - + diff --git a/docs/posts/configuration/index.html b/docs/posts/configuration/index.html index be506a80..4b4b73a5 100644 --- a/docs/posts/configuration/index.html +++ b/docs/posts/configuration/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/configuration/iterm2/index.html b/docs/posts/configuration/iterm2/index.html index d26019da..7577d772 100644 --- a/docs/posts/configuration/iterm2/index.html +++ b/docs/posts/configuration/iterm2/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/glossary/index.html b/docs/posts/glossary/index.html index ac4c33bc..73db04fc 100644 --- a/docs/posts/glossary/index.html +++ b/docs/posts/glossary/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/index.html b/docs/posts/index.html index 53ee2da5..f6dae74d 100644 --- a/docs/posts/index.html +++ b/docs/posts/index.html @@ -79,7 +79,7 @@ - + diff --git a/docs/posts/index.xml b/docs/posts/index.xml index 5a805397..3238dfed 100644 --- a/docs/posts/index.xml +++ b/docs/posts/index.xml @@ -68,8 +68,7 @@ position Defines where in the grid this module&rsquo;s widget will be displa Sat, 02 Jun 2018 05:32:04 -0700 https://wtfutil.com/posts/modules/prettyweather/ - 🔬 Experimental -Displays weather information as ASCII art from Wttr.in. + Displays weather information as ASCII art from Wttr.in. Source Code wtf/prettyweather/ Required ENV Variables None. Keyboard Commands None. Configuration prettyweather:enabled:truecity:&#34;tehran&#34;position:top:3left:5height:1width:1refreshInterval:300unit:&#34;c&#34;view:0 Attributes city Optional. It will grab the current location from your IP address if omitted. diff --git a/docs/posts/installation/index.html b/docs/posts/installation/index.html index d60d40fe..6dedd96c 100644 --- a/docs/posts/installation/index.html +++ b/docs/posts/installation/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/modules/bamboohr/index.html b/docs/posts/modules/bamboohr/index.html index c899d2a2..d84ddbd7 100644 --- a/docs/posts/modules/bamboohr/index.html +++ b/docs/posts/modules/bamboohr/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/modules/clocks/index.html b/docs/posts/modules/clocks/index.html index d72cba91..b751b8f2 100644 --- a/docs/posts/modules/clocks/index.html +++ b/docs/posts/modules/clocks/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/modules/cmdrunner/index.html b/docs/posts/modules/cmdrunner/index.html index 19ddb2e7..19bfb61e 100644 --- a/docs/posts/modules/cmdrunner/index.html +++ b/docs/posts/modules/cmdrunner/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/modules/cryptocurrencies/bittrex/index.html b/docs/posts/modules/cryptocurrencies/bittrex/index.html index e35a1e81..4443b0b4 100644 --- a/docs/posts/modules/cryptocurrencies/bittrex/index.html +++ b/docs/posts/modules/cryptocurrencies/bittrex/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/modules/cryptocurrencies/cryptolive/index.html b/docs/posts/modules/cryptocurrencies/cryptolive/index.html index 82e90b66..674316e9 100644 --- a/docs/posts/modules/cryptocurrencies/cryptolive/index.html +++ b/docs/posts/modules/cryptocurrencies/cryptolive/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/modules/gcal/index.html b/docs/posts/modules/gcal/index.html index 9ce5f87b..4612c260 100644 --- a/docs/posts/modules/gcal/index.html +++ b/docs/posts/modules/gcal/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/modules/git/index.html b/docs/posts/modules/git/index.html index 1b88ee7b..ece791e6 100644 --- a/docs/posts/modules/git/index.html +++ b/docs/posts/modules/git/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/modules/github/index.html b/docs/posts/modules/github/index.html index 24f15d91..2c309cd9 100644 --- a/docs/posts/modules/github/index.html +++ b/docs/posts/modules/github/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/modules/index.html b/docs/posts/modules/index.html index 7593f6c8..8a68c69e 100644 --- a/docs/posts/modules/index.html +++ b/docs/posts/modules/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/modules/ipinfo/index.html b/docs/posts/modules/ipinfo/index.html index 75bddff5..4b66bf92 100644 --- a/docs/posts/modules/ipinfo/index.html +++ b/docs/posts/modules/ipinfo/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/modules/jira/index.html b/docs/posts/modules/jira/index.html index a0fe2031..4b729d9a 100644 --- a/docs/posts/modules/jira/index.html +++ b/docs/posts/modules/jira/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/modules/newrelic/index.html b/docs/posts/modules/newrelic/index.html index 579f9e6b..40c10f78 100644 --- a/docs/posts/modules/newrelic/index.html +++ b/docs/posts/modules/newrelic/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/modules/opsgenie/index.html b/docs/posts/modules/opsgenie/index.html index 4c84483b..5aeade06 100644 --- a/docs/posts/modules/opsgenie/index.html +++ b/docs/posts/modules/opsgenie/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/modules/power/index.html b/docs/posts/modules/power/index.html index f26431e8..2a6b0c2d 100644 --- a/docs/posts/modules/power/index.html +++ b/docs/posts/modules/power/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/modules/prettyweather/index.html b/docs/posts/modules/prettyweather/index.html index 14d18b24..07437487 100644 --- a/docs/posts/modules/prettyweather/index.html +++ b/docs/posts/modules/prettyweather/index.html @@ -77,7 +77,7 @@ - + @@ -114,8 +114,6 @@ -

    🔬 Experimental

    -

    Displays weather information as ASCII art from Wttr.in.

    diff --git a/docs/posts/modules/security/index.html b/docs/posts/modules/security/index.html index f4d3a710..c4832a90 100644 --- a/docs/posts/modules/security/index.html +++ b/docs/posts/modules/security/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/modules/textfile/index.html b/docs/posts/modules/textfile/index.html index 74d61c44..00536472 100644 --- a/docs/posts/modules/textfile/index.html +++ b/docs/posts/modules/textfile/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/modules/todo/index.html b/docs/posts/modules/todo/index.html index 57e22e08..7cfb10df 100644 --- a/docs/posts/modules/todo/index.html +++ b/docs/posts/modules/todo/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/modules/weather/index.html b/docs/posts/modules/weather/index.html index d640e763..0378a5f1 100644 --- a/docs/posts/modules/weather/index.html +++ b/docs/posts/modules/weather/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/posts/overview/index.html b/docs/posts/overview/index.html index bc59f931..564adecb 100644 --- a/docs/posts/overview/index.html +++ b/docs/posts/overview/index.html @@ -77,7 +77,7 @@ - + diff --git a/docs/tags/index.html b/docs/tags/index.html index b567b5ee..3a490f37 100644 --- a/docs/tags/index.html +++ b/docs/tags/index.html @@ -79,7 +79,7 @@ - + From e228156a2649d12f247d8d6a91b51ae7e7b006e7 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Fri, 8 Jun 2018 16:26:51 -0700 Subject: [PATCH 14/23] Remove experimental tag from README --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index 4fb8b7c7..d9a53bd7 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@

    -

    - +

    A personal terminal-based dashboard utility, designed for @@ -43,8 +42,6 @@ documentation. Here's some short-cuts: * [Configuration](http://wtfutil.com/posts/configuration/) * [Module Documentation](http://wtfutil.com/posts/modules/) -*experimental - ## Contributing Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests. From e1edb929e946c39ecfdd2b48c5e7269fcb742288 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Sat, 9 Jun 2018 03:58:45 -0700 Subject: [PATCH 15/23] Add cfg/ as a top-level package concept --- {wtf => cfg}/config_files.go | 9 +++++---- todo/widget.go | 7 ++++--- wtf.go | 7 ++++--- 3 files changed, 13 insertions(+), 10 deletions(-) rename {wtf => cfg}/config_files.go (94%) diff --git a/wtf/config_files.go b/cfg/config_files.go similarity index 94% rename from wtf/config_files.go rename to cfg/config_files.go index ff57654d..85e848e8 100644 --- a/wtf/config_files.go +++ b/cfg/config_files.go @@ -1,4 +1,4 @@ -package wtf +package cfg import ( "fmt" @@ -6,10 +6,11 @@ import ( "os" "github.com/olebedev/config" + "github.com/senorprogrammer/wtf/wtf" ) func ConfigDir() (string, error) { - configDir, err := ExpandHomeDir("~/.wtf/") + configDir, err := wtf.ExpandHomeDir("~/.wtf/") if err != nil { return "", err } @@ -59,7 +60,7 @@ func CreateFile(fileName string) (string, error) { // LoadConfigFile loads the config.yml file to configure the app func LoadConfigFile(filePath string) *config.Config { - absPath, _ := ExpandHomeDir(filePath) + absPath, _ := wtf.ExpandHomeDir(filePath) cfg, err := config.ParseYamlFile(absPath) if err != nil { @@ -79,7 +80,7 @@ func ReadConfigFile(fileName string) (string, error) { filePath := fmt.Sprintf("%s/%s", configDir, fileName) - fileData, err := ReadFileBytes(filePath) + fileData, err := wtf.ReadFileBytes(filePath) if err != nil { return "", err } diff --git a/todo/widget.go b/todo/widget.go index 00dcb984..f529f53e 100644 --- a/todo/widget.go +++ b/todo/widget.go @@ -7,6 +7,7 @@ import ( "github.com/gdamore/tcell" "github.com/olebedev/config" "github.com/rivo/tview" + "github.com/senorprogrammer/wtf/cfg" "github.com/senorprogrammer/wtf/wtf" "gopkg.in/yaml.v2" ) @@ -95,7 +96,7 @@ func (widget *Widget) editItem() { } func (widget *Widget) init() { - _, err := wtf.CreateFile(widget.filePath) + _, err := cfg.CreateFile(widget.filePath) if err != nil { panic(err) } @@ -177,7 +178,7 @@ func (widget *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey { // Loads the todo list from Yaml file func (widget *Widget) load() { - confDir, _ := wtf.ConfigDir() + confDir, _ := cfg.ConfigDir() filePath := fmt.Sprintf("%s/%s", confDir, widget.filePath) fileData, _ := wtf.ReadFileBytes(filePath) @@ -203,7 +204,7 @@ func (widget *Widget) newItem() { // persist writes the todo list to Yaml file func (widget *Widget) persist() { - confDir, _ := wtf.ConfigDir() + confDir, _ := cfg.ConfigDir() filePath := fmt.Sprintf("%s/%s", confDir, widget.filePath) fileData, _ := yaml.Marshal(&widget.list) diff --git a/wtf.go b/wtf.go index c788f38a..563d748b 100644 --- a/wtf.go +++ b/wtf.go @@ -12,6 +12,7 @@ import ( "github.com/rivo/tview" "github.com/senorprogrammer/wtf/bamboohr" "github.com/senorprogrammer/wtf/bargraph" + "github.com/senorprogrammer/wtf/cfg" "github.com/senorprogrammer/wtf/clocks" "github.com/senorprogrammer/wtf/cmdrunner" "github.com/senorprogrammer/wtf/cryptoexchanges/bittrex" @@ -268,7 +269,7 @@ func makeWidgets(app *tview.Application, pages *tview.Pages) { } func loadConfig(configFlag string) { - Config = wtf.LoadConfigFile(configFlag) + Config = cfg.LoadConfigFile(configFlag) } func main() { @@ -285,8 +286,8 @@ func main() { // Responsible for creating the configuration directory and default // configuration file if they don't already exist - wtf.CreateConfigDir() - wtf.WriteConfigFile() + cfg.CreateConfigDir() + cfg.WriteConfigFile() loadConfig(cmdFlags.Config) os.Setenv("TERM", Config.UString("wtf.term", os.Getenv("TERM"))) From 6aec41df53feef978cc9a9c0373e623d7691c453 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Sat, 9 Jun 2018 04:07:01 -0700 Subject: [PATCH 16/23] Basic Weather widget API key validation --- weather/display.go | 5 +++++ weather/widget.go | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/weather/display.go b/weather/display.go index 1b237789..9acd9e3d 100644 --- a/weather/display.go +++ b/weather/display.go @@ -11,6 +11,11 @@ import ( func (widget *Widget) display() { widget.View.Clear() + if widget.apiKeyValid() == false { + fmt.Fprintf(widget.View, "%s", " Environment variable WTF_OWM_API_KEY is not set") + return + } + cityData := widget.currentData() if cityData == nil { fmt.Fprintf(widget.View, "%s", " Weather data is unavailable (1)") diff --git a/weather/widget.go b/weather/widget.go index 992c7207..fa92b60d 100644 --- a/weather/widget.go +++ b/weather/widget.go @@ -104,6 +104,18 @@ func (widget *Widget) Prev() { /* -------------------- Unexported Functions -------------------- */ +func (widget *Widget) apiKeyValid() bool { + if widget.APIKey == "" { + return false + } + + if len(widget.APIKey) != 32 { + return false + } + + return true +} + func (widget *Widget) currentData() *owm.CurrentWeatherData { if len(widget.Data) == 0 { return nil From 9cc44a97c2041f7a5ae0459a5493542f1f57b335 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Sat, 9 Jun 2018 04:09:04 -0700 Subject: [PATCH 17/23] Don't crash if the Weather API is missing or invalid --- weather/widget.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/weather/widget.go b/weather/widget.go index fa92b60d..8a1176cb 100644 --- a/weather/widget.go +++ b/weather/widget.go @@ -74,7 +74,9 @@ func (widget *Widget) Fetch(cityIDs []int) []*owm.CurrentWeatherData { // Refresh fetches new data from the OpenWeatherMap API and loads the new data into the. // widget's view for rendering func (widget *Widget) Refresh() { - widget.Data = widget.Fetch(wtf.ToInts(Config.UList("wtf.mods.weather.cityids", widget.defaultCityCodes()))) + if widget.apiKeyValid() { + widget.Data = widget.Fetch(wtf.ToInts(Config.UList("wtf.mods.weather.cityids", widget.defaultCityCodes()))) + } widget.UpdateRefreshedAt() widget.display() From 7825fbe1b09c5c8d513090c7f5f504beca2ba2b4 Mon Sep 17 00:00:00 2001 From: Michael Cordell Date: Sat, 9 Jun 2018 07:32:28 -0700 Subject: [PATCH 18/23] Add circleci module Needs a Circle API token, which can be found at https://circleci.com/account/api. This is passed under the environmental variable WTF_CIRCLE_API_KEY. --- circleci/build.go | 10 ++++++ circleci/client.go | 80 ++++++++++++++++++++++++++++++++++++++++++++ circleci/widget.go | 83 ++++++++++++++++++++++++++++++++++++++++++++++ wtf.go | 4 +++ 4 files changed, 177 insertions(+) create mode 100644 circleci/build.go create mode 100644 circleci/client.go create mode 100644 circleci/widget.go diff --git a/circleci/build.go b/circleci/build.go new file mode 100644 index 00000000..f0ee6cab --- /dev/null +++ b/circleci/build.go @@ -0,0 +1,10 @@ +package circleci + +type Build struct { + AuthorEmail string `json:"author_email"` + AuthorName string `json:"author_name"` + Branch string `json:"branch"` + BuildNum int `json:"build_num"` + Reponame string `json:"reponame"` + Status string `json:"status"` +} diff --git a/circleci/client.go b/circleci/client.go new file mode 100644 index 00000000..16d7eb82 --- /dev/null +++ b/circleci/client.go @@ -0,0 +1,80 @@ +package circleci + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" +) + +const APIEnvKey = "WTF_CIRCLE_API_KEY" + +func BuildsFor() ([]*Build, error) { + builds := []*Build{} + + resp, err := circleRequest("recent-builds") + if err != nil { + return builds, err + } + + parseJson(&builds, resp.Body) + + return builds, nil +} + +/* -------------------- Unexported Functions -------------------- */ + +var ( + circleAPIURL = &url.URL{Scheme: "https", Host: "circleci.com", Path: "/api/v1/"} +) + +func circleRequest(path string) (*http.Response, error) { + params := url.Values{} + params.Add("circle-token", apiKey()) + + url := circleAPIURL.ResolveReference(&url.URL{Path: path, RawQuery: params.Encode()}) + + req, err := http.NewRequest("GET", url.String(), nil) + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json") + if err != nil { + return nil, err + } + + httpClient := &http.Client{} + resp, err := httpClient.Do(req) + if err != nil { + return nil, err + } + + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return nil, fmt.Errorf(resp.Status) + } + + return resp, nil +} + +func apiKey() string { + return os.Getenv(APIEnvKey) +} + +func parseJson(obj interface{}, text io.Reader) { + jsonStream, err := ioutil.ReadAll(text) + if err != nil { + panic(err) + } + + decoder := json.NewDecoder(bytes.NewReader(jsonStream)) + + for { + if err := decoder.Decode(obj); err == io.EOF { + break + } else if err != nil { + panic(err) + } + } +} diff --git a/circleci/widget.go b/circleci/widget.go new file mode 100644 index 00000000..0e3d6a28 --- /dev/null +++ b/circleci/widget.go @@ -0,0 +1,83 @@ +package circleci + +import ( + "fmt" + "github.com/olebedev/config" + "github.com/senorprogrammer/wtf/wtf" +) + +// Config is a pointer to the global config object +var Config *config.Config + +type Widget struct { + wtf.TextWidget +} + +func NewWidget() *Widget { + widget := Widget{ + TextWidget: wtf.NewTextWidget(" CircleCI ", "circleci", false), + } + + return &widget +} + +/* -------------------- Exported Functions -------------------- */ + +func (widget *Widget) Refresh() { + if widget.Disabled() { + return + } + + builds, err := BuildsFor() + + widget.UpdateRefreshedAt() + + widget.View.SetTitle(fmt.Sprintf("%s - Builds", widget.Name)) + + if err != nil { + widget.View.SetWrap(true) + fmt.Fprintf(widget.View, "%v", err) + } else { + widget.View.SetWrap(false) + widget.View.SetText(fmt.Sprintf("%s", widget.contentFrom(builds))) + } +} + +/* -------------------- Unexported Functions -------------------- */ + +func (widget *Widget) contentFrom(builds []*Build) string { + var str string + for idx, build := range builds { + if idx > 10 { + return str + } + + str = str + fmt.Sprintf( + "[%s] %s-%d (%s) [white]%s\n", + buildColor(build), + build.Reponame, + build.BuildNum, + build.Branch, + build.AuthorName, + ) + } + + return str +} + +func buildColor(b *Build) string { + var color string + + switch b.Status { + case "failed": + color = "red" + case "running": + color = "yellow" + case "success": + color = "green" + default: + color = "white" + } + + return color +} diff --git a/wtf.go b/wtf.go index 563d748b..ff6725e4 100644 --- a/wtf.go +++ b/wtf.go @@ -13,6 +13,7 @@ import ( "github.com/senorprogrammer/wtf/bamboohr" "github.com/senorprogrammer/wtf/bargraph" "github.com/senorprogrammer/wtf/cfg" + "github.com/senorprogrammer/wtf/circleci" "github.com/senorprogrammer/wtf/clocks" "github.com/senorprogrammer/wtf/cmdrunner" "github.com/senorprogrammer/wtf/cryptoexchanges/bittrex" @@ -177,6 +178,8 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { Widgets = append(Widgets, bargraph.NewWidget()) case "bittrex": Widgets = append(Widgets, bittrex.NewWidget()) + case "circleci": + Widgets = append(Widgets, circleci.NewWidget()) case "clocks": Widgets = append(Widgets, clocks.NewWidget()) case "cmdrunner": @@ -230,6 +233,7 @@ func makeWidgets(app *tview.Application, pages *tview.Pages) { bamboohr.Config = Config bargraph.Config = Config bittrex.Config = Config + circleci.Config = Config clocks.Config = Config cmdrunner.Config = Config cryptolive.Config = Config From f3a55a1127bba28855bb4d42f89f8ab291720569 Mon Sep 17 00:00:00 2001 From: FengYa Date: Sat, 9 Jun 2018 23:06:51 +0800 Subject: [PATCH 19/23] change name of the module --- {ipinfohigherlimit => ipapi}/widget.go | 4 ++-- wtf.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) rename {ipinfohigherlimit => ipapi}/widget.go (97%) diff --git a/ipinfohigherlimit/widget.go b/ipapi/widget.go similarity index 97% rename from ipinfohigherlimit/widget.go rename to ipapi/widget.go index 9b781d0d..d65f2456 100644 --- a/ipinfohigherlimit/widget.go +++ b/ipapi/widget.go @@ -1,4 +1,4 @@ -package ipinfohigherlimit +package ipapi import ( "encoding/json" @@ -43,7 +43,7 @@ type ipinfo struct { // NewWidget constructor func NewWidget() *Widget { widget := Widget{ - TextWidget: wtf.NewTextWidget(" IPInfo ", "ipinfohigherlimit", false), + TextWidget: wtf.NewTextWidget(" IPInfo ", "ipapi", false), } widget.View.SetWrap(false) diff --git a/wtf.go b/wtf.go index ff6725e4..6499573a 100644 --- a/wtf.go +++ b/wtf.go @@ -25,7 +25,7 @@ import ( "github.com/senorprogrammer/wtf/gspreadsheets" "github.com/senorprogrammer/wtf/help" "github.com/senorprogrammer/wtf/ipinfo" - "github.com/senorprogrammer/wtf/ipinfohigherlimit" + "github.com/senorprogrammer/wtf/ipapi" "github.com/senorprogrammer/wtf/jira" "github.com/senorprogrammer/wtf/newrelic" "github.com/senorprogrammer/wtf/opsgenie" @@ -198,8 +198,8 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { Widgets = append(Widgets, gspreadsheets.NewWidget()) case "ipinfo": Widgets = append(Widgets, ipinfo.NewWidget()) - case "ipinfohigherlimit": - Widgets = append(Widgets, ipinfohigherlimit.NewWidget()) + case "ipapi": + Widgets = append(Widgets, ipapi.NewWidget()) case "jira": Widgets = append(Widgets, jira.NewWidget()) case "newrelic": @@ -243,7 +243,7 @@ func makeWidgets(app *tview.Application, pages *tview.Pages) { gitlab.Config = Config gspreadsheets.Config = Config ipinfo.Config = Config - ipinfohigherlimit.Config = Config + ipapi.Config = Config jira.Config = Config newrelic.Config = Config opsgenie.Config = Config From 55c4c26772db08dafaf1a29119b401d6d964c077 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Sat, 9 Jun 2018 08:52:32 -0700 Subject: [PATCH 20/23] Close #168. Modal dialogs now center onscreen properly --- git/widget.go | 17 ++++++++++++++--- todo/widget.go | 17 ++++++++++++++--- wtf/billboard_modal.go | 16 ++++++++-------- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/git/widget.go b/git/widget.go index 89aad5a2..894d71b5 100644 --- a/git/widget.go +++ b/git/widget.go @@ -23,6 +23,10 @@ const HelpText = ` arrow right: Next git repository ` +const offscreen = -1000 +const modalWidth = 80 +const modalHeight = 7 + type Widget struct { wtf.TextWidget @@ -131,11 +135,18 @@ func (widget *Widget) modalForm(lbl, text string) *tview.Form { return form } func (widget *Widget) modalFrame(form *tview.Form) *tview.Frame { - _, _, w, h := widget.View.GetInnerRect() - frame := tview.NewFrame(form).SetBorders(0, 0, 0, 0, 0, 0) + frame.SetRect(offscreen, offscreen, modalWidth, modalHeight) frame.SetBorder(true) - frame.SetRect(w+20, h+2, 80, 7) + frame.SetBorders(1, 1, 0, 0, 1, 1) + + drawFunc := func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) { + w, h := screen.Size() + frame.SetRect((w/2)-(width/2), (h/2)-(height/2), width, height) + return x, y, width, height + } + + frame.SetDrawFunc(drawFunc) return frame } diff --git a/todo/widget.go b/todo/widget.go index f529f53e..e06c007c 100644 --- a/todo/widget.go +++ b/todo/widget.go @@ -34,6 +34,10 @@ const HelpText = ` space: Check the selected item on or off ` +const offscreen = -1000 +const modalWidth = 80 +const modalHeight = 7 + type Widget struct { wtf.TextWidget @@ -267,11 +271,18 @@ func (widget *Widget) modalForm(lbl, text string) *tview.Form { } func (widget *Widget) modalFrame(form *tview.Form) *tview.Frame { - _, _, w, h := widget.View.GetInnerRect() - frame := tview.NewFrame(form).SetBorders(0, 0, 0, 0, 0, 0) + frame.SetRect(offscreen, offscreen, modalWidth, modalHeight) frame.SetBorder(true) - frame.SetRect(w+20, h+2, 80, 7) + frame.SetBorders(1, 1, 0, 0, 1, 1) + + drawFunc := func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) { + w, h := screen.Size() + frame.SetRect((w/2)-(width/2), (h/2)-(height/2), width, height) + return x, y, width, height + } + + frame.SetDrawFunc(drawFunc) return frame } diff --git a/wtf/billboard_modal.go b/wtf/billboard_modal.go index 552ffb21..70137c2a 100644 --- a/wtf/billboard_modal.go +++ b/wtf/billboard_modal.go @@ -35,19 +35,19 @@ func NewBillboardModal(text string, closeFunc func()) *tview.Frame { textView.SetBackgroundColor(tview.Styles.ContrastBackgroundColor) textView.SetInputCapture(keyboardIntercept) - thing := tview.NewFrame(textView) - thing.SetRect(offscreen, offscreen, modalWidth, modalHeight) + frame := tview.NewFrame(textView) + frame.SetRect(offscreen, offscreen, modalWidth, modalHeight) drawFunc := func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) { w, h := screen.Size() - thing.SetRect((w/2)-(width/2), (h/2)-(height/2), width, height) + frame.SetRect((w/2)-(width/2), (h/2)-(height/2), width, height) return x, y, width, height } - thing.SetBackgroundColor(tview.Styles.ContrastBackgroundColor) - thing.SetBorder(true) - thing.SetBorders(1, 1, 0, 0, 1, 1) - thing.SetDrawFunc(drawFunc) + frame.SetBackgroundColor(tview.Styles.ContrastBackgroundColor) + frame.SetBorder(true) + frame.SetBorders(1, 1, 0, 0, 1, 1) + frame.SetDrawFunc(drawFunc) - return thing + return frame } From 18f9612220bef3f7f765a627245f1f42f438261b Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Sun, 10 Jun 2018 18:23:00 -0400 Subject: [PATCH 21/23] Add icon for heavy intense rain --- weather/widget.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/weather/widget.go b/weather/widget.go index 8a1176cb..d1c3d203 100644 --- a/weather/widget.go +++ b/weather/widget.go @@ -182,6 +182,8 @@ func (widget *Widget) icon(data *owm.CurrentWeatherData) string { icon = "🌫" case "haze": icon = "🌫" + case "heavy intensity rain": + icon = "💦" case "heavy rain": icon = "💦" case "heavy snow": From 696c8adcb7946c38ff145aa854f44da5aac823da Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Tue, 12 Jun 2018 12:35:47 -0700 Subject: [PATCH 22/23] Close #92. Wrap the release files in a directory to prevent tar-bombing the local dir --- .goreleaser.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.goreleaser.yml b/.goreleaser.yml index fa4af340..e5ebdc42 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -9,3 +9,6 @@ builds: - linux goarch: - amd64 + +archive: + wrap_in_directory: true From 60e6dc2f837242225f0ae4dfaa1082cee96eb5f2 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Wed, 13 Jun 2018 02:17:57 -0700 Subject: [PATCH 23/23] Merge in Gitlab module --- gitlab/widget.go | 4 ---- wtf.go | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/gitlab/widget.go b/gitlab/widget.go index 9dd19ba9..35e56a91 100644 --- a/gitlab/widget.go +++ b/gitlab/widget.go @@ -66,10 +66,6 @@ func NewWidget(app *tview.Application, pages *tview.Pages) *Widget { /* -------------------- Exported Functions -------------------- */ func (widget *Widget) Refresh() { - if widget.Disabled() { - return - } - for _, project := range widget.GitlabProjects { project.Refresh() } diff --git a/wtf.go b/wtf.go index 6499573a..b7d4b942 100644 --- a/wtf.go +++ b/wtf.go @@ -24,8 +24,8 @@ import ( "github.com/senorprogrammer/wtf/gitlab" "github.com/senorprogrammer/wtf/gspreadsheets" "github.com/senorprogrammer/wtf/help" - "github.com/senorprogrammer/wtf/ipinfo" "github.com/senorprogrammer/wtf/ipapi" + "github.com/senorprogrammer/wtf/ipinfo" "github.com/senorprogrammer/wtf/jira" "github.com/senorprogrammer/wtf/newrelic" "github.com/senorprogrammer/wtf/opsgenie" @@ -196,10 +196,10 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { Widgets = append(Widgets, gitlab.NewWidget(app, pages)) case "gspreadsheets": Widgets = append(Widgets, gspreadsheets.NewWidget()) - case "ipinfo": - Widgets = append(Widgets, ipinfo.NewWidget()) case "ipapi": Widgets = append(Widgets, ipapi.NewWidget()) + case "ipinfo": + Widgets = append(Widgets, ipinfo.NewWidget()) case "jira": Widgets = append(Widgets, jira.NewWidget()) case "newrelic": @@ -242,8 +242,8 @@ func makeWidgets(app *tview.Application, pages *tview.Pages) { github.Config = Config gitlab.Config = Config gspreadsheets.Config = Config - ipinfo.Config = Config ipapi.Config = Config + ipinfo.Config = Config jira.Config = Config newrelic.Config = Config opsgenie.Config = Config