From b4970e54d4872d57244f63f35cc4cd00d63bfcfa Mon Sep 17 00:00:00 2001 From: Mark Old Date: Fri, 8 Jun 2018 13:17:37 -0700 Subject: [PATCH 01/37] 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/37] 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 eb547136cf3fa5e4fa852f63d2d67e0b931288d1 Mon Sep 17 00:00:00 2001 From: DavidB Date: Sat, 9 Jun 2018 20:49:12 +0300 Subject: [PATCH 03/37] Added simple jenkins module that enables watching jenkins views build status --- jenkins/client.go | 64 ++++++++++++++++++++++++++++++++++++ jenkins/job.go | 8 +++++ jenkins/view.go | 10 ++++++ jenkins/widget.go | 83 +++++++++++++++++++++++++++++++++++++++++++++++ wtf.go | 4 +++ 5 files changed, 169 insertions(+) create mode 100644 jenkins/client.go create mode 100644 jenkins/job.go create mode 100644 jenkins/view.go create mode 100644 jenkins/widget.go diff --git a/jenkins/client.go b/jenkins/client.go new file mode 100644 index 00000000..e599b9f7 --- /dev/null +++ b/jenkins/client.go @@ -0,0 +1,64 @@ +package jenkins + +import ( + "bytes" + "encoding/json" + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" +) + +func Create(jenkinsURL string, username string, apiKey string) (*View, error) { + const apiSuffix = "api/json?pretty=true" + parsedSuffix, err := url.Parse(apiSuffix) + if err != nil { + return &View{}, err + } + + parsedJenkinsURL, err := url.Parse(ensureLastSlash(jenkinsURL)) + if err != nil { + return &View{}, err + } + jenkinsAPIURL := parsedJenkinsURL.ResolveReference(parsedSuffix) + + req, err := http.NewRequest("GET", jenkinsAPIURL.String(), nil) + req.SetBasicAuth(username, apiKey) + + httpClient := &http.Client{} + resp, err := httpClient.Do(req) + + if err != nil { + return &View{}, err + } + + view := &View{} + parseJson(view, resp.Body) + + return view, nil +} + +func ensureLastSlash(URL string) string { + return strings.TrimRight(URL, "/") + "/" +} + +/* -------------------- Unexported Functions -------------------- */ + + +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/jenkins/job.go b/jenkins/job.go new file mode 100644 index 00000000..e360e0f6 --- /dev/null +++ b/jenkins/job.go @@ -0,0 +1,8 @@ +package jenkins + +type Job struct { + Class string `json:"_class"` + Name string `json:"name"` + Url string `json:"url"` + Color string `json:"color"` +} diff --git a/jenkins/view.go b/jenkins/view.go new file mode 100644 index 00000000..9041b297 --- /dev/null +++ b/jenkins/view.go @@ -0,0 +1,10 @@ +package jenkins + +type View struct { + Class string `json:"_class"` + Description string `json:"description"` + Jobs []Job `json:"jobs"` + Name string `json:"name"` + Property []string `json:"property"` + url string `json:"url"` +} diff --git a/jenkins/widget.go b/jenkins/widget.go new file mode 100644 index 00000000..a922fbbf --- /dev/null +++ b/jenkins/widget.go @@ -0,0 +1,83 @@ +package jenkins + +import ( + "fmt" + "github.com/olebedev/config" + "github.com/senorprogrammer/wtf/wtf" + "os" +) + +// 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("Jenkins", "jenkins", false), + } + + return &widget +} + +/* -------------------- Exported Functions -------------------- */ + +func (widget *Widget) Refresh() { + if widget.Disabled() { + return + } + + view, err := Create(Config.UString("wtf.mods.jenkins.url"), + Config.UString("wtf.mods.jenkins.user"), os.Getenv("WTF_JENKINS_API_KEY")) + + widget.UpdateRefreshedAt() + widget.View.Clear() + + if err != nil { + widget.View.SetWrap(true) + widget.View.SetTitle(fmt.Sprintf(" %s ", widget.Name)) + fmt.Fprintf(widget.View, "%v", err) + } else { + widget.View.SetWrap(false) + widget.View.SetTitle( + fmt.Sprintf( + " %s: [green] ", + widget.Name, + ), + ) + fmt.Fprintf(widget.View, "%s", widget.contentFrom(view)) + } +} + +/* -------------------- Unexported Functions -------------------- */ + +func (widget *Widget) contentFrom(view *View) string { + str := fmt.Sprintf(" [red]%s[white]\n", view.Name); + + for _, job := range view.Jobs { + str = str + fmt.Sprintf( + " [%s]%-6s[white]\n", + widget.jobColor(&job), + job.Name, + ) + } + + return str +} + +func (widget *Widget) jobColor(job *Job) string { + var color string + + switch job.Color { + case "blue": + color = "green" + case "red": + color = "red" + default: + color = "white" + } + + return color +} diff --git a/wtf.go b/wtf.go index 2c27fb13..5822eb6b 100644 --- a/wtf.go +++ b/wtf.go @@ -23,6 +23,7 @@ import ( "github.com/senorprogrammer/wtf/gspreadsheets" "github.com/senorprogrammer/wtf/help" "github.com/senorprogrammer/wtf/ipinfo" + "github.com/senorprogrammer/wtf/jenkins" "github.com/senorprogrammer/wtf/jira" "github.com/senorprogrammer/wtf/newrelic" "github.com/senorprogrammer/wtf/opsgenie" @@ -191,6 +192,8 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { Widgets = append(Widgets, gspreadsheets.NewWidget()) case "ipinfo": Widgets = append(Widgets, ipinfo.NewWidget()) + case "jenkins": + Widgets = append(Widgets, jenkins.NewWidget()) case "jira": Widgets = append(Widgets, jira.NewWidget()) case "newrelic": @@ -232,6 +235,7 @@ func makeWidgets(app *tview.Application, pages *tview.Pages) { github.Config = Config gspreadsheets.Config = Config ipinfo.Config = Config + jenkins.Config = Config jira.Config = Config newrelic.Config = Config opsgenie.Config = Config From e73037c84cd4598e62a68bfc702b5c32eada62fb Mon Sep 17 00:00:00 2001 From: DavidB Date: Sat, 9 Jun 2018 21:16:19 +0300 Subject: [PATCH 04/37] Added jenkins module documentation --- _site/content/posts/modules/jenkins.md | 52 +++++++++++++++++++++++++ _site/static/imgs/modules/jenkins.png | Bin 0 -> 13073 bytes 2 files changed, 52 insertions(+) create mode 100644 _site/content/posts/modules/jenkins.md create mode 100644 _site/static/imgs/modules/jenkins.png diff --git a/_site/content/posts/modules/jenkins.md b/_site/content/posts/modules/jenkins.md new file mode 100644 index 00000000..c4873621 --- /dev/null +++ b/_site/content/posts/modules/jenkins.md @@ -0,0 +1,52 @@ +--- +title: "Modules: Jenkins" +date: 2018-06-09T20:53:35-07:00 +draft: false +--- + +Displays jenkins status of given builds in a project or view + +jenkins screenshot + +## Source Code + +```bash +wtf/jenkins/ +``` + +## Required ENV Variables + +Key: `WTF_JENKINS_API_KEY`
    +Value: Your Jenkins API key. + +## Keyboard Commands + +None. + +## Configuration + +```yaml +jenkins: + user: "username" + url: "https://jenkins.domain.com/jenkins/view_url" + enabled: true + position: + top: 2 + left: 3 + height: 2 + width: 3 + refreshInterval: 300 +``` + +### Attributes + +`user`
    +Your Jenkins username.
    + +`url`
    +The url to your Jenkins project or view.
    +Values: A valid URI. + +`refreshInterval`
    +How often, in seconds, this module will update its data.
    +Values: A positive integer, `0..n`. diff --git a/_site/static/imgs/modules/jenkins.png b/_site/static/imgs/modules/jenkins.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd3521ffb929449ddbeb72429eb58487ded468a GIT binary patch literal 13073 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 Date: Sun, 10 Jun 2018 18:23:00 -0400 Subject: [PATCH 05/37] 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 3e27166858f3d0bb28d59a471d67c11997e826fa Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Sun, 10 Jun 2018 18:36:42 -0400 Subject: [PATCH 06/37] Add basic documentation for Google Spreadsheet module --- _site/content/posts/modules/gcal.md | 6 +- _site/content/posts/modules/gspreadsheets.md | 70 +++++++ _site/content/posts/modules/jira.md | 29 ++- docs/index.xml | 29 ++- docs/posts/index.html | 7 + docs/posts/index.xml | 29 ++- docs/posts/modules/gcal/index.html | 6 +- docs/posts/modules/gspreadsheets/index.html | 184 +++++++++++++++++++ docs/posts/modules/jira/index.html | 25 ++- docs/sitemap.xml | 9 +- 10 files changed, 364 insertions(+), 30 deletions(-) create mode 100644 _site/content/posts/modules/gspreadsheets.md create mode 100644 docs/posts/modules/gspreadsheets/index.html diff --git a/_site/content/posts/modules/gcal.md b/_site/content/posts/modules/gcal.md index ef6a70dd..03593431 100644 --- a/_site/content/posts/modules/gcal.md +++ b/_site/content/posts/modules/gcal.md @@ -19,11 +19,7 @@ wtf/gcal/ ## Required ENV Variables -Key: `WTF_GOOGLE_CAL_CLIENT_ID`
    -Value: Your Google API client ID. - -Key: `WTF_GOOGLE_CAL_CLIENT_SECRET`
    -Value: Your Google API client secret. +None. ## Keyboard Commands diff --git a/_site/content/posts/modules/gspreadsheets.md b/_site/content/posts/modules/gspreadsheets.md new file mode 100644 index 00000000..77d8a93c --- /dev/null +++ b/_site/content/posts/modules/gspreadsheets.md @@ -0,0 +1,70 @@ +--- +title: "Google Spreadsheets" +date: 2018-06-10T18:26:26-04:00 +draft: false +--- + +Added in `v0.0.7`. + +Display information + +```bash +wtf/gspreadsheets/ +``` + +## Required ENV Variables + +None. + +## Keyboard Commands + +None. + +## Configuration + +```yaml +gspreadsheets: + colors: + values: "green" + cells: + names: + - "Cell 1 name" + - "Cell 2 name" + addresses: + - "A1" + - "A2" + enabled: true + position: + top: 0 + left: 0 + width: 1 + height: 1 + refreshInterval: "300" + secretFile: "~/.wtf/gspreadsheets/client_secret.json" + sheetId: "id_of_google_spreadsheet" +``` + +### Attributes + +`colors.values`
    +The color to display the cell values in.
    +Values: Any X11 color name. + +`cells.names`
    + +`cells.addresses`
    + +`enabled`
    +Whether or not this module is executed and if its data displayed onscreen.
    +Values: `true`, `false`. + +`position`
    +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`. + +`secretFile`
    +Your Google client secret JSON file.
    +Values: A string representing a file path to the JSON secret file. diff --git a/_site/content/posts/modules/jira.md b/_site/content/posts/modules/jira.md index 08479c62..a25cc1f3 100644 --- a/_site/content/posts/modules/jira.md +++ b/_site/content/posts/modules/jira.md @@ -25,6 +25,8 @@ None. ## Configuration +### Single Jira Project + ```yaml jira: colors: @@ -40,7 +42,32 @@ jira: left: 1 height: 1 width: 2 - project: "JIRA" + project: "ProjectA" + refreshInterval: 900 + username: "chris.cummer" +``` + +### Multiple Jira Projects + +If you want to monitor multiple Jira projects, use the following +configuration (note the difference in `project`): + +```yaml +jira: + colors: + rows: + even: "lightblue" + odd: "white" + domain: "https://umbrellacorp.atlassian.net" + email: "chriscummer@me.com" + enabled: true + jql: "issueType = Story" + position: + top: 4 + left: 1 + height: 1 + width: 2 + project: ["ProjectA", "ProjectB"] refreshInterval: 900 username: "chris.cummer" ``` diff --git a/docs/index.xml b/docs/index.xml index 28d48bd1..288b8b9e 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 + Sun, 10 Jun 2018 18:26:26 -0400 + + Google Spreadsheets + https://wtfutil.com/posts/modules/gspreadsheets/ + Sun, 10 Jun 2018 18:26:26 -0400 + + https://wtfutil.com/posts/modules/gspreadsheets/ + Added in v0.0.7. +Display information +wtf/gspreadsheets/ Required ENV Variables None. +Keyboard Commands None. +Configuration gspreadsheets:colors:values:&#34;green&#34;cells:names:-&#34;Cell 1 name&#34;-&#34;Cell 2 name&#34;addresses:-&#34;A1&#34;-&#34;A2&#34;enabled:trueposition:top:0left:0width:1height:1refreshInterval:&#34;300&#34;secretFile:&#34;~/.wtf/gspreadsheets/client_secret.json&#34;sheetId:&#34;id_of_google_spreadsheet&#34; Attributes colors.values The color to display the cell values in. Values: Any X11 color name. +cells.names cells.addresses enabled Whether or not this module is executed and if its data displayed onscreen. Values: true, false. +position Where in the grid this module&rsquo;s widget will be displayed. refreshInterval How often, in seconds, this module will update its data. + + Bittrex https://wtfutil.com/posts/modules/cryptocurrencies/bittrex/ @@ -174,10 +189,8 @@ Key: j Action: Select the next item in the list. Displays all Jira issues assigned to you for the specified project. Source Code wtf/jira/ Required ENV Variables Key: WTF_JIRA_API_KEY Value: Your Jira API key. Keyboard Commands None. -Configuration jira:colors:rows:even:&#34;lightblue&#34;odd:&#34;white&#34;domain:&#34;https://umbrellacorp.atlassian.net&#34;email:&#34;chriscummer@me.com&#34;enabled:truejql:&#34;issueType = Story&#34;position:top:4left:1height:1width:2project:&#34;JIRA&#34;refreshInterval:900username:&#34;chris.cummer&#34; Attributes colors.rows.even Define the foreground color for even-numbered rows. Values: Any X11 color name. -colors.rows.odd Define the foreground color for odd-numbered rows. Values: Any X11 color name. -domain Your Jira corporate domain. Values: A valid URI. -email The email address associated with your Jira account. +Configuration Single Jira Project jira:colors:rows:even:&#34;lightblue&#34;odd:&#34;white&#34;domain:&#34;https://umbrellacorp.atlassian.net&#34;email:&#34;chriscummer@me.com&#34;enabled:truejql:&#34;issueType = Story&#34;position:top:4left:1height:1width:2project:&#34;ProjectA&#34;refreshInterval:900username:&#34;chris.cummer&#34; Multiple Jira Projects If you want to monitor multiple Jira projects, use the following configuration (note the difference in project): +jira:colors:rows:even:&#34;lightblue&#34;odd:&#34;white&#34;domain:&#34;https://umbrellacorp.atlassian.net&#34;email:&#34;chriscummer@me.com&#34;enabled:truejql:&#34;issueType = Story&#34;position:top:4left:1height:1width:2project:[&#34;ProjectA&#34;,&#34;ProjectB&#34;]refreshInterval:900username:&#34;chris.cummer&#34; Attributes colors.rows.even Define the foreground color for even-numbered rows. Values: Any X11 color name. @@ -188,9 +201,9 @@ email The email address associated with your Jira account. https://wtfutil.com/posts/modules/gcal/ Displays your upcoming Google calendar events. Not: Setting up access to Google Calendars for Go is a bit unobvious. Check out Google&rsquo;s Go Quickstart first and if you have problems, then take a look at this comment by WesleydeSouza which offers a slightly different approach. -Source Code wtf/gcal/ Required ENV Variables Key: WTF_GOOGLE_CAL_CLIENT_ID Value: Your Google API client ID. -Key: WTF_GOOGLE_CAL_CLIENT_SECRET Value: Your Google API client secret. -Keyboard Commands None. +Source Code wtf/gcal/ Required ENV Variables None. +Keyboard Commands None. +Configuration gcal:colors:title:&#34;red&#34;description:&#34;lightblue&#34;highlights:-[&#39;1on1|1\/11&#39;,&#39;green&#39;]-[&#39;apple|google|aws&#39;,&#39;blue&#39;]-[&#39;interview|meet&#39;,&#39;magenta&#39;]-[&#39;lunch&#39;,&#39;yellow&#39;]past:&#34;gray&#34;conflictIcon:&#34;🚨&#34;currentIcon:&#34;💥&#34;displayResponseStatus:trueemail:&#34;chriscummer@me.com&#34;enabled:trueeventCount:12position:top:0left:0height:4width:1refreshInterval:300secretFile:&#34;~/.wtf/gcal/client_secret.json&#34;withLocation:true Attributes colors.title The default colour for calendar event titles. Values: Any X11 color name. diff --git a/docs/posts/index.html b/docs/posts/index.html index 120d52e0..09edccfc 100644 --- a/docs/posts/index.html +++ b/docs/posts/index.html @@ -99,6 +99,13 @@

    Posts

    • + + Google Spreadsheets + + + + +
    • Bittrex diff --git a/docs/posts/index.xml b/docs/posts/index.xml index 71eef75b..62da9b8d 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 + Sun, 10 Jun 2018 18:26:26 -0400 + + Google Spreadsheets + https://wtfutil.com/posts/modules/gspreadsheets/ + Sun, 10 Jun 2018 18:26:26 -0400 + + https://wtfutil.com/posts/modules/gspreadsheets/ + Added in v0.0.7. +Display information +wtf/gspreadsheets/ Required ENV Variables None. +Keyboard Commands None. +Configuration gspreadsheets:colors:values:&#34;green&#34;cells:names:-&#34;Cell 1 name&#34;-&#34;Cell 2 name&#34;addresses:-&#34;A1&#34;-&#34;A2&#34;enabled:trueposition:top:0left:0width:1height:1refreshInterval:&#34;300&#34;secretFile:&#34;~/.wtf/gspreadsheets/client_secret.json&#34;sheetId:&#34;id_of_google_spreadsheet&#34; Attributes colors.values The color to display the cell values in. Values: Any X11 color name. +cells.names cells.addresses enabled Whether or not this module is executed and if its data displayed onscreen. Values: true, false. +position Where in the grid this module&rsquo;s widget will be displayed. refreshInterval How often, in seconds, this module will update its data. + + Bittrex https://wtfutil.com/posts/modules/cryptocurrencies/bittrex/ @@ -174,10 +189,8 @@ Key: j Action: Select the next item in the list. Displays all Jira issues assigned to you for the specified project. Source Code wtf/jira/ Required ENV Variables Key: WTF_JIRA_API_KEY Value: Your Jira API key. Keyboard Commands None. -Configuration jira:colors:rows:even:&#34;lightblue&#34;odd:&#34;white&#34;domain:&#34;https://umbrellacorp.atlassian.net&#34;email:&#34;chriscummer@me.com&#34;enabled:truejql:&#34;issueType = Story&#34;position:top:4left:1height:1width:2project:&#34;JIRA&#34;refreshInterval:900username:&#34;chris.cummer&#34; Attributes colors.rows.even Define the foreground color for even-numbered rows. Values: Any X11 color name. -colors.rows.odd Define the foreground color for odd-numbered rows. Values: Any X11 color name. -domain Your Jira corporate domain. Values: A valid URI. -email The email address associated with your Jira account. +Configuration Single Jira Project jira:colors:rows:even:&#34;lightblue&#34;odd:&#34;white&#34;domain:&#34;https://umbrellacorp.atlassian.net&#34;email:&#34;chriscummer@me.com&#34;enabled:truejql:&#34;issueType = Story&#34;position:top:4left:1height:1width:2project:&#34;ProjectA&#34;refreshInterval:900username:&#34;chris.cummer&#34; Multiple Jira Projects If you want to monitor multiple Jira projects, use the following configuration (note the difference in project): +jira:colors:rows:even:&#34;lightblue&#34;odd:&#34;white&#34;domain:&#34;https://umbrellacorp.atlassian.net&#34;email:&#34;chriscummer@me.com&#34;enabled:truejql:&#34;issueType = Story&#34;position:top:4left:1height:1width:2project:[&#34;ProjectA&#34;,&#34;ProjectB&#34;]refreshInterval:900username:&#34;chris.cummer&#34; Attributes colors.rows.even Define the foreground color for even-numbered rows. Values: Any X11 color name. @@ -188,9 +201,9 @@ email The email address associated with your Jira account. https://wtfutil.com/posts/modules/gcal/ Displays your upcoming Google calendar events. Not: Setting up access to Google Calendars for Go is a bit unobvious. Check out Google&rsquo;s Go Quickstart first and if you have problems, then take a look at this comment by WesleydeSouza which offers a slightly different approach. -Source Code wtf/gcal/ Required ENV Variables Key: WTF_GOOGLE_CAL_CLIENT_ID Value: Your Google API client ID. -Key: WTF_GOOGLE_CAL_CLIENT_SECRET Value: Your Google API client secret. -Keyboard Commands None. +Source Code wtf/gcal/ Required ENV Variables None. +Keyboard Commands None. +Configuration gcal:colors:title:&#34;red&#34;description:&#34;lightblue&#34;highlights:-[&#39;1on1|1\/11&#39;,&#39;green&#39;]-[&#39;apple|google|aws&#39;,&#39;blue&#39;]-[&#39;interview|meet&#39;,&#39;magenta&#39;]-[&#39;lunch&#39;,&#39;yellow&#39;]past:&#34;gray&#34;conflictIcon:&#34;🚨&#34;currentIcon:&#34;💥&#34;displayResponseStatus:trueemail:&#34;chriscummer@me.com&#34;enabled:trueeventCount:12position:top:0left:0height:4width:1refreshInterval:300secretFile:&#34;~/.wtf/gcal/client_secret.json&#34;withLocation:true Attributes colors.title The default colour for calendar event titles. Values: Any X11 color name. diff --git a/docs/posts/modules/gcal/index.html b/docs/posts/modules/gcal/index.html index fa6de93d..79d7ae93 100644 --- a/docs/posts/modules/gcal/index.html +++ b/docs/posts/modules/gcal/index.html @@ -124,11 +124,7 @@ first and if you have problems, then take a look at this
      wtf/gcal/

      Required ENV Variables

      -

      Key: WTF_GOOGLE_CAL_CLIENT_ID
      -Value: Your
      Google API client ID.

      - -

      Key: WTF_GOOGLE_CAL_CLIENT_SECRET
      -Value: Your Google API client secret.

      +

      None.

      Keyboard Commands

      diff --git a/docs/posts/modules/gspreadsheets/index.html b/docs/posts/modules/gspreadsheets/index.html new file mode 100644 index 00000000..16f16696 --- /dev/null +++ b/docs/posts/modules/gspreadsheets/index.html @@ -0,0 +1,184 @@ + + + + + + + + + + + + +Google Spreadsheets | WTF - A Terminal Dashboard + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      +

      Google Spreadsheets

      + +
      + +
      + + + +

      Added in v0.0.7.

      + +

      Display information

      +
      wtf/gspreadsheets/
      +

      Required ENV Variables

      + +

      None.

      + +

      Keyboard Commands

      + +

      None.

      + +

      Configuration

      +
      gspreadsheets:
      +  colors:
      +    values: "green"
      +  cells:
      +    names:
      +    - "Cell 1 name"
      +    - "Cell 2 name"
      +    addresses:
      +    - "A1"
      +    - "A2"
      +  enabled: true
      +  position:
      +    top: 0
      +    left: 0
      +    width: 1
      +    height: 1
      +  refreshInterval: "300"
      +  secretFile: "~/.wtf/gspreadsheets/client_secret.json"
      +  sheetId: "id_of_google_spreadsheet"
      +

      Attributes

      + +

      colors.values
      +The color to display the cell values in.
      +Values: Any X11 color name.

      + +

      cells.names

      + +

      cells.addresses

      + +

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

      + +

      position
      +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.

      + +

      secretFile
      +Your Google client secret JSON file.
      +Values: A string representing a file path to the JSON secret file.

      + +
      + + +
      + + + + diff --git a/docs/posts/modules/jira/index.html b/docs/posts/modules/jira/index.html index 258cb919..21d14850 100644 --- a/docs/posts/modules/jira/index.html +++ b/docs/posts/modules/jira/index.html @@ -129,6 +129,8 @@

      None.

      Configuration

      + +

      Single Jira Project

      jira:
         colors:
           rows:
      @@ -143,7 +145,28 @@
           left: 1
           height: 1
           width: 2
      -  project: "JIRA"
      +  project: "ProjectA"
      +  refreshInterval: 900
      +  username: "chris.cummer"
      +

      Multiple Jira Projects

      + +

      If you want to monitor multiple Jira projects, use the following +configuration (note the difference in project):

      +
      jira:
      +  colors:
      +    rows:
      +      even: "lightblue"
      +      odd: "white"
      +  domain: "https://umbrellacorp.atlassian.net"
      +  email: "chriscummer@me.com"
      +  enabled: true
      +  jql: "issueType = Story"
      +  position:
      +    top: 4
      +    left: 1
      +    height: 1
      +    width: 2
      +  project: ["ProjectA", "ProjectB"]
         refreshInterval: 900
         username: "chris.cummer"

      Attributes

      diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 64670468..e7166e26 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -2,6 +2,11 @@ + + https://wtfutil.com/posts/modules/gspreadsheets/ + 2018-06-10T18:26:26-04: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-10T18:26:26-04:00 0 @@ -145,7 +150,7 @@ https://wtfutil.com/ - 2018-06-04T20:06:40-07:00 + 2018-06-10T18:26:26-04:00 0 From 0a62a643fbe66bb22f069dd2019de2d216cbc539 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Sun, 10 Jun 2018 19:35:42 -0400 Subject: [PATCH 07/37] Add documentation for CircleCI module --- _site/content/posts/modules/circleci.md | 53 +++++ .../{gspreadsheets.md => gspreadsheet.md} | 0 _site/static/imgs/modules/circleci.png | Bin 0 -> 62031 bytes .../hyde-hyde/layouts/partials/sidebar.html | 2 + docs/404.html | 2 + docs/categories/index.html | 2 + docs/imgs/modules/circleci.png | Bin 0 -> 62031 bytes docs/index.html | 2 + docs/index.xml | 20 +- .../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 | 11 +- docs/posts/index.xml | 20 +- docs/posts/installation/index.html | 2 + docs/posts/modules/bamboohr/index.html | 2 + docs/posts/modules/circleci/index.html | 169 ++++++++++++++++ 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/gspreadsheet/index.html | 186 ++++++++++++++++++ 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 | 2 + 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/sitemap.xml | 11 +- docs/tags/index.html | 2 + 40 files changed, 520 insertions(+), 10 deletions(-) create mode 100644 _site/content/posts/modules/circleci.md rename _site/content/posts/modules/{gspreadsheets.md => gspreadsheet.md} (100%) create mode 100644 _site/static/imgs/modules/circleci.png create mode 100644 docs/imgs/modules/circleci.png create mode 100644 docs/posts/modules/circleci/index.html create mode 100644 docs/posts/modules/gspreadsheet/index.html diff --git a/_site/content/posts/modules/circleci.md b/_site/content/posts/modules/circleci.md new file mode 100644 index 00000000..af7abdae --- /dev/null +++ b/_site/content/posts/modules/circleci.md @@ -0,0 +1,53 @@ +--- +title: "CircleCI" +date: 2018-06-10T19:26:08-04:00 +draft: false +--- + +Added in `v0.0.7`. + +Displays build information for your CircleCI account. + +circleci screenshot + +## Source Code + +```bash +wtf/circleci/ +``` + +## Required ENV Variables + +Key: `WTF_CIRCLE_API_KEY`
      +Value: Your CircleCI API +token. + +## Keyboard Commands + +None. + +## Configuration + +```yaml +circleci: + enabled: true + position: + top: 4 + left: 1 + height: 1 + width: 2 + refreshInterval: 900 +``` + +### 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`. diff --git a/_site/content/posts/modules/gspreadsheets.md b/_site/content/posts/modules/gspreadsheet.md similarity index 100% rename from _site/content/posts/modules/gspreadsheets.md rename to _site/content/posts/modules/gspreadsheet.md diff --git a/_site/static/imgs/modules/circleci.png b/_site/static/imgs/modules/circleci.png new file mode 100644 index 0000000000000000000000000000000000000000..96eccfadb188eec9c604b0735d24d9668cb37d41 GIT binary patch literal 62031 zcmY(q1yo#3vo<;~Gk9=!3GVI|1`F;G+}+*Xg1b8j1b0t>;1V>r!{87I?p)sYobS8; znOd`@cJ1!n)vKzzpQ?^lR+N5+M2G|c0N%;UKvV$$*r2!TTm-ncx1pYM0ajK|Pfr$4P8LTOOICJ%etuRq4pt5h<~IpuS8oS5V=rb0SE~Oh zo%?{u@a`2TBiaQ)wAy)}^apC_#B zENra*E&J_Np?|mnN-oysZ#Dl@z6iU}zmorl_rLWBvHnx}|MM~b)6##rZ%q|J5@P+Y z+C-4#f70pz0Ac`Hh`72J%*l87ATw!q7C(W>tNvg7v;%7ziyEI~=|TVm#_#1Ddf`m( z$jDdtHZAQ!na|Pi$0hiLIa&awRAL}w5JD<&v@)%!b7oJdGeyvI^6`gSYe(go+vG`& zrWKsVsmF$H*#y7)(W+qU4-p&(v$YO)s_m59p#MWXa)ZF>40$;y?_Qa__XW5lNFuFa)$?_y!E~SEFr{hy z;A05fk3_cPc;I{4FYg~wQDm#A>8pqFF2RRHqq0TH$wl%ir!m0Nys5C!$cRghD5WCo zBsbd+N%}xeYS`aeqvpFW@SbvySE$yG(Xy|-0p17o(q{V;pWPJYDpGYW3Z~22ODK5Jk1AyXufz|i-^?}OxWb3(42ra$b z&0fSXOST)W!Db0kGk%eX} z(kP`=o=g>#SM6-Ng}#+7VW*$M0KhhEIYN)2b)lAOqwI|=kCS3vf=DB{fi``K=OUVCQT`H$nb~(YIi_r^_j@FC zgjpAAU+W#y^~Zd6_nTv&BxziAs`Q^g6u5r#3E8r-;+n8j43jU_BHnKn+~p?Jv!9eh z0|G!p%hg%rB*{M3c)zT^nfvBKFVOD~1VcOqj3I<7LsLTCZmah3$KStNBeHAW?OPIt z>2d-4*A07D)D3&%G0;|4Jr<=^_uAEF^X@9`&sN&EcHaAExv1z_>9b1N%u)pi}*FO0PY`lO6&?8CV2Ha5`C!u{w@56@F+KPwYW zdA%&4EV%nQX0Bz$i&kwiTKD58>llmO{FSpb zG9hP(Mbp6Fagt79)8@I1f4V`Q2MxkTo2@2k6L`xi0DwA9`4!RGSJTVVu=y1Y-Wz^R zIi0__@j>Id4T|RZD{X)(Q49K*Tc$}MKzY&7Xg;0l(S9q)JBIh;6|0>{qTw4-gynuN zOXWg7jV)f8+5T7BUB-?3aHftERqvG*Ln<}t2&jw$!poCzAo=k=O=&B7 zM$AHO@)fOPYJy1nhh(osNw#jq_q2j=I~UPJ>nxTNQF2NmEUz0QoaTndSRC)Lpbn2e zd7<5o{_;G3R>F<?4FpVga*H7Ne1bV-s`SJHOlr7Hi6=E>U+RvC@23pAbS&PHrOL~-R_Qd0d2}DA%*^s=sn#XAGW%cr zGpJnO0_rthoiqz#TAEoI0A|uX{6aB%>KaDkIRLJm6hpL$xJDQTWjGk1vv*s{ZFG_h}%*Vz}&OL2@Qy zSU(U(;!yS?bH5a^LKC{`x-dMP&)o1Zdc1q~Q1t7_h+4SA% zkNmjow_yn)UQ-UwVuyU?yyJV@Ig#4cOM$Ogxz5MVlS)q9wDN1UKL?#i#yLsGn(GBl zrmgTFS9aYehtholzBmZ~xxbu!_$m9!V285ME2P$VoDhjtqOS8)-D+~2jC6q!mj9_! z0GfaLJ@W=HBL&xOB)D2M)y=FOvQqYO->BeZ7`rsqc$8`H5BrxL=BUx@QKi5KxhxJ^ z`qrmS+6yf$J;vSjK9}1CGT0$??*)n0(PZ~y=NA-851hPDAC|21NIK3-G7l1WIZZAO zLeWNnx(~bADT>J{xXq(wKU`~>4EVK{JIlsk4wHiQ#6N122u*bw-ejZ?e^}vRuuc4^ zoyg(tlVP`XSLk=QBlrcFG=O9_I*@yElu2m~S-}@8&BoLuQ0m)G*m^OzzuN6)HB5Ww za+Vj!xjwqlnmBnaHHjDUrv7I{9`e!*r#5@LQr7eQm#;kIUeb6 z>pW!b=Ba{zW}@;wXioRi#3~KARd(Q)J_Kt?6MKBE?~jPy4uRcWCv#e31;VG}&lR4hD7zf_uk?z)`A8-8B7l_8zXe(EK0*+KIdb2MoGNcuGW z>@q>UKRmW^CFOm)b?7o;%bIfcM|xLc34`DCkoeHfF@tkK|9vOfB1O9kHn7D4q4jVe z=!H&fe^yC^mOt!)(#i^DW!cTsC|H)fpNh&uGPSk2c`S`AAaY!ZDOE@9lbO<4qP*xA z4cuB0x%F$>%DN!)Y@s_R#pSYm%5LWeze5M5v5~TTntexqb<9)Rv53ZW8KUASInAon z=1ZkP9)V|5W@B{^RZ48B+fDa_bkq+8F-Xey_-esm!&$m3oa2W?4LKDlhiO zBMm$TDuUT9LwNsD_xkmZAL3bpuG#%%ZZNKM*;mCtv4}@2w7A@mNPk^9r1v-I+*Hp? zwLQQI1r;r#THsi=eu9Nd4W<^)xZ`!o#%|+9`rE;L$hWCumCC*S)NRsUcpf*0?AU5$ zMrKmmr3IBSkgJO&AlWt>L~Fsh5+{6U%A;Rs2saaO*{4+oJf^u!TVMVyaf8%!?70w( zzm%<>H^Q_V@o2)BYI}@_mD%#{aaF_syW1;+-1&so!ZpUFOIcGg8Vv&4bHn+AeRZ-CINVj;`bEE;H2YVVLp#&Z{`B(aK3f3aZ7F^R9RMqm4@T$@-0xiME>s zCq)z}h6U7tUe7^YrxA{(;Hx*E*L^8~Vonv&R%Yn)$bo5}!*X^v>q!AikOY6$CHIHa zfNr0qpH0Xwldz26v}F#@=O1(OLUXz@7kHV!2j*^0KBok#+`^byHF%O!WfXLNnmN?G z?`0)TMIA?*@eaaTukJR0{Ig1!(x~)jkj+`e$ERV2y;^; z`*U)MP^zf+4!%AGO|=Frm4edHo5$9%Rq96bk1h}&UryD}{pKIFK3anQ1ndR9$DK^# z+-o%z6^5YVji=8SiFk8mBx}{=GsCwEF?XLFr@Ce|$+2XlNQ9ba2?|#?bl8v9^VUU! z{@^#ADvK8TlGt;EP^{7xkfzQ~X0t?kppBgDx|%r%Ft)A-YAfpUNPeovGj7*T?UTOH zHk*l$x8|yUq5w^5_+ej{pF3LX<-V|mV5iaDjWedIbDBIRqq}YI;m=Pn&ham+hUc~qo zkMGxl*6AD=Twgiho%yv(Mm^lfG4kT5R#D2^gyxWPvF6Q>D(P*R)EGVckCh~titL0P z6K(BU{}Ob^#Xq$0sH&gsN)c;n?QqD=X$5^#sUpb1?jwi`>e_7`Qdy{~$71dzCAybB z-sb+cBQ%JDEe-Rhjz9PZ0adoVEK!6vvbKxd)Ie*P`uTC1sQ!|{vcZBB^uD8&k}`a; zl0cNa-UxJg+6G28%owcB;ZIOD?2KsjOJ#p`pfl_}XB4C8p+c&Ylp}D2(#bS5gT=6H zBmTmnv-EG~3e5F&$L-Gz4~iJ;wqVvYuDOJa~hlm-5IEiPr9h_q|7SV zO)FU!CoE#H+nyT1tE16r|4gutI}DbaN>VNF3$Iw07xOWP*)Fe8a?ZSfQO;R36 z!K)kbx*f!xAM}x23Jba84#}p<`l{G69UqOhkyf%<^%#{AUMPD^-Y4}OZzsMxf%;ox zUZdmhoF(}!W`o6fp}#)s@u9?UQz9YA0&moU%%49x$u8m15CkFH)-fy!rL|q~e)bO4 zKGD7Aw~vPBCG;g*1Zx_&yo(LZYDa-%j3Q1&nB~Iq;!Q^L$^N z+N=MY$6)Sz5ddr_-$lwg(#om_yN~g8eXbvPn4g9*P$S}@H**`0>!!#bt!HZ2YC%4c zsYlY7IXGWBb}do8ddCz(mmqr;cMoEjGnbbVC=j~=+UIu@P5*23_=O=_$qm&zdx-D)yK|&}KtY-$_^W7r%d=8I zkZ~hH+|H25jTaQSdci)K0JiRTfKPm#b+BU(rAVzs(<&tyJ{IW0JRHYGNfKj2e!xpe zh3;>Dch>xfYn-;F5c&Cn?P1K@=q}P)RtI+3GXZ;-6oU9wBI02UV!3yI9)NwB?O>Rm z>>mpDRx*@ii6Gu9Ic`CRmgSw4UfyNm!+)b(T}IBVUuHoyqISa{O4TK8sZIxAQG~u` zYtKiF2Q=+h{7Lm|W@LD65M<@;TU1jf@JKAbP%R8e92(Km`|MdK5~F2foEeprQWye? z_`LOnvK{cW}4)ET^n-ZPAWC>-lM9JAN1pOmGVNc-(`V}jexAa!)syU~+L};4r(tjC~K-iYK4&batOeD>4 zQx?4yuR+<*tNSQ;S%u*juJuQ*^W*dan2WBh{za8rLE*2&m$G+lV zv50p?)JG{jW;0QS0Yej)p%2$a^xx zr>$b#5iPElCWIwyoPb|Pj=v@F(1#D%$ls|EK2=McMu>IZ!Np-~>YSg&+@u;!*67#4 z_oZIp*&h+%r<2+-_mBbp#p(W?bi1LQ9~u003qkyWR?`j?O=eWekUM;cQX_MQ!;8&AdkdjRA{Q(_|{LduLF7D+0`J%A!IUX-I#a^8q#=3P$Y?oN1A zR~gPtF${6KxgY|->GOhqufyV?E6wsa2dOIOJEWa4pxC>v)GX0;9+T zc<&Si96ypoh_&iFvY%+>AnykU{WelX4Emzo->E0d0f(t`z~6|J*`r2($!$!tEwh;s zg~!bygZ^RaH|%R`uR)xY3aT6U+mz&nO3{S0npj^KyTY( zf@^jq0iXdG;=r6~dX*pqTEKO3MkR$Q{I;?<8{ut72u8_8(I{UMAm(SwP$w_kN<`S> z!EBj^d((d~^EOU*kU&a)x(RY&3ikvQiYOofhJTC)Sc!z_ zF00U!|8^bUa$X)_IR|9^;1>iZa#I$FXeTC+uX@l#I9sNmaoA2L{esZ8`oOgmWU(~( zlhfLIQgDQAdq=SusLcOSWc$G62_y5edzHG1;J>7&LOQI;28kU;h(k*5z}-ACfglx- z437={eJH=IgDnYCOud2S(O3zR^qvD#Is{GH1Lqi=j7TQjT`SbdobcmaSn^Jq>`}!w zQyfqr#D9%k8?Z?FDXqNBJH z{HW62e!Q{zl6_Do7k;xXB){`2(AE&Q){kL?jZ%&b)b?mqLH8Q(9dwpG&rmR=oBGw% zjZq2a9L?nTC2ujv11BOr!^rF8m@dg|;W7 zqbGD%{!`p9d`vR}7%l-=Rh%~V$se z$iv8_08VC7^hNf5t{_hm*Bvp}DEs6xw~}vkTsCfIQMSQieM~rkzF+WV39=mhDmIV%!F{2foZ{iZ9Vr11*5T% zec=pkztpW}J!{eX+QCljk-!e+@x=Pn1u5g&%gV6zPn@gs)@O9(FDWAtrSv{i}V9qj- zh}Z*66e_WJcQX|Pi>E2;zbZfphi(-|qYOjZ?1JE*60g%a2e6OEHq@C8#*5-pR-rIf#0=bksrt_yc@VbBcd&8P zl2vt^k#pXGW4Fza_s4?IB)BkV~-*@m%qrSLb}1I$P0@%Ia-7RA-UUrX18(8ZLyl8Ooxt6;sW>K~g~D44c) zKKV)Pkkpq@9j`F$o8&=WUoB}zmpc@>UqwV~#Th^V5=vDtHlPrF>NL%xe@- zVdsJXzxDkCtp7v7vWLNlXn>^w^zpjd?Z|u5*ja{5*kFJ3jmqek1{NG~!{`7nFg0CN zEhvnybd1ymly?P>*KB0ESc*dNFoPO(PhGY6JU`Qv3j%S5fYEW7KwgtZk^yy93KKuO ze&kUz6$9?PB3zMSUKYidOZ_RJq+qr#+#Ij(0_hoKfG1|qEK67Acdgk~ zU^gkEYQ>{5;C`LHjZ2TjqkVpTA*={6arWUGz6;2A+|??T2qVB`}v)Acdj}yivHocsWx@y!qW|X!)Rhte1X`BK~+Qihd!dRQkKnPDd zU|~B=hu_q+5E*#8$u2hgd%%jyUlPIL8dm0Tjy%%ZlPe@g?U<80drNI-=PH34o&)i0GPnj55U!m6 z{?BU$aEd0;+-y>P5g$Oqj+S_>oG$T8LOZDVMwvAV3~O~@)UNa&lf+sBmXOV3kpg&J z=yQt55om8#QENX17_pMTnlFq^IPwTo7LbjOVY|J=u!XIJT1|?c{u-<7tWUT^fo#Vy2)T}4 zbDx|1h|iY&j4Tb}0Rzk?(gcgf2_S2~JPmH~d=Ekd3ju?cV2Od$`e+w!fJ~NAW)2En z32Mz{p(tr&!=&-`rT1af(^w<;Fq9I0BQ^DdWB#Narv0o4`8rhUJ& zIT;RVV0kClg<^+?;XU`EDVJ&&X<6p&M`Akb%a~Xon967m*FNj{?F!@fvnF7VEO1*oVO6ywHjZ_S|3QS$kbH%@^^QnLPt%%7AB z<@aY{th50|sI=MNFugun=yp!)ff2+dak;cvK8@*w@X(W7$I>BiEu*k@&ru2!3O5aT zMSBCCsw|EtvmL7Y>NC8hh!pfeoP6-3@4Xs5Do~P2X=YU7nW*AGa#uK5;`k*tJe1mv z2_Hv-fVJ(~&FVOEZRqvW0g5@}3( zDwd6B+)&|dra}nZw8Jb`)tBrImIz#^^Vl50fGGxI*F=g_GiWssVN=G^EMXI+45>#( z7h$;}NXmyV1TL8-q%s^3$!lzzMZD+89QIBxtR7=p3i*VIRT|G;+TOb5E$-sH-4cDJ z!tu`QAqX?Fw~e6hOJH|3IoS}>!L=1oobl8X>k z|7A~ifH(wfw#+ma>Wgv~+RT$&aMl=S+D%m**&Mpilx5`lv zsFk!Ie z1JQ@ugGAwG89-JDT|wOy9_r@bretxo`ASO!-irJ?T$|b{tC@qB~jV?J1@T?YV0XDE~%&%C{QO)5^WfS92Ic|7}GX zAeF%V)4ZMo*N*`$DwbsiZcJ|2s;6vjJK?{sdjWWJsNd+M3J^O@J*gwJQ!=5d1tQz@)S(dHW4paaejy$Yh)R zx^>5m-b%0W!|jquA`Y~i9^FWK0zoZnXH0R1SCj8OYIw&@(p(T-cpCQHl)Ld7gn7*2iMHe~*m-TkEI?MgChYuJI;dA;0I11{|sx zU%Cp3E329dwR?mqzGoSob)w%T(~6%+rmQ@|D0FrK`)8{HIwg-b&r{M9)FbVK6L z1Ov55=}BTetxh9RSErn_?>=F*BR{-ZMdP+MNu+6OKh8#j!?py=UJi=tSo-KLOeqYytH$8uc!Ouh3s zfi-nqH}v6(4Skv&BvpGQ)Z!_ge zzWkWdnW79?&}w_e?(T_NBPR2E=(^zS#X+MsHZbvbODSDOfkq;2V0!u^T1?l;&)h1* zP>tR`MDNWSO>ZyS9xfI$k{if1#@!V{OQQE>4!pUsuKbUiT^@eEsC#!dis0i9MoUPE z(cz;UC;}eKNge>XE+)wLaR6hl1*S?T%ap5x^0Gma91F|XS9r`BV4&yDsULoQT3@6J zT%LYjn}ZV-?qvlTN0bm|Te_2>!R3W`RVc2jP7uA1@krJPZi%JT{ab^ktr;=g=Q%_uQW1?f zc9^JkWH_WIN~ndO{&<2QmFlr$_s~n0g^!AHQYh?QpnzP|%a%Q!m#{u7@hcG?+Q>*F z!^IWaN~vjp?1ZqRjEC?P{m2&`{#$b$Zzz_%st(B1D`BZ)Gv@mvq~`g%v5trabZEmS zrLQp0=OaO_YM-BEP`f6C)EBMaOq9|GIU~ayt~ep`24b#+7N{O2pQoS1ZOfQ^7&nrB zx_MSL{B7TCHzXw{dE8^zR1iQ2gI>Z+<#N;Rn=b4$_RHLtN)1BdbJ zbj8$V<<$DVS_^Hsc|mg5)zB(wlxZ>c^}o=X7h0GwdI)4|J31+0a9S6W0{o3c1xKn* zh9Yy*R&Dm+eOk%j*VS=M4s00WhKVb7OxL1hsF&>Z`Ir-zU<4g{>}F$B9b$=&3V1^hm+YdI4qc_VKt5>X zB3qM?^46B>yA8ME5#?7px#B#QHp-yA!9gqI!YpJez&L^rtR%=h7GRSHlQALZTJtVF zc792Y2UU{c0NJd367hyUP2=|oqK$GfP6&#fvpD6uUIB!~ksIS-Y@`SDcX@LvNufC_ z(8dCV;SE0y+Qxc9LAWVA#vT23J_}R|*gG-3h&ryv&$f*mWIa`92aqr|_jUcwhAp0HyU~Jz=CnL%B4p%gj%&iE zedxuVrx$BOkbwQ>P!6J7KNOED?J1gqc5a&PzYpR%K^`U*t*I7_lDp;Knhw9xsa+eZDMF6S_Jj~CY#A!MHkOD=w180(2 zZm}{PKrxoed){d#4T528^k$+_r;vcS+91Thlgkn5HN?9L9Q!8XQY-*wd3Fc8x_10g z;+IO=jJ_3efU-DB8g9?z$C86DBFj`0{h^_7NJnM=06PI&0V7a6Zx=Hy4qZUCT@17YHY zm63tjgO#w(Bu4P{Y*;Hv;B!3=tcA+VRLd4g^ZR5BYx{u@*?Ov^Z$SiegjhXdNi1E<6jZEuDyj-+F=@7(aGc4n#qrWtJL@DpRoZvyds&&Ed8V~cJQwXA%bXz@ z3`y+F&4UX@H1jPq)C5#uAzMXXe4YwRWeO4!ATZV_>wqK((|x^oEAZtAp9LN>cJYU{jYm$H3p5Hr$(0UCq6!&1-I@DSR1qiW$1Nxs`fG^E>*; zeg(R*iqf;XT5NHi$!Tq!x=McD$Ls;xQ9-KEm=(o+U*n|ACOwPj!hV~6B(e!sTW z+|?%Oq^AK&;Tu4+dKz*5crck-1*-kZj+&GM<(uXt9I)rU3zMJB9He#T4Yksr*oP!&!29DMdf8ZwFzRB4|a&fCUGG46w0 z6If`wL`RMaabM$n#HFmNBI=5~kIGx>FTaqP2C48?hOB1fUZ!|xnx+=|%&!LJ_Gw(d z##o;ZLRVT|;v;k#pg{5pB)@4i!PdduO$w$`vG>UWDPqDg8S%qvx6L2n0LL9#IOYJ? zlh0~Y4JHl-EPYc}v|6twi(Wg=)yc=QFv>XQM@{9#t1-O`>BT=-&Zput&2Xj6cbghY zVYo1iX6II`a-vn{P=ZA7%S_UoP%A&XACUDh_8u^Z#DWqwfAHD5Yku}!**(4;fc{#b zXozzAE6{a}=N`EANAvzy>(%J;?Dj>UDhZt&b+oZ*=Ve!1*QlAiS!qJ| zMM&a-8MoS;T#j=26We}0#9%(c(v@_8(PndP zwhM?dUI6v1VNE`E8-v&xK|4-`!3`*MP$zKlHo~KY=u=5C5?n*BQa}Nzki_v6@|Y3_ zeXOa}iEZT-7qPL}o{BgjO!7l?e(FTA65-reEBk%|O~n!tAw*Sdi4}Hy2~2l(hB4!d zL+!9$1pHIc2C~a_&Ar_&9Pc!;#kBND9?36kiOFe~@Vm@s2NJqFE(^l4OYMS`zODV# z-Ku8yA;~A}gegOX`zU~xg{~5$Rsqo40}ym#iPI4WHsH%12n;admS#|=>;+t1(A z+U%Co6K}v&4WYsoaC@jt42x^ZKkw4*t7*V^u-p%d_4L9`0~ImQo9p{IdlcvK}0( z7x2eMwoG>yzS~P#V21hN>R6$I1(t$<#MzRl7gj(N$`4xp93?BL={_HW(VPjXON5u% z781(a!8<5o2_a|sA!Ga}5A1!SP5=X1rHD~1o;1Ci_*|sDA6g&lj#=8SThEqlg`19# z2-_0eTMi2xog!t?!yoK|fF%prn~jkZ@q3AuA>R?10M_DMLo}n9VNANQ2h(DDH^l7Z zV%O@`J5yF{-K^+PW$WCY8HS%K$6tPu*Nd<;IAQHC9!`1ol##*=yI_e|fOp)||M*G^ z$2+uagwPW9Ry$agb&u*?vwDHwY zec&K_Ml~C&1UQF7AGb&z*rYruli6#B?Jc(Xp)D!f9ndAzSgO(Gr@(MDd{nM+J;zrtm6V}NTg$@WCvY9*@HM%(d^BA=jvsAmdysr~ zN4#eMG=hyur>d2hWAgBHni0Xq^(yW<#{2jVv@t(jDtJqU?3A=4PFo%cG%JDCA-3PI zzwxcF!x}}BA$Q23is^wDUbhSf-8a5-?cHmXm_~gi*%l7!hGRisFdcrGmd3;cCprSr zckD4a6l%>0bGRM9^HR5(EZ%qFP-g|F$*!P5N`V4x0$GLWtP7^kIfV(gM0l*u8zN5= zNx;tH1`?GaY%vk4CL}52KJh66clqE!wBUO+1O=C6alwEpwog8wzIB!JhG#oz(1Fty z@d=riVaC(KH znW5Mra3Sn}w8|v+`2LnTBfnKr zlRaX7YpD%e3v|19)YoUQQA!|vrnQTM(>$;ISDk$3Vf}9n$ zE16bon;)C)!_dd}H9790#Gcp=nXs02$a z<;f3BlJY>}?EtV(nFr<|@aTq~#voi=NrR7ivvDD*wm0Vw--J$KuwV>fRmAP#C#)@z zL_hy{ZuTIMQg>p|dI@o`bvrY+?Slw^%CR;?I^Eu2dgd^g@u$jR3zx>$YpI9o{W!n( z63DsNJNr`CP%@sNv9D5bZubvJ#j1n08BF-`P2DsYAGQ+71i^&MaWz2-Q<3Gfj7}c{ z2~~)hk+~C>gv3bQ&x`B?@psKFKf(&ENXc#pO_u|j_)_PB85mG74;3}P-SirYp-p&V zrj$+KLJ30UJBuXJs{D2GeoH3qd{w~^DVv(#NvtSKwbdl8DPai1Ib1hj;J$-a1vYI~ z;joFYS}#N8)|>;fnMLUZqtJve=`oq zHb$3conL=ehIaSh(L&&`L@K(2&eSn5eB4=4mX1paF0E0$A!LIfHH znO#!vM5s3}{o6+5_qfje6-=dSfo-uq+M*jf%XHMr1ok$miz<=VIv@H&rW46+w;pYD z?a}ZVEa%5R^dqK4j&Cy_$J1iqt7u3ZT0{Y#z%ukusVD`7Uc7NFL(Cv6q!TlK$0i9U zH^y{{zy?8iyf!?_I|u zUdIPMCCcT?=fdkM(O(cC=SP9gM@D*1f z4-WdOD=qIpSUwz(ssNOrLeKnJ@eNnL?PPEjH{|_-e-{f#JzVHabG??EcM5lp3ZNkr zfi>nW9t+PD*}nhHl`ew_d?8PQq0?0;_eoV&9;*jjxla5wg2^L{b?aKBq?baR^p<{i zu1X2YY?5`$=E;I4zqMfi4zvpGTQpw(p9pQRvv8qOvp@|ZGN4jZ)1D^}wjl6kOM#Sm z5KmAh-!>mClddIhkVu;+K z&chU9@%XaEadQJ5$0@LPS7oVid^FLs%dN}hcMe27nip+VIfIWQ3pIAQ=TjZMp(x8w z`olIqn}C?65{VdNE8n&CswbAcb>WUSjXi*J-Pwv08{^q=<*=>g7c-W-PY5R%3f}vB z#@^4ln(d%&SRV`m24mu@EE4`D_(%nD62dBpwW@3$VR&<1R@t3XB6Dv$ydLr%)8K{J z-E=f*qv49lq-wcbC$WPbW3!wpJEWDyenJ3$daE%zVq_BuJ7W!UDN&R)|2=^_D($j- z;U}|9)>N4-<-^!?^3mN?HLNpER9ZD@zIs3WK)$CHYxVrlqPS5yUdCwNJoG2d9U8jj z1m_hBJ;xU@ml$smoP`uom!Ce-DM*O~Kf?@1Py6mQ8b3iz>@NK8Z@Nk;hRA|$1a{Qj zTLICYo-JcP!LHXs#Zpt{@^UI@VvCzSfD;rkEhBWar$+@7f=4Shsnc_k-^yy-;!zdt zeAbh}RN5zM$~_jr1>cBbn$pMnzW%X~>WYW+@&67?{L)LDhap={f_8+UhicWK;$LvVt-Htqy>4ess)3GVLh5F7#_xVu{}Yp=7NhvrHwH27pY4O(*6Nru}o4x*E1?Y5_V1i3+v5>oN3M}-Q zI}K(c&flA>d!y4$AUzw#uqTVu4C0AXbRjk4q5O6$CxOK0e<2{i_81TsIw4H*34X;qdj6wXCM>Usw#R^oRoiKN zVCmT>nrmHu%Rugmk*xbSL{1?UA?r|OW%^bW!nhp<~f(9w6 zbj;$*3!ND}1Yb@-epFOoCNfYhWpm_lyS6MRt3m>Yg4=q>L%hrwziES^XqYV+qmloB zX;3O-E=X>kg3{ov`K%n{2W?DiMd9K_QZ6(KK%~4RO@xQV591!x1y*)Zb?_N#Qnu6tOqF=dMFNK8x3a zfi8tX6p5`SUjea{fa1SesK**Ie;dv#VW zs@;JPw|)eo7dd#xIk2?;dvLR)UhjT-Lk>(f33|;`0#YU%unsSeHJ>FD%aWHPA{W00 z;+*&GJ_c*~)|Vh7utm%K=75Gxg6~cVtC2F-7=9ds(-QWsRI_k{c_z3P&G;!ES{Yo- zFzSd6%fUp6_d^~FAw(NTRdqy& zXUh7L1aBF#$UFRlAkJKpP?fj|4SmQkt5+=I!%si2)z065kWPpNX+mfihaff-ZIIw2}*XQ;K# z*H{nf_4zt5usLCdWT%zAQ5-gAj5!KI`Q?U|us@z7zvy3P9(>t8w zosMVFqSP~8*)v#_4xPyHB%Q7oB=FuY!4~j*cmPv~fYI-|UmhyGwlb<1pF*#mMJ^tH zmM>CX#1bg#+e!RXG{sCC;~%bvZ~vFXj_+;kkp1pj_#*H+ZgQBx=#}DmDYg-Em+QR>5>)vbm|L_s+%Z9$0N85-*tSxxy9I;xBXhapKP;!gTA#1#1F@=5XXb(YZo?3oHXZh0> z&N}<*yVs!;zkZBaiWKYexk$k7bA(S5x*CuFIYx|BpCFu*C9LSK8z(qMgr7BVKO}+? zM(uh&@J75|4zI0DgNq2w&k4!>%Q>?ZYPFQcTMcRQ8b|5*r2F}qpKwkDhq-t81;t2m zq;E~&?=-Ni$d3n|%r;;WS;&*GREWmXxO$5i5L_i{=2H=(urS8-a>#~PXAYk(_p8^3 zcPMa)0LXXzx$qrl1?P6rt+25LJ;!a9`VpCpp^hmiYkbnJ($a_pfxH`z*0waYKrlAZ z5ueeRug|4RDm9%8qAO-shH2J;H_Uttb@KbIqd=3ek5jOHuI`2)?c-vVeE`7`>@?69 z@;=I^l9txt@;Lio^%ofpgqQgG>awYfa&GS_qt<(iqof+ zhfhbo-JsR=Z#jlV5*!_?d;o8Gw!d+&uPYHVl1S(3Sp2;4MV@vG+#a;7%;l%TvGP-m(&XHV+)4b ziyo_CbXT6QY3D;jH#6m{qb^weNuL;?T%DxnPhVO5Coo3l z15PHb*krCKR+ox(gb#0x!&x&gK;+pDg`dV)YJ^B1;KE7-#k7m~@i(j)a2o^nKC|Ry zIk=wx)O%vvShRW?o#$#BP>cl>B+Z$}aeM3QH+QqecO}-b4|97gPtXsy%M6mCI)qEc8 z`R@?Q#gJ1+s0W|XR0hZ+>7rr=BPFuKLqQg7jTDi8MyNeddm%AF5TnxpIRdo-_vt=c z>~st;;e0sg%GOTc}0q0%$zI;64Chh7>^73#i(X~f zRQK<@tmL;P`p^0<}Bw%2Bz!o z)mR(wZ6{<8n{D)-NKvvMSmF(Rj6t|o78#a#GH66HrV-vuIT~+(4e!DA`w*hS;AY5E zH>Nsb@!%MV%C#W8sL&ljUk?gHQ3F<2%4G$``HOn!X-wXyu+D4H*U&;RNgRGuBP zDm3M>^FJn-=*mYe#Kpt`{B#wr#qPW;h>EMuA1t?n8S@e5)YfS#6(_fPbu={z;=2x4 z0|EGq`EQ{7@1ICnEKB7NVm8x*b5L5s;pEew;V)11Kxx6R=v=9Un;^Lb0-F_xMs$vJ z#u__2DtYC>W*+sJGbRE8M8EDIQq@EegxbUn??86`io$J_lzHd$<9AW(yEC=dF6Ml!Jx!d5MU>9Je}oYA zy0h^>mX;)`JOM=Y5mHdZxqm|vDe5V-LptY4FVy5(taoP)kcVXy%%1z#z)7?W*MrV@|Y& z-jB93`&J;x`^8gyk>c;(e}sCrSXe0$b{Z}~r42G^YOTmGsQm#hM$4nrk$pS9I0QER zAQ$_*8I*D_Ytq3O*ZK~-1@3nD=tRiAR82-F#bJgbBW|6Y(aU6KP}@PO#)-<03YWXrj!KFk7}=#b7yzW46EBehwFea)%~! zB`h~1wxGwB@SpvmOD8N9|FM2#v%)G&XEzR zC>ZX?u3ABl2!p5nLBFAl+cCQWIf16Aj&uog8E4vrJSb_-WeqCv9CFmFI;8g)=nM&OH(*f82H z0s8&fXo|T@36n1R(|S0VOl;Ep%<1W8VdE~RGGp{v}j~eH{TaLxO!aXk#|w#HHKVP8Ix^*F1JxkOZ~jQ;kx6kjEBSu>;%I zNtt0~RA{n9TWn!Uk$E1UA5P?pI28+{Umm_+MzSZG=}4uxMJhB;RWmwrju-2(EM{wB z;|YOu@AklvSMOM!YDo@AQRE*`@*t)5-BaTRIpjWHz!rawxE0uKlTbKD_q z_wuc%r(Z+~?|QenrM4gF=m?Xcu^A1gOqNVkiNTFrL^1mTMyNmL7c8>v+aPpOP_K90 zzE#}OZCt~S^lD7lFLH@2ojOeK$o~tDncha%5`e6h0;{n(k z8qXDATa)TlgmBTjx;)Z;GrQ=TLpjK4H8ZnX0&Im)2Jb05Nj>bpQ?dGk7zu%B)0Pos z)GRh=FkTocox}jMQsC@{GaD^4uL$;>kW>;J1{|E8gA2e8F2@Qwp%De?Pvo?d!~#r$ zsC}(hrvwE1?J&7Ss&gp&!PnS~GBcu7_Pw~K*>v|x)tRinPh4|dEL>FC z4i-dD9>k=5T~|_ihubT)X7R7Y$;n9)F|n_a`0l4wxCN=(Kt8eHb`#oePEcFcI)M@3}v3r$S zDFQ%yU4MeN8bSMo8kcMiye0a?b#)d2L*YorQxh?v*U51XBLTx|8>w-i@RQUB_fi}$ zXd`4fPLG$H9QS@}QJ0Ma1~672u+6^>R8(5v%bgUDWnJ8cW1Qf1m1UCg;!OxEwAM}! zUmj+5pi)Ori;k28LoXq!5#(w(J2P z@ExwOv8K1;?{nDAwa92*90Uq0sb>ARIQhpxgd9Az$`Qp%JA5+AU`Hl>IW=D zBW{e~j*m&a#qf_#8_|_jmARsGb81NCMPbyYkcgjDa`?f?HX#4l*VA$jINH>!c`Xnc z5@KNGR#0<360E{54H;jA67BRVimnB3G%^%!GNCA5)CD0;Ed}WwpMKUCsDy{1-n-#uL8{;@%FyEsOz;#LXQd)U<+6Ow>j)ZCa@2bJlz0-=DbTiY-Y>~hF zNG-}vNuLYU1zrW-o*j-o|FWNX1^F;<#Z#?yx}n}QOq}VUvq*!__rC?&fgiU(bl9USZOK=E>2JPNwh)^D%vVcxK-nl~qQ+&H(<|z!%Olm2KtdtwCuys)b zzZJh}AQEav{p7pdY_+2azOTGUYoxRKq&u6}E7E!J-#+y~Ne_{8!&BCAEcKPLx9Zs1 z-;GR{)TlbrIla6Yra1f-z>$Dn<#3Msqq^(3s;RVpfVT5ikj)rKM!M9F4F3t z7nOiT5y(*^aY7*u_|E>E^ttJWNe}~h z$d(U?2?EbNrYVX$raH#X1S*(x9bZKk-R`va^6(^3gOm`4?h{{lS0xO|7%8kd9NZpu z4m)3T#0o3=+IBYYnl{`kRI_rwqs14sGPqSZbS3VqUrTa%JZVhE5h=Z~9-)z{rYaciF@z=qrfEh6{`?}P)Li%{KeqJXauM1Ih6Pu+%ynXNxFvc&wq zwo-Lnj#{f@z+l{-A&ve5=_MPFMYIB*K>hk8#44=0$2gG6&iH{g%@EH)P!u@;qftH@`}XNCp1yk+sq5nc61J zQcb*}r&fV3Wl>)|lCZ`w$=H}AUp3T38AX5d73s{CIgbAVZ(Hv`rOdve7_+wDnj9Zb z21k(PX5QUc)7(-iPKfJ*{8RS7ZDA`U>9v4(3taR)<9hG>)x~Hh!Hm1WNREE^Q+PdM zn8~7WUvNGhERdr9FoI-6by~=y5JP!Yx;m6B+N3oB6B0&YWcn~%Uw<5y^`I=ojgZxqinrb#2 zZVbd#jO@8+ijUNBW5@p`Jpa``mon0%1a4D4!={ zjzH`g&22}zyl~2DC16j3m($5O99;iw46Ak1l9oB&USDYdvcNt8u_(9HH7d(dh@a)dlWHX1kWU@jvj zlLE|^Q}~w9AzM^Unm9S5eWE47j4Y;jhU zfGXmb{usR%uR_ZYpUC_A;wh0a>u@;lh1~l|0AW--V1y2#CDDl86DG*z_#6tgZj_Bb ze_)gqkiZdr3vd`y%uXt4S4O!mljw?m{e@1}bGLP%i@YM#5FIJKWZ7YRj32O(CpJ_H z5JRuTC{Bzsf7N1>L%aFX=m0~+Mhv}MR88ZHE{6o5Mon6@bl&e!g}OABKm}QWRhH1(^MUt@9?+y(3@m{b5}gkq7}7KSPtd2GYq~|D(}YU>+ojalm7| zp2)|0AYeP-mrqjPQ1lh^jzNPAABic}*gNS*AW#93s1F0gv*1gxO7g|lj2H(JLrX(J z*}%}ZX)t#-xBUhF^(tOSFIt?t+PWgOv6rYqHLm!|!KzXs*Q!;UbSCY4gu5LW;=M8! zqcC3b(HIXgijGjRlI7GPZKSK@LxzjF)y{1fdSYVdB{@Cfxls^zwP>R?KD^d!`|SJb zE)?aWhuSRmz**@`2!gQqKi2)H3kk-FgtW-5Icd<{38CK_6^s-I>|}dRhMm`8Yv82q zfkyP<)_OL1TL&DZju&(ahZ3n+N(OxdukAXcZA_6l|%|^zBcmj1q_fiu< zN`XeAGdKXp-Wg`|mARSpC3>|E0W3IGT)PQ!>k**`IO&xPS`577p>5!LT^@oyyoSkH zupTg=K-3!y=zd%Z3oI}`t z3kIO|bvENpvj1vf0dO2wx=m`=mQsK5UIDFvUq5@pKzbwnHlnFn9$d6*`e{<)*p07{ zk;D2PaTt{6+2b5r+B9igo?GwtOoc;uqOvrKQDMS#mTGxBw`o`;e~BbRv#?)hZAZmJ zVpqvOl61HCZ}*KcYV_R#(oHv=r?ABOEM{xg^fVyf1Pa2pF-TbIjh8lZ4*LA*hbXN1 zDPC<{t<;S$cZFni7Qr%dWKCc3wYg}-U&8e`ThUfULjet@F@f6WTYa^}Yv~5nc8^`; z@CN6__ry+rYt_w|sVISf-c3&Ri@cJ<12v`9+;cG~i!fh?q7#w~|_yl%LFKiQU-?KVWJBOva1k`TT((1k%0 z983v>?ptK*9S{RhcisTD93;FLLezJmYsW3jH%<%~-)8J!0G}6oA-}<|1ypg(rBU;y z4Jb3%jQ-r5qj9nwv=N}~4+}Ya9)lx9K}DkXC}4)dRsrPV1rT5=#G0(wcsR=b~}v*>Q+?yz&`3Fz-bcn)>Nl44wLQ1`tC@A}y2^cvp1S$%JV zqwC-eH*DgsVeSi+M20b4G&?R~e20OUS>TDtWiJ!E+qHk+Zy$L=yVTnQ3+`~IC9^YG zAJ*^y-^2=f-EVW(6XARclzEP(f>$nyTa8AkyDF@CEx=mC;h@DwD_|^-`YSUHP0qJS zo38~suSC7S3;H@=rO6pCAi=Lml;ZVR^+Mv%PgujwBCWMwHX{}*|HhojT8QCNdVW^# zK+IU)(QmrQ&iI%Uni{w#(Y4GZl;p@P7T=g zVK}=*#`n}QJtijw8nvFVh@n1g&1;4iOrygfRk-BDk0k1)X0?BAsmY=91lmh^7_Xun zl6#nP%`U)amEj6P$r8oF0DECMx%(V2DXl^B!pF7ln(>F{8j~Z^k5O$ifX3>_W zxb{N@=uENwHL)WH&0oZ4 zyci@}h&JLwArS$_NZ*NfQtt~O?UTyJOK!%|`50$MgQw=2zEz6LbRbzIQ(`2|%1U?d zfME7{$HQtK>V@zN!OPk*?hPA^AyGuVm>Ct@Ck%|lcx5sfx&!?)>n7uV#nbbnjE9%p zXU(r}GnHCw>Do6n8|VIu*hG>cF&5$XBa`9hhenGy|maM`mdpepO48)M1yBI0l@&Zcf{%a}`eR&5#ia@7v@OOc%p1{p5#&Cxa# z@4fYU5qURy*fZDe-M)DH**F!9*{+~I!65LAUn!jycvKJD{#AR( zT@3T;Hdj$rHt|)zo|g+P)FFkkgS>@4qOGAv$)K2bcRhfuQf6V)QV8v0_MHxezdm(K zpJGaI2jWnSLt=QN^ts^REb0Q0A| z$}V#3@iMYpRp+uVlARmMU=agZvyR+|Ioc$g?@-><4k;7e~LsLL}N<&5fav@fO{*gnPKRpSthkURaerKgk(Bx8eg^=>zhVG4?i~!6)A-z)EDY4zQ&|X0{x=iBC?~peqIz`8uQ^_6q}h* zKGFNS!F)Ibv;#WQMJmdDHM*cmUb8Irhauwh-B+i54e{izU8t7i^sRI%Vc6eWwDmBP zyO~bEDDiZHIJvRLYQP0H1nAUhe>IG|QA4rHepoltK;MaH#3?|UnaVyreAU+P`2IWl z-X9&xEf=)*gWU4in66m?mB13ZfdJ>lb9PGXYb0b10+*GOGKdxyDq`q4*l zTsg5JLO%q-WZj;b;P8A2t#cbt&EO4p{8Uk9Z|0b1StcI}So>CXj2_U@tM|SrTSH$%MYF|~8Qc%aG`l;>WhFek>p?$vki0vc zMWqvMtvST889q0^uKD*w?&9!1GS%(^$!MUJczac9#Ce8h?V$wqOtS^=I@}@iSzMF4 zBTR(w5vbiFCKrqTo~pxooD3^^`!t%Y?*bQ|(Y$3wk9w-;);QNjzqa?79YkGu*-c$r z5_+9-FiXDQ-s1~=F!jY;F57P`haR)JE6h%Db=nT;!|l;&6kR^eCLvX<{O|;z8M}wN zA-!UdlmJHaq%IDfHs&BC03&a=#4fHrM@h1f2sTbmG%jycw}-(L1&zE{4VGuRhF^V# zh)i0rpK?=~g+ z>MT^u^bwkyM*!6)Ch?2A#?IhH+r1%s@_O_?g9p~Fcnz;c9>ec42XlTFZhg5mp=8)! zo@itCb0Fd*(}~O2;Te?4ew>8O?*X)sPsPT463vf`$Fuj7kK**Hy8Yl(>Vx{UFo) zdB^ed_mdt?KB!q53e}M(oJ@tw{EsBkGdZFDuIHi&+U}0ZeW!%kf)M56I~2}iD#CSn^+N$CaDb7fPPi2Ux; zM&>v8Y%i)9=usE;jdc;>56e)`k5CFrt1p)q_F2uQPM8Q}2DIdsR)w41x!Kn{QyP(z zJ_2TWbfN0SBoPkiXFEEg*S`{iy8g@>WYx-ghabNxT@#Q{K*8M?6Jv>6Zd<{<+(;Gx zIKl)8&%)9;6gVV;!(AlPe!HR}*^r98uupOwQo zJIXp~$zuZ(Dc^w5Cq%X`?i8SCV$yT>n~KHYWk-}RM<5_>g&f# zNT3pMt!c;mtsrlOS~@r9H^f=>oJ!Ua9ohUY1Z z@f`(gq6%4&UIm54Qo@bgXfA-bN3C~l5OXVurt)o*|M1JQ*j=^-b71j@{5ds3Y(Z6+ zqiqN>H1)o=`ClB6M7$ifN0I+XXj(osBhgwKJ1U$PZ0sEn;O4jfzPE~}wjO|538mPl zY|ElxIhxA2!P-g0-lSHB#Jbw{>5CrUk!c!u!6kPaARK_@{rYid#j^Hd4e3G%OypEd z8m7D#sF+<|MnQr zjf~}89|ZcVqEdZ^8{p^YkJ?gFZ(0V$msXrrI$kvbU*gsy610)r!v=4UUk5Dxs3+0; z5*#CF8}SoDt6i274=f%JbVQ(Jjg4&tjak18{RM0@7ekuwze7M>Fe{S2Yk;|h!oMr- zW*(DX=}B}M3!%c^QZ$*jn%X9&z|hv9T?NoVxItLuyM8hd6D?-zf*;GVr-I zh>)(M2VevR)h(IZqxi)mw==Ti3ysj%)cU9~QB>F4&SBisJAke1gbBXtrV(>KqzLHRvz-Xw%_n6AcT?*W*@6UZenWs9* z9C8hbFF+ZSIJ7m}k!fS^>+%h^O9@H4_NU7QtCM~c9>2#6-yAwlTb#x9GzZ2NFsmZ;oD_r0vJj{~hMu50 z*yc<&k)-?en@A%mLw3QN$tZ+3j~5~5>MulqrG7kA7=cM70AkgaUn8QM+Aa`lJsQQB z9WHa1{GS#5=Q2i-LS*`&FO{p_xg)egA|TH>l2g!iGCW(MKoIM+H|07czDauef@2zr z?c(N$K}qH9BiEJ~arLlV8Y^OVQJ42FI;Pzaf?$kT2$30otURH-O##F9J?^a1G=YF7 z`WcIf`Qk)`^IF(jie2GFDaNRqF4dJf{dJC#A$zRtX$WUDxfx$cHoP!IPG^lHvg}`& z5H9Np4wF49(?3i$f2i4@;+xnWXupS$E4mgkdvvbZCVNv0)Xa5lla!uGiYQj_o*%|6 zEw5L-Ov>#V!LCK6IW3=~Km6pQlmBr3vQrz`BcG3mySF}Zm$*jvkI@B8Ypj!ZR}>q& zDZY&Y@?=N=9_GY*AVGeT)3rjx+%_xXsbw8pcRJa9xvFZ(_2}(#3s{@^(4ZD}Sj%)+I*PF} zhu-bpe?;&t%ev)-bWV>x=7_}2c@af>%!e@+%ce!(f^Mxg%%9ayY&9%|apUn4 zD2Nd>wYvYs<$l%|dj2^M2B2^*vh|5Fd1*GiU;?Ids8jNUlNkDdviM?|~js zMk(4dvNqE@|F_KAMt^kQK2{nU7syHiToY_~#qJDV82@HH)7iB> zwBtUW`fbB|Ie)`RKHQBIWJ#ONXM92hHv5!cs8M_EM(zFQgyw7zxnWJ-VG~qUu`2SW zA7|HQHf-QgEy%gY;(aac+g%2MCaHh7^$pw?>UG4^<^1bjaMc`j!?1R<+jAa^m$W|W zjnkkt-D3~=uXAqTc3KIyM5R}+P)m6vK|C(%Ze59d>(i09c$izNU&%3}Zlb1<_u z?L4)^y+Y7%3E_judERfm9` zt%R@ma%d}y3UHGtYpCMAM!n8NH_cMWFqQU&r+%*+Zo##48I__4!FDI|OpX5UPje{` z4lKTy(CPW0`nQwJ#Y_O}40danqK9KfZ#Wli9G?;Abz{uYA;^PxwE7;s2j^8KD z_N9?|()i~75K;&<%L*W4)5gRDP~tQS4JXC9u5%Kpxf8X0&xY6)QRQYM4g83Z>N|b0I!LrMC9OaD1+q!kYD;3cvavCCWMWm=F znCcw5t1+lle4%G-8&iCEpKERMl@$SUe9di|C<}c=G@KMjd}Wf>nPBX1SyQ8VDE<+- ze{3W|^90*rqP)~eflL74R29uvyG{bzR1F((4{PBfYPV_>Fq(^}dS~!5uu%`E&oef;nZt9@Pm^~ZG~p-$-!=>nh&|&~U||vo z%_+xo{C6##It!43y}AnfvZ~S?33`7RkSOIc;;>Y9o2O$LqkI~DK&uYu^$Y|+;7W> z{XQ*rJ6n!vkg!y1xE_5cXi^algBvef78m-&5U>$H_qc!L2ug{xASdP?j7Gu1)J7Ks z6*EOk>I>4&(Jny?B??u46kdb>k4}C;ltcf_rJVhERmmLSij*ObMu>`|^m^q66Hnb7 zA1~GB^h$Sh`Zf@}e{T>u^{9$AN6CC|853A3vD5B;*Z^j&YS)gB*LNMB%644N?zyi+ zQg=LtzNOgQyDoFUXzlC-b$!M1F)55&(Nxg4tyG(s_u4rph^B)gnY z3QmuvzE|HSRU`R)20!7~SpsV-u;=v)=|~0ACVncNvz$Y3_7tXq8ie40qMMYp`l{;# z*}a|>W}7q09lecVn>%d6ZqD8IMfMIdU^dUgJgffDw!jUhzwSQ-qrQF7cBK57yszbM z^VmwGt8_vFh7$H5H2$q5df#AnQMv$(SNo0{@D`xuN5X+xww+D?-!uDes%2y>?fe0~ z^qvd+T!E8f!CrQv2#6?xIl!cH`hZBqb+kiW+Bo9gfGxpx4ptOVjbzPQ4u&8c*3R9% z9ne>&F$=kdYZRQb*p9U`k-wWERp#|;qKo@d0%y`njjOW=X}eJ*c$s=k%7(J>+vWOa z?rZJenou9s9s>Z$VFmCgT5jKY#pf?a*3l)K8|1L}nFc*<5zRE1zJ3jdMo&t@BOi@5 zkC){W9t28qSDBonqp?`DOU!<^U8YPyjp;x91b4}@(C6rmg#u@m0PB9S{KHas6h3d3 zqYm!U{Na;R*bzRvg?-6M{FS%l&*tjzL#TZk69*h%+b>ZKJu)?p^uK&UC80zkGi&6& z+}?-MlAbO@gg<6lljOUCW~0-^-Ch_mJauX^zE$lS1%xg@mN5O0FMKcwy03`(T{$GT z+xTC_>2a^Vw?Efp3{v#D`^X9e4Z{Vv-rbnf>xf5rv->j8HqSXezY%TFjkOCfZv@Yi zbeE2JHJ*)S?liUp( zOhH#=gBY8Kugh6%gfP4tB6I`B$e4|w_|#_26d|=v&nKWbEDTxE;}!Yg4&!bLDS1dr z1f$qC1s$*lxN?-wc@#dZ(mWz zn9Ak7XXO>Pt5xdq=#=SyG3;`}Hjo+>(8lM{1+j&BdF8sVp!!?tZ}`_Zp9@56+8mhY z1(GVHu7l>pSc&=g%K&G|j)&O9jaTMZcOC6cpG^%!L7$8DocpO7H-0Uh@9^ebnai-P zS*siNf1hQ-HF7x~m{Lf#Z(pFRYKfP#`)wSKT3nPe=Sq6IP~x-2Im(g{b2%Sk6J&x~ z$Yn>sOC#Qi&1}C29%gO&5EGSbGNF0K=}P$~#B?iZ4T$9i@ZK%&!X$~S_QuTW!dTsb zd#YVQ`SJ5RpHOO2Sy>%a&CxNsWR~I$EuBU(eY0-KD zF-d3tp7UxRvc`UM7zd$5dl$zLCQPhT6yp!fdg+mS=3o1vNVYshLY82JyYKOs@gT-Q zy~RP*YaQIsc+K2|j%IEdZo`Bs%DsC+u&vr`Qohqo?Gv}1&6I=5H0J*EFa)6F zfy&)f;VmbEow&vde|z?Y_egvh4($c{873Tp7J%Zg6ad()PYK*-G;2Uu=N_@0B0%=$ z@WHRg^$BKu4iP2kE}G9>)49YRv9ohRYw9C8aZvt*l;43C1~Ad#145nJ(Y7Y+6{sEV z2NI$DISvI27^pRaWg%Vfmu&3N>iY$STZ7HaUMvRdRc!Ex7b6)k5!WMQqo8fOw2X3p zn~*ox(JPJ599e8Wycw*&f%|e-EJ1@yB2l9#y7jfp@{ETVWIHsb5t0!36K?v&_}Ouc z6)bcJhu;i3v0f#w`YOtZYU;lmpjQY=L_v)|X|TS`H9f_K(_9Ji2|Tx`H_2f0{a^JP z&@nQmorQo;wU_`>7VOvtkc*0)B1SF?1z7iYKJPCHXZi4pfk3gID3|p#rkOIZyLEAT zg7C;sK0lm$9|fC-UchM~ibB^Ci}&L%9E0?f$U+T2h>9gPC}S$}2(vKidEHuI9Q(Jm zfWO!K0UoAG9ZOz)>{VQHJZ3x$2g0ICcLkLbT3UOB&N+7M&~@zfL$zA>6x(kGzuav4 z_+15m%bh($1T!sG8AwTV+EtP8Si5X3fK9q&BDN%L$j!%w_ClW@)QV+i8^-KR)FC`M zVhkTDl%wY2FT98%II6JskN&qjp;`on=Jb6ihd^TFxa4#A-1}D=UhP7uHLO81p5wv&$LgqI)+l0TFs-JEtlMo6R@@gxmD-8wX8-cljoY{$K(RT=X-MmF z9|s*VZN1oE)McGBJ#NP?6?_(*J#@^(vxxyT;IaqR{d74 zJ4#60U{HRDxQ8Ozk*SlXsVe=!XU@NL_Uk}t#Y=2A%7E8Ox;m#}WYFc_h4!>~VBc1~ zG_p8K>{3RfQO8q;F@m)N+^>lqrv&2~qIea&{G-}%;bygZrk~WjM02;)Do(!JA#sX( zEUxo#UcyjSOU#!i#r#*(;{CAO*KYM3PJoMD~Okkf9XzGZ27P@3|~ z%>kNb(ary;YX8xIR1t}=NP0E4fGc23vdBKo%tYlpBr>yH12_uD+{%0O&h0#y)07^N zy)T5dt<3);>Mb0i3frykp;1y$QbGY~ke2Rl=^8qQ?rsF6K?&*Zt|12yq`PD2uAvzk zKAv;l=X`&|zW3huwXU^(Yq>f(&opn$3nEIcBc^c#s6pWRNvBa{bJgo?k-x&441mg%`_*&P{ zHXxV%IHYD;$Vsh@tjLda5vPvc>{3QNla02LX$t$8WEY=>X-!}}{!!9ZxQ5|*RKIFP z?8D2bvkSt1Pqt%?j*+qDe>PYmxt-Y`Dr_0?M0x^?k=Bjq9HnU06{2k(u)Nl}?MfFH zEn_hd_NlQEery!;!ECZm!INcXUp-W~OWpw7>xt_FO+;ypZH26-Vg>U|e4bm!;pp2L zXUnn=A-{L+3J<+Re7Rs83qGEUEJ78w{9t#ToAnIi=J#i~)sY@CyF@3es&&$#_bgM9 zM%-Dk-e~^{`FfkQwQcYE5LF6-C$td+go^r_R@KntM=vHTt9cMWvcv>!1w^6LBJXSd zpjxSMz`!2OE0?C^BCd-@qxawJByVU@%O*}3b-6}2qhzo&G|FI#;HbBbta?c1wJF0t zNAW7Z^xEhyw%ay_?VK_!siibPoik^bd8a^^OPHev?uk%XJ%ygdR$LSGcfaOwHIT=CFT(qxlaocU>rIKm0c1wa z=!;R*u6a>|#ddGf2}5wbwz$lYTlbm*TT$3ef-GCSiS2;|GHATP>WE4!x+ntl@f~8r z89qtxjI-7pJPUnqCEjdAHdToaB+t$Z5H=tfp$Y;?X~bd*67=HJTeRu!=a@!;!T`<~ z)LYb+#8R~O%v5-S)JmYZD--x+7RlfR*+Wn`8Kf^R2oSlFLjp{=*^J7hUU>d@H}Z)0 zpK;?=V;A9*=`y@3aN0r2981Kf6Dacckh<)$Z!t{g$9P1*ew(@UQ5%4u9vB{>+I%Oi)xvT=^fhY>af+2cCy+b88o=3zRk3eeEp=9PgB~v&!$I#?7xws939Cg%j7f9n zl2M+-fIp>U4msa_ClwFx0tUEvcYSXOti)EtWPZ=@>tAvy6~wG{L2{#ksEQ@Hqq=QK zvhKp?c1n9&Klh8!6`##c1VcG%HxBOPu9I({&@`F@Y%|^6_f{-mMiCb2)A}xrDO}55@t?= z+AI``gC@N4J8w>9i?ivk@qt|cmLpoGa5lQAtV!~JdLer~#jo*_3#LAb%jQ0Ide|QE z9`C}lW1E40ivXAjy`{~{@2Jo`?Ae6>{D+$vzPOqL*&Tp;JI4pJc7<%H#C+VqRv+2T z6M&)XrysS)a}-pb#K_Ig4T=+jM=Kt@--%)Lz7j7aPRot**)55VJQN+TFmr#eQlxbC z>PIEAmYVNdi4yoL{ zj&~FR15h&>ZhFv_uQgH4Y-cdY3z>%#Q`MK_5PGKDHz@~Lcf|^}TA+9lQuySoBuDsf z@p!`~ues9ayA#5*aP4~9VUMbl9RhqtN67c&(@u_Rhtu+PAFumG+6{a9gqtgemA;jk z>;d_VwF2tpGi%uEB(cx(2>je2rb^eYUJ(-&ftPq%UDybiXv84kdSIy}D)p=^L{UWg zDl|jXJ}l+%bB{{fu~d+R<<$N^jfO8tT7ER$%kpWrgFJ-0ui$`fbc1)P4>uh7dl-Ammp}rDvfH_6&eruy z&Q`$RDtd?v9B7Wt-$~WDXK?iWh2cDCDgmUrSkt?mVfrBigI?_al@ zS5csb+hu_5Zmyt~RSZjY8i=}zrk$-ci26{l1CB^#qgbID?poUDqe%5PE{VPdw}&yl zpNv*CgC`UMR(p`kO8Bl?Y~#sciNRfu9&Dpn+IIR7xX9c}ZJu9SJEy0j{7`e@|9(@a z8>wTh=4WjDTkn!HY?faTLshT7&QU~+=OT8e<(|0-IqN4<^g7)kL7%Deg=}olQ`A9y zjlPR{<$ck`|9)=KQc#iw0RJ*J=$ehRf$`@wi=M<>!0%bbW+A_lrcu@c{^e7&jrm!e^*i?$YUE5C_<9R~(Q>Q6~AbM*-$m z1xjnCbVY|SGGH^zMm10y@Emqy9OgFGGFOJ%$dQk)7nVm|%Mb}UOw`bW|7754%XV>I z*XFgT^(=!#+#4C<`IEnkw-<2-S(4n)PY|+bX=`&AO5jks6(Z2xH?m&6F|$$b62(f0 zG_JiTdQJ)hO%{1_dbR%|-mK)}j{NYL(Z$3J#ww<;!C5^cdw@}_GM`@5tUxSRV|8cu zfYKsXf(@;jN5uCDdAfRb8snN|sT8$J_f827g2J&p<7=HLp755?<<@(C<9Be+IagU> z^vz50MNf*MotkhwAP6xqJ3yfSo#(ke!iZSr;w2?9$jTr44|#m1OU%dZQeV+d=LAy2 zFB2vV`qx=y*4X9GywB%psj8BUy}b=LVRZ@(2ESaFzIY`D)a!`{VKHbeEVJFEznq6jT;+;h9{- zSsb^uNhclNn`;aca!<)pFXszFb-!G9oPTmTc&@%AGW68SALw zr)U&0Iz7;_`?*6!LXu80QN$n$mLA^!RZuC;JxnET=9-#yx42cSaf*ANe3<-CRcZ7b zAL%b_CW32c`^FDlO};XA(|+C9fg)8PbaUNOdg&g2ESsOn?`89V@s=D5Z%jj%Pga99 z^G|={t+;o$VhiFN8YN|d*LI4NC>47{e$46Ok!cjoBK#Rr{Fa{Q%|sewGc<^I-I~s! zr%@qnG-|6@+Is~`s@KNIq-8&m&Mu0qlbEirA?iVxAee4xBnl|z;9GS{kwUxW-*GSA z@gxA;4JR{Se)7-OV%R~wkGxmyAAao*uFKmC;%K2QW?-OTdbcx?tb`yf0Nw`R6j8c4(=qFi95Gv6k8BlOBLO&GENE|qG=sh^f; z77AS}qzhPXppYI(;qWD8@wIVu~9nOL>Bor)f#O-NX*fK?s8*oOH%vbX(33plENviA~j3Tw%z&@baJs5>6_ z8m;4lU?Rh62Ni);j5s56SVh!Kbn4RqNLIJ zV03wsdIDPrw7%LbL=S2CbM!0u6S=5p!KEn;R)KDS1SMFu;SYiKBnV4LkR@t=S@%IK z>ndDMyxM|{@Y14xF9%rE@)Oz~I>T zVa)WeNvgVte@4&Wpiz9!@)jT^bmF5Zcd<1SAflpz(DuDbkFjslWw=HV5U;G9;{HA! z_gJw-H^U}3m|-`^)}rW;T|ez-eUV|UnMD#369(RiTT2FSIsvX*3$dQ1Tkgj!6JOU75!~!NusOh30A%5UUfr){)SoE zM4}ogG9hof$#u?w(Fl{-jgtafbT2=O7NN`_&_#AFc~XZsPWFD-^ksx;=ZX$GZlEBX?VKG@1=3A7RGx3}A)H$*e7f6)`4rbVo7us38o z!3kPLa9*r73Nd+r3wnq3>}~{B{A&04`*x;!5)DGzllX(Ovw(so+yS^}9vmJ= z(Y5%QPEHPW^*{P{rGyDNw5Hhn=d|W-FTvGGAQtb4!^-_k6+m;iGV8Aj z%gfk*<+HNET6qCkgnqUBHWCs`jkz4J+BcC(eM76g1RP~Pee!q?sE;sKySaqkLEm#y&OiVyN(T#lL zFG&N6<9qD>R1eRnr-gx-jc(W1l5II?<0{O0j=ZK3)f}i=iv#;5J&W;{V(f^XjkAnN zd}l4Cx;3u|GmtkehKZsipCaesJgV_)feZD04IOi8M&RFg-wqaY6^_?9I$hp!ljD znl-U_9})kFrR(1wGk z*OVmztA{3;?}sG%VR+zyk=nGoq>KNHOixevBT>4VnW@eJLB)J=ei9@_+zN#|za}d~dSbm}KlDU?Kij(7BgOi&t_o`j-;iL51z5g+r zh5SW)bYpGFxrs%ic>BG0396?-+lRq1b#C1_h(tsmq*SZp^?gV;fkcqNH4#Z1#pi(j z@M~h6Wxl|oJR~Ulil>tYD$Ov?~-A%xvKnsay!8*ZUJqXzAa$v$vILz*M{Zn z$5R&Q6RH;`YX*EnNgz@ImO2ai9JgAzH8~UHQF^J9p9?i-EP~NgS1&THU!;+@DfhIu zrz2z`i;NAp*I#m7>7_9*HFap#&GsMmR{5fRJMDu%VyxvgnnJeu$9SY&XJmnUm{`hJWm6P9pQm z%kUB9E6RXE2}i$-x^{^LSEnsr^Ujy-#17Feig%{1Ndy|@)4k{m2XlunN5W7LEyW6` zKT`!PiXZ|Zf6T+wd$q3*b`pf8r06_Jig@?r5`C{7CM&EwV@N66{N7rmB9cY{-6p|5U6L2li87RYT%`Er?%*LUi8!nJoucJO>! zIe2jW7Z}n@y=^(SIxLrV^b6d5feHr>#m6B${^8OGTfXE!4vKS!X29`mr+t#R&&$j| zNmhrMG2%=5=$;<1*zts<{35hp;rOsRxcS_KcI+sU8(Ixix=OCF{LmE0LsM|BF2|VJ zu(muGtx{5@$FNl#{laAal*sUSYa$>ZZ0D#aB)>tC^1>vkS5MHXSE(OW957Y4++^t` zK~TM2T!83^y=DY<-GJJ0OY= zndkZBMG)`+87Xw3`jM+4;Az}pbtV&bgLHl-Didvrfv6<~;M1PSRsJS~@Awh$Ibbk| za9rrhQJ=CQZ8tbC0P}UNhl8@Qzg%^IadwvVwmgi(&m{MM2PeEnkuAaZAN1=0xF|hQ zktx0aU`h2n)~O%`;?_%>_eaTv3T||xT)P`#=_l-E31pNM{GdaqBG2`^CgCb)W6IqeQs=# zEt8A!V4&sUglUN*ZXm={x$S94WfADAJ*rtbb0`|5QpEUu6-f&?L&AdmTZ|R^V7~cz z)LL0^X%xNCFg<;LCy%o35h)=4cn$kAez%FUSbxyxPfUIImycegQ^zL>6LRtsFM$g_^ac0fIwn~2AY~y-^h1$|4ix(`nw7*A}}LMGXmJ( zx$#aF0<}iUd%Vk{ur!PEYO$*(E!-COi*TnB#ohZ-c6i16La?uWt=KqPex3TZA}dmJ z#2@Nc9;oAbCA#?h_>+=vpz2P^ucga*CL7pfE?<7?d&ZVUoM%Ydkvz#M4tVl0aywFC+%4SR)l>gky zh~CV-@jM?BF+>zE6vFvh^9{>gE#c zfA&94`$FrWV;o*phgvP|Ul7;4=h!KXeI0T6hq{|If62?pJsLWM&D7}JKIOQmK61BwgYs%f@eO#f zG=13p_@4ZsO7pG5+~EeqR0H;@Ux20Y7kDab;iq>GX$*B=Mq%V`aA6P&GnUX^tn8)i z{t5Q#+pFBS+0`^Upg$(^q(O!yXKYuQS2#gj?=UDX6U$r~A}HIN-kp64-+1_pPyKrx z81C9QLQ5HSJZ(LtcwtEVmC^5Un1wZGIkSBp)pnW5@=_EKfBA$ju znVst5?3ZhpH;|K-JuRE!(+8NxK5)O{grz0G`r>Tj3!-yDZmQ`@s#Jb#hg-?V5NbWkotP!3Ki_CKWUnb&hhr zBxk1pnQbF+ffF}U`qAJ|{_o%2N@0)NolWr*=U$5>w(`3054Y`lyn1$+J zTUY47PdlC=z08~4s9N&?Gx~(0;PnQ9v!tY zmT zXmcrsuKk0@z*-Ib_9qQ4%74I7KmofO-x6pan4r?-CwYg(13Z+Mt3l)x)S!QT{#*&~>~@83&n^OOpY18l2G$;_}G%D_&4On-LR}ZjLhp3V!ynudf%rokq4o>JIo;Mkp_AwYM7vhP%c)cf3??~| z;xyYKF(d&2rmCEeBGh2{vfIQ!IenB2`U8S|gCP%~Ut`I>vq^e0;wAzk+e9DziZ)sx z@w?1hPs-2mFB4K>P}+f9`x;aLhF6tvlSq-{*Fu4RDt5?fs+Lv0Q@BdiAlzC5KT?u} z1f1JgcrH^~0mw`$=jhI4AFn2A9HV_k&9UZ}Ya(4lMEz>G9{ zy9$RMBWf4Yz`awjzQJNnZa--#4eLaLhOvE?rzzP$#5k(HP(#HHksDs~#j26gdV7|J zS(}iz71bFIP-gdKBeVc_Tz_JmbooDlD9*f-!TPhdx)v8W}A>qm8P|jPEA0E zbbri2A};*FXM60|ij&%)w|Z>EBqJA-064!#=z`}-7rndIg-Iy2>BM$n@xw^RzF8Qj z-y`Gv4Yb-?V*|Q6XHW5zI2LWdB07bmvZ07CoY}-<=vw7jg&5Q<60|)cX9OqLB6mHO zL_qXHq?}|X_=)(d!V&AbD;6du{c-q{v@1;r=is?EQ6BcM5Pwyz>bbpls(J6u=>94F zgH?MQXm;ysr62jpsvE=B7gTr`?S0K!@9CxW?gz4AAe%?Oj*j8RIJb~!paIAA!t!_ zGwX)SM~SV=Z@N97$(VibrfKPQ;{&7L{Iw1BUstr++AZpFPBctOO?rEym2_c9VYaV1 zcc(ooNYC4!p$5j_((*jQK|4P3c;&h*TlOssbwiojXweg+Eb!mGRw3%g1W^ zecV;rNK11-M1voY=Pv?Pw+hq$3Yh8y+P?tt^OUTnFD1p98Tto6^i{U@5zm&|L2GHf zl+O-i37#6#x*5Va`zFfQB2P`D_+)B`e{ z0pjsT@dqL6Gy|0)zl;KUN2bg#xK$Ln+7c}{)+(FDsL}z*mw-|$Dd-ojh^iQ6+9Jz$ z1hI{=P|UDX&elhcH$v|OlL{CPrMJzmO8Xj$5-uJ*IES1!GIsOx5x7 z8(&v^Xh3}2K;yIhj%A5{n>bz0C!1%a3}XDujA?MDzyyyjxW;uL+jSUqC?FmG)=(z^ z1)VJ?nkTsbJsl6J+9!A+7;XiX^UT{e0$vN#3?<{l!a^XCNFeFNBNX;Kw&&ma=iEic zx;MeXRn-9GNknO~1UB+g5C6PJP+@QC5ASA#n;8 z?8#S(GW1EprevP0$e?~}C_fbiwyq_LEYZq?c1u6CrXR(T~YtW*?XH?|WJmsNN(F0%(3+Sik~i2Nvcxr~MbPr z*W2V~R*79vc1oP^<%<~Y4W}0oI<-Xpq4K%;j1VO^5f-s526_%s+UJ&rUDn(CuqE5= z^E|&hG#9WOQ2d9vBFI80KJhJBmI7Q{>Op|^J=WS>1WVm!;g=m5a@LJnY~`1s4v0ig zEasjR4npsMtf1qB?>_x1HF_PExC)yJ#h^O27glT+F@)q(Z&TPgqcKjKh-sYQi6MJM zakM6HkVk?8QGH*&yi4)W*Ap?>TAd6=gUM$%!;ZTC26O|`TXt!NW8OQ69+xF!dVZgu za$3hvJYmCMJPrXo-%9zw7TviBqL&!SX<-geb{cx(){M&0T(8txY@1dZr>*&pE~9%M zG{U>O^k?l`E{U)2ml(WY4E%o6&Z7v^O1FrVI}!9go2hT0?OsVq1>*TMx&9po$QHvK z?t3d#?o)&11%(9oGPJ<6QZD;%pjs)L8O+#_)UJZ|PuF z_wV>qv|lU{wefL`Ut>8zOkMLOw7Ap8tQCgS5HqV7F#bfS;9~rg?i_K>kt*qwI)HrC zHgT+eK|D6N1b3&G9c`HF&>()k+K?g)DaCqS=nA6&LHEt|9usvyox_LSj*atq-Rh!; zC5E3Ak+m#Ul03QvU6q#NFOT68oK^-h<)TOqf)6^MAAHEO3uTg%4(HaDN-<_Cnri-L z_x~rc_w5^V4JU$86tfrT;#X9r@9}Uh{$}~KSH^oo;{+=F0jNSKv2`k*%(s&YjAPd4 z{>3>1()wxxuSUO(6UaqyB^c-BDcvqAETQUgpYS-W{|lD@)S>f5+M&@@YjjaK;82%z z&Ll9r&*ymj<#EOR*-6}e@0w>Ryt;WM*+JKIR&ZSVMb;8(*zqLu0JrQk^H3nLQ_)o7 zS6J)q9imt`T(pC?4!_LJWwnfj*MwrBh;XjS^(?y*Xk?=CQ7Oz-k0K$s zf3{xhDpQPH$j35ulL^%PVl#VTObT1(XoLB92$=1BPjTD8R`=3O~Tnw~x~gh=Cl$b%6E^9Dg3Z|2aJJ^TXnaaZTquH+icj86%$9 zLcNE2Pc+|=Cb;+6JV+Rzy`R|3!AIdyV@N(D_VkSKT*T==1f%R4)+x$hBN;;RX1*-z z?q}$G?hrbn*#DjJ^-X5a5f4RcH-sAS?c}INiDseALm}{Y?l|B*V`@tL;RJemSW;{* zdB=*2en1V8KV!Lp`FuEhTrNd6$RLo?r(%|(H4Hs&gUuT24bXm+Z-43!-7|#Qe241x zLFYiG?T+l+_S1?uPRG}5F6MXSw^zgt+ZF+sZeljASqakro6|hws13yJ#>1?o8dJag zV2cQFo%+|(=c%TfMH2`RVe?;(`hq-3|5d}J4LP@P%6Hb?>euRN`z4M0AOA&}as%(fPjj=KtIz8=YqsSS?NA_mPHXA(={^b;I?g|N-)OQu#_zsx z`jTA@o;P$Zh!tj7vRQ+ZJXC6tgKl6%O{anjcBlSO#^VpH-$s*Dq;17k0?(xIT$77m zGuS7Y^{Qb9o}?&V5~3#M%rwpOWq1FjQ~Jvc&U9zqS{z}r?WsPxO3{=--$RUJNn0r_4K z&A5(#RZ!_ei5W#~O1GM3Z+S}ZR>75L<$g4KEI5+FFn0hlBUl;Wx;EvrccTTAZV&aO z*bwiSXY?A0d&|e!KN$lVA(_vi@J2Mr@5t~BJf_Tk8jlZu)aIU%UuhWKOEy7!;-|w^ zaX-Bn?#xHI3;!=6zSk__i=fU)JYgozLSpi+9ph6Qz{OdeHTp4>vNMVUZ_-B>vmRKG&y^ zl`&qE7QZ{H)25dP@%hEm_EY6!GFCptYlX!hyVzL!4GHDKcTPT@vq#67DP*Q(&dh>O zw{gdi&P#v2yGX*|(cnWHj|V?PSNPQSxbA(K;| zb(M$i2cScLHAT(h0R(;!=er=p=ilJB2^p_9U+T2KPW!}o>fbGNW*K1D`84ODeUp@3 zLkQVKf!`n5*Xz-u*+G<4N>9m(xYv!5B*L+pdjv10QIW@*g;lyvmea2YoR#R&W?a_R zd+PgYyv-0Px00eVX_|g|7K0-rijZ)f)ioWOz;lBlLYFw_h+(_|X)d(mbZ#8#$KqpE zIO3p1fxkKIQ>L@(JGt74%%&4G{=Zg-t6p z0;ToT`Bou4h-ylablyi=c~9OF699O`VxE@BCQ9s|U=WXbfGd6@0{l?lVLs%AAwEpM z+!&<&rV^p^%IEi32!FYyI9AK{8J|Au+2KD$Hf!1M0HWl4R6?Q=lC)eZM+ zE&IBy*fkRvM?T!sEv6Dp&rBvvG5*xYA`bGDc)b*9k?waKBK+MVu2R@6Qv+p+e0APF z{7UmURd(#-ipjj+3-QC)wT~_I*x6@zc)TV>-!Zt-`=G^rW{LaZ2uI(}(!e>5%=ur= zD{(&EZrJ$9cI)w{-;*AqW1JQY>C`(Zv#oUc`Po!!F|^vJ5OQ)|4QBP<_W|FPP*v_7 z_#B_U>?rZOPsca9RW|G(ZNVd0mu(5#Z$jPgd1)6qea~e1uQG(t0&{3~_pYzqp-Wth z%NV4iV_|-BVlP{uyhpHYyNO=HIrUc&U&bs%RYCVnyj1LB zhT2Q4+dsADeobT~AF9%69MP=O-dz>hb`H>KM%3O82lPxl`3a^GmfZb`g~oGP{@zl< zUT%->7`jjBQsLqN|Do53KVj5jaiNUpCz{}4V>8}*=o7^LmYIO&622>Yp+JCaam4dJ z_kZ^G|HFn@Mnp?RNu~xwbE_L32gc1hO2Re;_I{VnfNY48HCjCLGiVt_qz&G6bv@wk zZPgfzoUkF3(`JDYU}$WtI%-rt3gLVU`x7K>o7MjPEZPomo&Y#oTcu;dHTEmMr`tnMO6 zxzfNc++H6v9&kMjtBMPlhL2+D786vUH!++QhGGPh*Dpnxo77_?`kORO-^3o*_+*_l zO_lj^1GPa1v!!3Qz*>5LFt4%D&29dQ>j4%J;?Z)#>=1yT*6Du(-F(mhCB$I~ zf2%zmc?laSSuI3w5+3?KlLJWSa5Y_U_ka?EQbIY{z!Sf-R;O_d&70pFMu@&;OI~Dv zw-a~udAtdtsKNvd?7HK~A`^nq3_KVEuZSQa2i3PV(x3S@n2T7l^4E1RiwomfW&{I) zZqG^oO_9D5Dn4UOK)J(dumA`D-nb5VTAV$Kn$jhFqZr3gxVLz~5s}$Kq9pRUfA=uj z6!85{{N8HcsaKcxeD}EX1y&91O@9j~47f;vYiS9YAB`^FJwoM}RNJ$|^Rfi1TYNckRJ`9UGOB-TA!rL{VwbXQBxnKlALs2;kV8L1Sr z?+LYkx6ho|C;gkV_gEabzNc9|J0^&V=BQ&;6L;wDaX1PT| zcBk+wIU!KsHQWPAx~f{+={N2JyW{~{IefcB_BqR^T{zAPKc1c!uwtA&a=~VH8I4?7 zf$xvrEa(Yc6~p`Ld6qmIjgXRzA0Em(>;nQk{t8_@KiH0@zjRgk?FIT>MvgBiHY&Pz z2qb41Nr{ZfrfWgx9agNXzdf1wO!JS|9=$v=o)j{?W!sD~p%D8k?lscqhHcfWN#WlB z+4$oI25B^ntsKAp3@<|6mvrqE9u}G#M-`Uf2Dav=@Bu2Hye$b%ED@54StlaOcGt=hjcRu_H zdRi;q$TgZlAo34j-oePGbU(ralGN@!dm$j zAyF1-0bq<=HQ82D_T7*+kmI(^;hzbNau{S$g99oK1sCml@=t)~vYAtlzfp|~B6gC6U# z){lnN!)IB64$2B2?1m3$q2f&|_7oHWGhoG};~)63V7=;x*z57*`rPlm_8rdz58bAM z?i&e$no!@c4>v-#TE6!GvgqvU)Eevz8iVqY%eFz#n!%2{cXn)-k6SPr2k+kykG;gF zqm)h%G~B}O!F7=QfWc;=xxcmFec`N55gyC|*7mY3UvYkFUU}Fzg*W2(^#RY9@ole- z28Es`ZD20*D-N`o&S?fuEv&i^|A@hP`6g=*CvnGH**PU)UbZ?`7=)reRrGZ&Q8S?03kI!vErnU~ZQ!29QA7EOMh0 zEXH0BiZNEWWM)i?gbIZiJNlFk)O(mIm|HFnqfU?zuM{&|mB~>pI;Bok%*{G!o^bVI z6o0Z|eosFVW~>A5T`blZ9eG#q(PLJavY9Eq%F5qRl5WDGZ3>_Te*BBSAE;b7ls2xd zX&{8AayM}Nae9z_Dh7;j`(+0u=HbmR3v9}Z2p;PZhB4Ov=9OM`C(3#eXsTt_9mdi4 zzJfGdagk;(qu}Yd6?RB^TH_D1A#7Jf7^yA0yfuwG;|(-MGI8rdM60;c&wbiKuSM`o z*l)gJ^P-4LWIx+pemTPj8{przAz#H~(N$I(J{--F5PNOTtdyhe_ zqjD&szpBpMixM6yknWN?Pe+(S6eK@~M!f?pQ<*ms{yUQEVsT8P}NSb2sBx-SnN+3Hm9N(-GwlldH&!;3p_@O<`{%3-se zFB<$0%Zl8^cB+STa)ob}s~zR!@+C0`*=^r`>t2N+s*bG4hm2R_f9uD)<<^m5tArpS z{SYU}Df`5+8UQDA?MhnVP@+em_U6?<*Czb{h`D?qDuNwM9-i5+Sc>q-(IZnB|GXp% zkDvLUtT30dh?+AI|;LxJmKM>s&a=6C@>QrQuPbVau*r+#zR_NQm7V5n< zCV+B$?qUnEq;26hF>(;u5XT7&meV2gEhrZfFw&ST#h;MMOsa#EBpL;p%a&^=;C!XG zbo(5=Ar>29isJkO7byEA&)%5kX>XkkqsLz04QH*VZNhp`wRcIw?f)`r9TY|@N@Xnb zjX-i&ptWYO9xhG%?p06=pk~yeePnLoAp4l z)^@A4yOS;O;e-Z>H@5edo6Coq!Xc#RNL{yd)a^6M(7+h3#3845vimceSm5|iXE7=E zk&4NJJ7%3)s@Pg#=4fc%WQi!)ONp?4LJ2R3C(87l&&tw#O?i}Ju_^uG$Xm05ui0OX zaUmtM8-Mj)nLPk75f{e(wmSi{z&kd}1REE(4N--~U%q)j`TVl3(&zyx&FEqi-7SI3 zGaBory6yizze`gmf&|E-1T4%W1JVt+IGhZK2L&R>hcRN8)=-v<|<4b?BcAaRTR4@0NbzUO;#3d2r{)$kW2 zLqQ$DQv|ig?|>#7iX^5H$7jc&JrKoEZ-ib{@zRN-|^ zTI`?j(~gZ>PlHB-e%tPn%rW4@*XMZo51X&q5dl2M5j^1PO&0=jpINC?u0B#bSyRqG z$}TyW>YaLW@xbV6Aj4Nrb^)YwAyF^3WjJb7D zLzx9Zy;mErwn{Qu4vo=H(Zo>xXL_U16t4sjK)9bm6w6zjr4^)ZFdhr~lu#uw^6(~6 zt`T!IZpPD&Wx34I#Xjr*$Z#Q}i1PS=DKz>Y*jdKX@ar6DOk!85*V?b*(crF$wRX+M zK6|DNs*hnr9JRo(+blslu>W+1+na9!E%~F#%;`-BZ)WFtEXz?x-H)xBIz$l^C*kOH z_PVsECZUim&T8d@NWLY-@Db>=UZGyoqnJ4smd(J8N+w#Hp7|c%4kR%)$}NkGM2dV? zZre?|VNp++ad9gSWc;JuJ3~M;T!9!sC_be4=^X)|{Ssqrfm279SgPyA8Nji(S4;4f zVBP-ri2gliMitCri5mS>^D2^{km`HNPFV=bqeFx2wM>$UC|P3$T^Q;mBMnzr9U7xKrdz z0ztyPV|9_3*eII}go91U6y|3LVsy8wEOl&xU2W^3(~zJDy7nVu{zC7|6;txRoEJ~z zEzB|~LQTpttsct--%l{)VK5jC{yP9`;niE*BLn8S;dXE+m;^)(T zVrAM+wDA735Jwo=WiF;R@hV#-W;&Y@MfyHUL*Sw1QSSSt8a|VkU;M_q9!~dXi-u4t ze^xHpjAep_yxw*Cn#W;nUF&Jp&jZ=bZIb70PvQ0the-w&;Zdh~^m_CFUU1thtK*sl zTIKE9s`vZes5RBk4h(ND8w1Q`1?c&}&ZW69oLa)H(u-DQrwgd67*7)>uwx>J@RyEF z5oIR*BTb6Os=R{q*ceXS-}&iug6rRPQBerB>uSL8f6i!EDN_DG;-4Vwh>hjNI+YV8 z+^m?ZS7<;_l(5V`4i3~M+4faWK*ds~;gc-+@y)D2d;YXK`N%U+w6}3iB3&sw*SCve z-M@xnyhixb*ibNb5#<*K)D}1Xw*XK;Z(0)LI-}M7@65KyLK5{@ryrw7eGDe!S(wp;0)!uHpv!*_(qq@Blcsf5CXKcH@}a=9JU{nUMz}>40Ps~nRzg(01*Hvs&Yiw6Yu@D7)&o47 za^k-n$y<3W3?^%e{mq0YIa#b;-=xoOpF}3;-rL`1cRk6G)>*7kjreu+)BZ=Rp(r|n z;9q2j9Jd!!U-NUFx-b!N;^Dz_UmK5cG3l+-uit!2LY6>`M|?yP*$Azxp8By%Ka?)_ zqB!Z!91pS?7t!gPrlCX&jL7469doMv*Wm+JtQM3e#D&All|6x-k#kg`m82OQb0c}qq0=Uwxo5Ukuo!1v9wbS z1p7@^gD-IJihhRj)GR&X^+6f(YDuRxYe3ia@!7S5zQ)8i2Ff63=F{-Ta(T_;DM&nG z#t33pIzHX;?C0`abEH;^@0!7>!+Z^OYt;==#e?HA7T7;{kWTk9VoE9#|1vVh%!K6y zw8N$dbrtPprV0RlY8>WG^j%yNgTwzfhde&$1!lcA|J-zsdUe($dhAy*LL&G``NR$C z$tPJ|8CLJW;5UIHYXsiP4&t&dSF!pb#WIq!^jbS?(E9R~sz6z{eJEm#0T8&$A>5ZNDj!kv}rsU+K%{hHAc^%APK7btN}g(anjn2YkH=iS1&48f%kPvt6jBO zWs-Y_?mPagXtEemRTJW7$Ip35s0D?Tbd@+le1=_EM<YE>qRXUVe1)kd5fIu5O;k zL_6O=w9)$?|6f^e85KtpbqfzVNU&hRoj`Dh!JXg|AUMI@-Q6{~4el<%H3YZ8f?II6 z!R_*V@A}rgcisM9t52QN-Boq=?!BD?!`W@Eg8Ac(^a?`TM`-eZpPlPQb$;jPqUqRmy<0S&rqf7?~*@w*d_b^h^ET$srAZNaoPy1oQ44mpT=!tlaC zM$kL{9r<}L)XFa+54kRq#3S8*H6`ZC`E=5`z}uN zgCTsk&`Gawe1#~@f`0Gqm;%32)xVH5(4`g{5js+~-eWd>PEGz5Y*JD`&C{c~w~gw0 zB8P!WJ&%C-Y6oV!~^Lh31o`SmfKW4`fqUPQ73O$#-BD7JxR&&iPG>cDcr-&-f+R-1gnj1Izy&XR0ulr*TFFu@i=6#>K@hl z%mEQzAnZ-cOBmLBUhv1nP!u0R!!JizopO+A{tIOtBxdQG4ibsXB)=OsI+B%7D>$5_s$VKs=<`4LtaH;x#lfE-T%g>c_hQ>5 z{%FW+L{UvykufzAs1zEGeE1f0_|E$zrR_1u_#Ep~r@nSJ3QXsy>b?U9on~7#&Nx-$ z)gE!Coq!S1M_Gr|VC&*dF!!;;1A=qrXgv~S^&V9ugiMJI8HUE%bg)BHicyD!7X>}= z${GH5{csq2<=Xk)$=ILNKOD_OLZU+_L*k^?qGyhZ45_Q*Xk9iiasG_Dec?jug1HZK z$2+Oiyvf2F^db-t@#X?n)w%uEe+hET@~oH=y6*v1-vve8Fte)azjMt&y@lKStWI2Wa%o+K8_NtmdFiFaW!bhR^+fQTppl9*_kyrE zjZ8swq3Upq4JGe$;pE8nfCCBnVDLY-+VvFKcrVP2s+kXLUgUBJX1n=+2)1)tYO;k? z=fit>YzT9be0R7$!gOn)(KHSR%3p0&MwL{b@6dmM*FbBWmnZ!4^SCmVL(lPVdlb?z zY(pW(uCQ^ga-h9x*rkQ9tXr#$Y~HF>yy2r-&SYo;ju=2?jzjCFhZOqk*nlkG&4_t`S6 z#+WaLVZSuEg>DFqd1cV4h+trk&q3u8MgKQB|3MiU=Fc%_#W6s{T&Sk*afGfx4bpDL zZ?NuC&=Ako9!!3=4X9e7h5+T3!VvlGZH52tQ%~GcTzuDh=|Vhp^EEBS*8%>Zyox}8 zt;!p@atJ1_Dp|#sSF6ORFPBQ{k-~XpR`V6*A%ODY6clLSBalfQHCZ)9LqZfMK?P|y zF@bY(HvXpD-M{1Z_KA;Uf8E*K-`bvdMN9f_(c64)+;gHWp0B5k!`ZeYr(rH50OPGe z#oZh|Z&A9c+AMzr#PDw8;V6-E&=4?V^(1~CxDM9^^ujoy#yA-UB*Z05rcXxaS@d&@ z+O0{kmiSe|fXcp?7=;>&k_ZRF`7X^W9sDY!?i=hK2m&n_c(3@ho23Qy18;Vt-D``< zNai)aXh_X0|CRw=Wu~|k26fc>`nrQ$WT-Tb5;M{UM22UKWNW*v^$f5HzHa#2XnGxz z&_^DlM1V8m1QA+3fyU=FV5?(~5oQ?lD_WS1k5JTDx(hsP zI^2YTO+v#sW0!qgLB|IL{q$sFr(=|o%nKn6a8Ur>!pGE=Ra^P|!4SCY4alRzWMZ^p z5r4oG9HzL9zJ8_9OI6ZCCtRd;2pXpAt-)-f%w;L0+dDW2>bG^%*qdq-T&ybqxO&EF z4rSnVb@abRU!110NJn(S{`vVJ-^bE040lj`s`6I$fsjd*gL6<*!-6OteO$+czW}M5 z9iLdBQ9Ikwig^mgWi{O%iyD5l%4}+uIBz(Cr#UN>~t9ZB0pF4;*INL(@ zNltUEDDE$8r_HB#&4FrK-_1Zr*FVRH`x|UB!mn|l4a`L}>)(8iEADfA1KE{70VbbZ z#@EA0q#DrLf3&-Ov_o~@iC`uKHB~dUJlNAI;56`UTy|6>wiXQtpL^~b5t5yJ6`qOD ze91IZM!aH#tTGlyTUJ@IuRV6pdFTC47Fb9zzHO^%TJ^UJS}B*-G2)7eYL{e`tY69CWVBtDI$t@@jlxZ0a)`eg z%yV;``;GZLLV$Or{%^T!mQJ$MKX5H0I|&fHnaw|6C2qjiC&fRAAQ|*m%9rXMWO`Y& zBw$PIHQNL;U**B!E%ZdLF;LC&T`>=R?UX;-^i%TFt6;<$k%n97{Mw!-dqg>TV@?{I zTu=&WCAP?(G+7i)XI;fVF7H;&x0CiJZLv$dl#pVx4Y#+B+^>nO$g6VYx>Xk4`nVE| zCI}@x5UgtytTTq|{Xr5d+~JX9iv196AImKOb67_wh7ZY_{9}h;)Ok09zv=Y3EY3wf zT5!@TwZg`mDr$+(-K_8Bl*=wmDTRF^Xc-)9-yL~bro>#Wr!D>B?n8b!f% zZqUHF_kNS z2U-z?OdC#cRn^mt{Mg> z^-gSvPjuE}=Du%5DgA!{InB=~-)^!Ejx31;h+~vYgP7tS)3Z0*$@_V2*r*1A4d_=KF^w z8^oy{b#tU1+vBd>OhoW^ZPLP3^Pgx_y zcIGCP$k#>(Mc7v@Q>?7jdbzaR5n;nQtC~v-?>uo$M2#gT_l}!zqs}I1A2KWsLGONp zv+$CkvByGUB?mK0|H@?M_PsH5MM)PEVaPEIJL-3b(Ygu*btZ!OD-noYA=qR%_qh-L zuZHAw(NnC#!s}Wq@V+jWpS4F%?xclaTl{>e>w3Tl63|%8BauAbn$7smSWpldEPSeu z=T2ErFHpiGxSO=WO{_LOack;R*qporCghtLu@m%gJyqHw3s&=?|>#LJqpEdl! zszIE2yl7?++T6BUH&+SM*7zEH3!~U&FQ$RTsik)P)LHo^z!|~#;z&sirjfDummk0I z0653RyTaqF)GNe^nT%FD2R6x$zJY=qyYc(lHtNAv{cajn?!TZQAFG20C8c=0}nLVHSYf+dJz$Yw@w z)L$a{a!uFiaU=!nT1V2cQ)=6-;W~;98mHx(cB-#7X ziXMoPve>>nN-GVt2?djx6#0lY{N19;OG?7F)a9uIazU_pr7xHqwX@G)UfAn4i*)}a zJ39o>P9oH9`zC%+t*3xz$&(oM8AI|Ejbk2ERnnn_#*QBN*N7SnoOV7uWZaN{BJ?P5 z0XL$Fxwh$fb}+Vh#)b-hjG|V(b#Fot4^2D{a8CpRk6}wRCpG+~tKJ6a?hg2In(g3qpcLs@&yB9t90EaTmY2Ps17} z%vNA)LuX6hWp6{E7x^mAKsyNmz=`TnK{A20T%TGf_zoTFu#5PO%3`KHLen!HAsvz+ z>jQRRSrDblopKU%;2`IXS|zvA!wSd$itKcYvo=adCuQrIEX_hCiIu)>oB$S^>yksBk^beT|+BBL-h+;v}eg4C* zEZMZn9iilts%d836F(cv?U+we7btRFSqT_rP&I+im!jx}+~ST-)}tXG4Ass(z;Zr+ zA(m4nTm`#@u>O+d3E$_)PDNSfc9`JAdfU=>o|D-R5wX9*DH|~%QxA*8u5VU+C<#dE zxV?|y#L50R%0qYG_(DvBd2}=b(N&xLQ4@@Tb^;{myC+5nyb4*Ot;77se-dqy(snBz zc#Dsf>Le^3uwiP_Srfc$q5jRrjYNuXlIYjr75mbCQo!qRHtW*0lwOOj9|_&vOM2MZ z-C0hPipWu0n%1Jk5gG-Z_O_-33~VIU3!%$Li1(ZWH<~-?GZ&=6wud`^SKwP(GQtnZ zDw<%u=cAeK0y8qynx>XAAzf0qZsKHDS1kxtjvY4iR=!{@;S0?s6@tY^rr%ceO07U^ z-j?=g`53{x4}s84df;_!s1!;sBY<%+9hg#_GD>@>yOu#Kz*%pu#{gL+HG!0TO8D2o zwj?G({4%;vxu!qEVFizxN?AAm9sb@pL58RyduB=|0ioztj`=BC59i8k-?NB+^!U09 z=mrM1-vMqPayqYVnD8#i$t^agCd?;Er!;`Dm_d~R4dEo6h!v&r$Lm*Zg}W561Px1a znBfWT#GbFjkYmocLZMMnrRwPKzjx>)xkd1esI+B0NiVmMiMoO{{v9M?rJ8xaF>c`WQ-ws0!d&D)in3)W@fR7w?&4+`x|)=uJPN_GW))3qVs(=&{B9 zR+k$&+H&!7i}3^Zf!0#9L=sN}Nx{(Cr~|v?HCOX^QrHyxGORbZArnv+ugtlxn_!m; zT9>xdEz&tRL}%!u-5V7FV^<0eqYVwB2NQ(VTY7#U**#a zXGA;6p0lnPG3d~=po_&t{w1H2xeIJ=iovs2fVt{qVGbov9$MkjOwb`ML?Plwwi_Lb z)Ab4mJi!&v3t)52WDuhmGCLEh2q3m?>g`Q`SBOlTGYLqwSHKuWBEK2V(PT{u7Gu?A zIGQs}at>x0l@r0MAT}@(oe%Fv(DKOW>L2AakhBygcaOE;3aY-#JViF5oVhGf@Hs;e{%& zSnBDb7X@i0+YwN$;9rZF0=As~#x-q#<+S9nWki7vAtiN0lNZd&p~NzggAp<*Tcm;U zHb3G_PvCPbV>|O*!K-287&$4fP%r8~%^7;c@EIj^h|~Avo4uL=aY#*_dyAnLu-|b{ z*#txThoeN2s6u`%c+z)}7w=T(wB_?Qi(}B=s7MVSekZoNl~I*b3z3v$y@7Ze4teEu zquxBP)i)#dpR_dFaSA?ppf3(*U`#6iO&gpE^2%7rWyTas979R%jW=k$ zt;4499$o4e_54mD+0vMBr728|T=I`WGM#9xfQMCTH(51Y_Z2b==&J7pk*L0CeMYc{ z0SSS=@kCWU3?V18XX5P6WZ+Gs;TFb29nFFxKBK}R!))?=^WR7ufoj|OI?Kn+c>H** zOtL=oT~I1=r@Puj9ThHwYLaSw?eRX9c&rvL1Oe&0e&qW3zM0VbD%QpuQ2c;51~?sJ z_E2Q9Y;dQX5i1>Xlunjp;lf;4#ksNn-9qCa8&)Aj>NQvw;j!)W1G(QJth)RaBXR(Z zP!VYZoF*4gPI*3X#*nx8n!@`wX*F(|1iqDV_tk%Y{KP6XEmXDgy?O9aF`lQz{Wr}veuvv~9#5YPv zb_er}ZX!NC2?M$a|0X*mZ%EJIMFZu-&f^YHsMM%oaX{_9Y zWmEvxg7bo*KUE=FAsd}9j}GbM*2#_cHperC0e%iOgjIW zhbl(K-hTbfexkVaYzj|&;n~dc6x^6S1!K@W(&wzvJc(qO2uIt})7d;p{g)a0`>0ko zm=}|W(}($^Ac3uy^GMuF{#|d%ULXk>z^Rf$EGYj0Z()@W0qwS;*&|EGB_tjW$g88^ zXJnVD-|GU}_cdBl2lpOO$ix(KZ7#xRqA!ubxp=5xpUhIma#h>Y-zPC{~-m80X8l{*B)(71vQ> zsS3M36PaLFrcfDH()sAUxaw_hx#%Ob{)V&OU*Kh1Zg6Id`anMxM@XnK`^@WE-s@t1 z_D~OaTcjk^2zI+f?>H!Cx~p7x?V`daq+d;W-Kigd;!0lHYm&})7Hp^Z9Am||u z_BS;&@erl+x4FL2kTZ%7c0O}3@VPvfA@USQ4~=ERWt5)t?k_Aw7$@!1@CBn62j&eq z!skg9g5-_KZNF-_a{m};hO?q8A8V)$FJlhkLB(C{mm#&?ek_UG4AcAtZ?=dbA|3vL zoM?ksm8kV^FHy#%X6H#?8_6JD1sXH_rPJC!YdZH-jaEVXhEhiGCR~=$IF;80%qXv%DKNDOv|oTKr7Tu*CJ%1cdhHY!_L&^jt zw+4wW^D9+rk6OltkBt^gdG~fT86%nYcbT5r#3S!-_Gv<}a{FgI7Ez=3mW`| zWsFgM+|c}JdmS~z-pi=Og)N!3Y|WF z*wQzTLCy;P!NC+GP8CWCVJP6r!xdDZApY{~kmE?Iy_u=Zx;rWt+U1l&4p|KDL4@MCRNN!7o?=6vOy7#oQVjmkp z!=1>Kf@IWDz8L)@OgH$RD!AGvnx_H{Ux(x0fP?Ds6Sm%=Gb9*7C+!?acWES5nUs$tCPg`_yPX(rnNml_vHYfgfO{;pSbdav>_3*ar49qGbm|NHsZvpg??QDj z6iRCKl4c5>D-U0WG-gXVU2dnXX=m(pdBBQ{H6>F~Z!I*BfX@-Yg-NPB>y3&X5bXb@k8#NR|I-JN5=|v=MGj+On|H4A_7ZOZ#a?!KdcCgg1 zVevB_qI(smj-7Y)+91By0&1cZ1|;b4O}n3^eX3mVm8~sKjo(Jl*Zc{MCzwKk+qe<@ z+Lc{b1Y=b?Um#26a} z+EfF)nloPL^dlxWkH5bgDIX(z*G^JnIJ5=cmhh0w6`q&hG)bTS2I8rI2jq1I@nw&M zya}S%owc6QdLt;-*))`Mm`69i_w78@xXhe)FGLW7at1n8#oKm@lY$g47Nd8$X z2PJHORbm~BpT-R@@shYp)qLc!! zxN{3-{jL)5IM-Nef`4{-7+~mYte`*c9#QI z=*^j8qA?vs8t*IGDQ34AWh(c*jYD8hU&eFEoCz_B>wJn-Ask6z?q|7bN*%oh4RZTk z#HAUJoUtF%QmHEzww(d?gm2$}wZ12|b@bO&G+G642=34EmsAQ#IvuZl4jmerQt`!* zqvc~&?PbQclvAz{a_ovu4Xmdit%?^AZuOaFUV!o&+dhe?7X>pJskBX|!)}Sne;s*zIoq1;- ztk>)t9sVvvR_pju(JVRw2`gA0|Kx=!N^TDoA-W`4XJP<-6GvU~YNG1Flv0n2Yt5Gh z!!;!C{;rO7_nuoR1oepRd`RN@z$3r>aB$=mBqH^|39yK|vxE;%lnzK- ztqu}FeX$xMT)5`Vb0=cIt~Q8a98oT9qgUMC-Om;xgSqf395!fW+=n*Vg*_BkoCwiY z%3`yA(3D$m|IqRaT~gtP^<#I*`>y9-AOjU(Zq2+0#r4{eN}H6}(?Kl2nS5+Gqz7lu z6_4nAMm^}xdr|r;W$wP9t5%8J@Utq_@fjo{uUu{2h7rDI%5(?Rj{nA$fn2V+VN!cP zY;Wj(cW7qIY&3zCG}5P?VqLX}DLUB047p?)tJ6F}`J`EQM?>Hr?7*nKIe-1t^QgbY z0)5<&F=x&iTq)gj^|0&|=m6<~UC_$B+)>tbFUv`aHV=ox}zAd!1g_^L1q= zj$43)dIt*&{Li5lUTeRtv@w68CV)<^h2QtB7^F@D`x)2!e$CERvR(C-mftoYNt!Tx z1Ug5(m%!Zl;QidD@(-KtL|elHlhefnwp-xx^=oJ~2O>2iDhRIVQutUEtb9hB+UByo!BLOv?e?5qy7+577x{JyxnQH=fh7LPBZB_+wA zm{6|S;B$3Ke&+*+aUHpE;jZzPr|Yq4OQC|~DtF;@E|)1u2A;fK?xCvAG~8g#VaaJj z{$;F{XNtj@@6h$*!ZX7P@xsyYtGEuvd!wKk$Zb%`F9g^L^VP;4@Q8Rq>nzjc9jAg# z_x@9mU>zDUK^1YyPJ-wRoBIuz=iM4uzNTdttUTyRPK7~Mh%lUI<1ZSfY$=s>#$G#i ztR?e%^eaH$7=)@@&?kgy|Zk(Aju4N4VFR` z=I;R8O}7=swHZJ2Vcs*1IVj&F3d4;2u~ba?r!BXp=#qz zc!mR4gZ54ZE6N4#5y&H-&LZay@?!J$c>HdxqpqzQ)Q30==Utm8EZ+?i5xU=X$pYd& z|7lU;-K(TP^%p3?R#;BpCK$mZC6O0%*Zan?Kd;e%zXf4B-W2*a{k(gvz9qV!q}s%6 z-WuOf;S&P%-gk{nQbnw4#u=vCPewl&;;D&#N*3^6+Uu;wJPlI}9_JpyAb4iuz|D2C zql0tveK*(Z;{g7|X$xqE<$h>OzPzZfW?Q(;-s4;46Z$Fi`g4S{g;U1ANYF>0WxEvv za57jTIdN?iNANMC6z^Bw)>G88k0OD_lBO=2UX#K zJm|~{bfn78sONnk5QY5y=cN5z8#!>g!iUNhMHVwGR}uVa;dg@D^^uKns>)mzV^AuR zIaf<4mQU=$XG(ivX)u0Xo}y?oXnT08ZSsxzpVc>y7mL* zKP>lsRne3Hc&O%3V01krse~6d2+Funeat5L+kczv<>P=p{HPJo0pv!?zaVq7J{R z*R|3v5B)Mya!ZGA_DW0o9hEZ$ zsAQQwivZHt_#CLp5OncTvz`e{qkQ2ssjCtQNCKJMqsM*(Ds~#}bCtmE z7~QD0&f&eLW@>Te{1hFo|KB>XAz7_R3kryNkrn@BlF;SFLV!md{u;A@y%6I$0rO`@ z4GTh+jH=cNgK7(E+tHnU6$nD83vRvCRM%ghL$Oa~S{7R(p5$+*`VAce)3Nk%y_zxU zuD#zn{A!S2P+xn|i7ZhOx72K4MUsZV!!3CMzCXV1OW!EZ6lBa^pFVt?nM|8(fqQ0j zp#Koo?2L7a^>B&ZSe+}Ga^pl}F0#>HqqkTVy$~@AwLA~ZISE|EKl#q@nILVb^NID@ zvZJj@(-cHxC-d6Nu`D8+Arg)rf?LQ2*vWwS!kZCLvgDzw3!E~sc!g*L2o6i)2aI2I zvI#gR=%Q0Lwe(y}{;)x@Yxf~o5v1&IlbJTQMRB{#;L<1K7r}ob7)m0=B*@8IK}1&^ z6w#;u>_t8x9%uNkhN3mg*w~9{=f$ z#?#l2Jx??m1t430@-?kF{P;6qNS?jIXZXig5gdZJE#mgv>9ID|1!MZ6;XyN4x?Wd> zLOI^a*hNmON|8t(S`JV%niCnf6AFUMy_0_OEB2iB$m`;cL~lQjf-ZTh)q!+ zrZ!qo+QJP@0vZAV`hh9u%#AS>L$EZL>XIs-YnEHJ?Iqig)kbGH>GvA(6Kgmw6VBWq zhSBk%bX}Z?0Sh+{c>KJ-_IimD6-(@79ijAnk_kcOz@fg0RP^O z-9)+S#l-?V!Y#dw#vb#qV5H7|v8et{8-YOr;vEbGZFYGAmzchjtJk~I7p=r1<8sL< z1RdQw1kR`amqV8ByKuF;G7!g0Ncj`%Hm3<9DD}Emlh*i1 z1}B0Psi%&7K~3=*uz;XoAMjS8e{qrsn*S#xKXNfu2l?8dAp;&{(*Q*aVuAY$_VSSr zNK*2=!kY@+>;tP|qf#B2akPcg8=~bgG3WtEh*+DPeqs9lx|M^Pg!|o&e2bd)@Cp|b ze9q)q{uaEnW3=h65$&sX_xIOsN#;$2Z$E4M4jU$vO;#+i-Ga8_xwCIKb&;U;o~{0C z2$Yiwwj8hJ{}_nqUA)z87vy*#8s!H8X|Y?vK3;-dVu>as&wW!2Vo>-66FAf4*j7;& zI8wLKQoPkQ6VLe)j>B-~0E zd+1HyhK(6}yRV5XGB>|$Hm67$1}Ko^V8WweHIw|&Y%os5%V4(8RBbP4CxH*hx*t_X zUtcQpO^ug_Wc;pSv`=fvAmd}^Xy`w>mtV0m4Vm!&^eG1^{#YvZ`Jg^gvKX)skWm;s z77NX&Aa1D|{uxsNA-^U?EBr`3l-NT#^3ftplq5t{t#UoeJ-w4~A@xjWXt!W(=QhuOkM(o#(3pY(Rx z(jQRgf01-&hB^HiaFc!>urQcYAFCOwVYvz@9B0@ftXrAxU*2@(+Hhv#0HNxB@WdCF zYTmmdp0YgWV_=E*t=U!0aOw`2x>-03y_$D=bb9~p9t7eG0p>{Tr%YdV{+U_Z@tz|; zM`Dicgl~H8&TcZ;^SJK+3?6Uq{y9qz>ROp8XKd)4-2Ka$96sXcRa^N$xqi(S+MTMh zAWe8R2X`_5pq+}P5k9=FUc-?(($N{fnG6Fk=44>a;`XR;U zqjdAN*l8*#wKZetajoBY9|@?`Z0BA2=$$|J&zv5)JoK9c5Yg`YxbZ4Y2**rW9yc`) z9qZ8^I$xqb2dun1>27Oqt_*azvh>gNZ&Cue9p!7!b)gCC3GdxIoqa?Y~7I!ZbzDI#C_3t z2ZwUp`KL;;g%)(i@&x4LX1(<$@Grsf^iavLE_HemV?ph=AYj0!9c&l#8%s7?UU!Yb zd9?Gf|A=+}5oRF&KO<@X2C#u9+|_Cnoi6bA>fiza1Mu+W&&-u<`(b~ zr^4tS_QkEkgJ3U4boA~MYKm*VPuhkKi%=~7>>d7>W3dN)AM@?2mII^5S20sPx{ckt zztKwD1JIYy(RSr-{EekWspvVaMvvpo*FzU0sVBM3PWF@gAjE(esDe^l>G|u03WtN( z3RCv{%%c}uFL&l*(*G6ae{s7AB=qlQEImMjbBSlAPD6b05ZrD%^LS`iu$w~#!G3ya_dl0lynXl|uHpZ2ujrjX zFW-SUWPo))v#Pf@+y9fX6rfMf0?Hb`_y3*9E}(uD>Ak;O;h@^2jPh^5`}I{)QKCl7 HFz9~)E0>}H literal 0 HcmV?d00001 diff --git a/_site/themes/hyde-hyde/layouts/partials/sidebar.html b/_site/themes/hyde-hyde/layouts/partials/sidebar.html index 7d593f21..b8c698a8 100644 --- a/_site/themes/hyde-hyde/layouts/partials/sidebar.html +++ b/_site/themes/hyde-hyde/layouts/partials/sidebar.html @@ -24,12 +24,14 @@
    • + + diff --git a/docs/404.html b/docs/404.html index 262aef29..a41fa335 100644 --- a/docs/404.html +++ b/docs/404.html @@ -65,12 +65,14 @@ + + diff --git a/docs/categories/index.html b/docs/categories/index.html index 8803ffb8..1e98fb7b 100644 --- a/docs/categories/index.html +++ b/docs/categories/index.html @@ -67,12 +67,14 @@ + + diff --git a/docs/imgs/modules/circleci.png b/docs/imgs/modules/circleci.png new file mode 100644 index 0000000000000000000000000000000000000000..96eccfadb188eec9c604b0735d24d9668cb37d41 GIT binary patch literal 62031 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 HFz9~)E0>}H literal 0 HcmV?d00001 diff --git a/docs/index.html b/docs/index.html index fb5764ed..8e536b3a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -66,12 +66,14 @@ + + diff --git a/docs/index.xml b/docs/index.xml index 288b8b9e..0259bfcb 100644 --- a/docs/index.xml +++ b/docs/index.xml @@ -6,17 +6,31 @@ Recent content on WTF - A Terminal Dashboard Hugo -- gohugo.io en-us - Sun, 10 Jun 2018 18:26:26 -0400 + Sun, 10 Jun 2018 19:26:08 -0400 + + CircleCI + https://wtfutil.com/posts/modules/circleci/ + Sun, 10 Jun 2018 19:26:08 -0400 + + https://wtfutil.com/posts/modules/circleci/ + Added in v0.0.7. +Displays build information for your CircleCI account. +Source Code wtf/circleci/ Required ENV Variables Key: WTF_CIRCLE_API_KEY Value: Your CircleCI API token. +Keyboard Commands None. +Configuration circleci:enabled:trueposition:top:4left:1height:1width:2refreshInterval:900 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&rsquo;s widget will be displayed. refreshInterval How often, in seconds, this module will update its data. + + Google Spreadsheets - https://wtfutil.com/posts/modules/gspreadsheets/ + https://wtfutil.com/posts/modules/gspreadsheet/ Sun, 10 Jun 2018 18:26:26 -0400 - https://wtfutil.com/posts/modules/gspreadsheets/ + https://wtfutil.com/posts/modules/gspreadsheet/ Added in v0.0.7. Display information wtf/gspreadsheets/ Required ENV Variables None. diff --git a/docs/posts/configuration/attributes/index.html b/docs/posts/configuration/attributes/index.html index bd308fdf..69f954ac 100644 --- a/docs/posts/configuration/attributes/index.html +++ b/docs/posts/configuration/attributes/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/configuration/index.html b/docs/posts/configuration/index.html index 067aab24..ded17263 100644 --- a/docs/posts/configuration/index.html +++ b/docs/posts/configuration/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/configuration/iterm2/index.html b/docs/posts/configuration/iterm2/index.html index ba679394..9f554d43 100644 --- a/docs/posts/configuration/iterm2/index.html +++ b/docs/posts/configuration/iterm2/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/glossary/index.html b/docs/posts/glossary/index.html index b88135ae..6ed76f0b 100644 --- a/docs/posts/glossary/index.html +++ b/docs/posts/glossary/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/index.html b/docs/posts/index.html index 09edccfc..ddb5c845 100644 --- a/docs/posts/index.html +++ b/docs/posts/index.html @@ -67,12 +67,14 @@ + + @@ -100,7 +102,14 @@
      • - Google Spreadsheets + CircleCI + + + + +
      • + + Google Spreadsheets diff --git a/docs/posts/index.xml b/docs/posts/index.xml index 62da9b8d..9a86c641 100644 --- a/docs/posts/index.xml +++ b/docs/posts/index.xml @@ -6,17 +6,31 @@ Recent content in Posts on WTF - A Terminal Dashboard Hugo -- gohugo.io en-us - Sun, 10 Jun 2018 18:26:26 -0400 + Sun, 10 Jun 2018 19:26:08 -0400 + + CircleCI + https://wtfutil.com/posts/modules/circleci/ + Sun, 10 Jun 2018 19:26:08 -0400 + + https://wtfutil.com/posts/modules/circleci/ + Added in v0.0.7. +Displays build information for your CircleCI account. +Source Code wtf/circleci/ Required ENV Variables Key: WTF_CIRCLE_API_KEY Value: Your CircleCI API token. +Keyboard Commands None. +Configuration circleci:enabled:trueposition:top:4left:1height:1width:2refreshInterval:900 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&rsquo;s widget will be displayed. refreshInterval How often, in seconds, this module will update its data. + + Google Spreadsheets - https://wtfutil.com/posts/modules/gspreadsheets/ + https://wtfutil.com/posts/modules/gspreadsheet/ Sun, 10 Jun 2018 18:26:26 -0400 - https://wtfutil.com/posts/modules/gspreadsheets/ + https://wtfutil.com/posts/modules/gspreadsheet/ Added in v0.0.7. Display information wtf/gspreadsheets/ Required ENV Variables None. diff --git a/docs/posts/installation/index.html b/docs/posts/installation/index.html index 57c8acd7..74bc87db 100644 --- a/docs/posts/installation/index.html +++ b/docs/posts/installation/index.html @@ -65,12 +65,14 @@
      • + + diff --git a/docs/posts/modules/bamboohr/index.html b/docs/posts/modules/bamboohr/index.html index e3b3ac7c..a93f5425 100644 --- a/docs/posts/modules/bamboohr/index.html +++ b/docs/posts/modules/bamboohr/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/modules/circleci/index.html b/docs/posts/modules/circleci/index.html new file mode 100644 index 00000000..f2dd6ca0 --- /dev/null +++ b/docs/posts/modules/circleci/index.html @@ -0,0 +1,169 @@ + + + + + + + + + + + + +CircleCI | WTF - A Terminal Dashboard + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +
        +

        CircleCI

        + +
        + +
        + + + +

        Added in v0.0.7.

        + +

        Displays build information for your CircleCI account.

        + +

        circleci screenshot

        + +

        Source Code

        +
        wtf/circleci/
        +

        Required ENV Variables

        + +

        Key: WTF_CIRCLE_API_KEY
        +Value: Your CircleCI API +token.

        + +

        Keyboard Commands

        + +

        None.

        + +

        Configuration

        +
        circleci:
        +  enabled: true
        +  position:
        +    top: 4
        +    left: 1
        +    height: 1
        +    width: 2
        +  refreshInterval: 900
        +

        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.

        + +
        + + +
        + + + + diff --git a/docs/posts/modules/clocks/index.html b/docs/posts/modules/clocks/index.html index 71a519c8..8440038e 100644 --- a/docs/posts/modules/clocks/index.html +++ b/docs/posts/modules/clocks/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/modules/cmdrunner/index.html b/docs/posts/modules/cmdrunner/index.html index edca5f69..4ea15884 100644 --- a/docs/posts/modules/cmdrunner/index.html +++ b/docs/posts/modules/cmdrunner/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/modules/cryptocurrencies/bittrex/index.html b/docs/posts/modules/cryptocurrencies/bittrex/index.html index 18730b2b..354041a2 100644 --- a/docs/posts/modules/cryptocurrencies/bittrex/index.html +++ b/docs/posts/modules/cryptocurrencies/bittrex/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/modules/cryptocurrencies/cryptolive/index.html b/docs/posts/modules/cryptocurrencies/cryptolive/index.html index f84dea13..6a61168a 100644 --- a/docs/posts/modules/cryptocurrencies/cryptolive/index.html +++ b/docs/posts/modules/cryptocurrencies/cryptolive/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/modules/gcal/index.html b/docs/posts/modules/gcal/index.html index 79d7ae93..6289bfaf 100644 --- a/docs/posts/modules/gcal/index.html +++ b/docs/posts/modules/gcal/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/modules/git/index.html b/docs/posts/modules/git/index.html index 0ab526af..def87518 100644 --- a/docs/posts/modules/git/index.html +++ b/docs/posts/modules/git/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/modules/github/index.html b/docs/posts/modules/github/index.html index 55afa1e3..b912baef 100644 --- a/docs/posts/modules/github/index.html +++ b/docs/posts/modules/github/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/modules/gspreadsheet/index.html b/docs/posts/modules/gspreadsheet/index.html new file mode 100644 index 00000000..4baf7c3a --- /dev/null +++ b/docs/posts/modules/gspreadsheet/index.html @@ -0,0 +1,186 @@ + + + + + + + + + + + + +Google Spreadsheets | WTF - A Terminal Dashboard + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +
        +

        Google Spreadsheets

        + +
        + +
        + + + +

        Added in v0.0.7.

        + +

        Display information

        +
        wtf/gspreadsheets/
        +

        Required ENV Variables

        + +

        None.

        + +

        Keyboard Commands

        + +

        None.

        + +

        Configuration

        +
        gspreadsheets:
        +  colors:
        +    values: "green"
        +  cells:
        +    names:
        +    - "Cell 1 name"
        +    - "Cell 2 name"
        +    addresses:
        +    - "A1"
        +    - "A2"
        +  enabled: true
        +  position:
        +    top: 0
        +    left: 0
        +    width: 1
        +    height: 1
        +  refreshInterval: "300"
        +  secretFile: "~/.wtf/gspreadsheets/client_secret.json"
        +  sheetId: "id_of_google_spreadsheet"
        +

        Attributes

        + +

        colors.values
        +The color to display the cell values in.
        +Values: Any X11 color name.

        + +

        cells.names

        + +

        cells.addresses

        + +

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

        + +

        position
        +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.

        + +

        secretFile
        +Your Google client secret JSON file.
        +Values: A string representing a file path to the JSON secret file.

        + +
        + + +
        + + + + diff --git a/docs/posts/modules/index.html b/docs/posts/modules/index.html index 5ffb2199..af454f60 100644 --- a/docs/posts/modules/index.html +++ b/docs/posts/modules/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/modules/ipinfo/index.html b/docs/posts/modules/ipinfo/index.html index 5f3994cb..b5ffd710 100644 --- a/docs/posts/modules/ipinfo/index.html +++ b/docs/posts/modules/ipinfo/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/modules/jira/index.html b/docs/posts/modules/jira/index.html index 21d14850..13d8e003 100644 --- a/docs/posts/modules/jira/index.html +++ b/docs/posts/modules/jira/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/modules/newrelic/index.html b/docs/posts/modules/newrelic/index.html index db3a032b..4ffc2f24 100644 --- a/docs/posts/modules/newrelic/index.html +++ b/docs/posts/modules/newrelic/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/modules/opsgenie/index.html b/docs/posts/modules/opsgenie/index.html index ff57d7cb..4c55ed2a 100644 --- a/docs/posts/modules/opsgenie/index.html +++ b/docs/posts/modules/opsgenie/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/modules/power/index.html b/docs/posts/modules/power/index.html index e42e346c..155b011c 100644 --- a/docs/posts/modules/power/index.html +++ b/docs/posts/modules/power/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/modules/prettyweather/index.html b/docs/posts/modules/prettyweather/index.html index a0c4d36b..871f71ce 100644 --- a/docs/posts/modules/prettyweather/index.html +++ b/docs/posts/modules/prettyweather/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/modules/security/index.html b/docs/posts/modules/security/index.html index 98c131b0..489decf5 100644 --- a/docs/posts/modules/security/index.html +++ b/docs/posts/modules/security/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/modules/textfile/index.html b/docs/posts/modules/textfile/index.html index a8e53ab4..4aa7b9e4 100644 --- a/docs/posts/modules/textfile/index.html +++ b/docs/posts/modules/textfile/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/modules/todo/index.html b/docs/posts/modules/todo/index.html index 6c105daa..640728af 100644 --- a/docs/posts/modules/todo/index.html +++ b/docs/posts/modules/todo/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/modules/weather/index.html b/docs/posts/modules/weather/index.html index 7021ceb2..db3e41c0 100644 --- a/docs/posts/modules/weather/index.html +++ b/docs/posts/modules/weather/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/posts/overview/index.html b/docs/posts/overview/index.html index a3d75261..67e5695a 100644 --- a/docs/posts/overview/index.html +++ b/docs/posts/overview/index.html @@ -65,12 +65,14 @@ + + diff --git a/docs/sitemap.xml b/docs/sitemap.xml index e7166e26..6a767e8e 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -3,7 +3,12 @@ xmlns:xhtml="http://www.w3.org/1999/xhtml"> - https://wtfutil.com/posts/modules/gspreadsheets/ + https://wtfutil.com/posts/modules/circleci/ + 2018-06-10T19:26:08-04:00 + + + + https://wtfutil.com/posts/modules/gspreadsheet/ 2018-06-10T18:26:26-04:00 @@ -139,7 +144,7 @@ https://wtfutil.com/posts/ - 2018-06-10T18:26:26-04:00 + 2018-06-10T19:26:08-04:00 0 @@ -150,7 +155,7 @@ https://wtfutil.com/ - 2018-06-10T18:26:26-04:00 + 2018-06-10T19:26:08-04:00 0 diff --git a/docs/tags/index.html b/docs/tags/index.html index 9681fff7..184370f4 100644 --- a/docs/tags/index.html +++ b/docs/tags/index.html @@ -67,12 +67,14 @@ + + From 1d1e280282a73d997be62f0efa61000b6b67e172 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Sun, 10 Jun 2018 19:44:56 -0400 Subject: [PATCH 08/37] Add documentation for IP-API modules --- _site/content/posts/modules/ipapi.md | 60 +++++++++ docs/index.xml | 17 ++- docs/posts/index.html | 7 ++ docs/posts/index.xml | 17 ++- docs/posts/modules/ipapi/index.html | 177 +++++++++++++++++++++++++++ docs/sitemap.xml | 9 +- 6 files changed, 283 insertions(+), 4 deletions(-) create mode 100644 _site/content/posts/modules/ipapi.md create mode 100644 docs/posts/modules/ipapi/index.html diff --git a/_site/content/posts/modules/ipapi.md b/_site/content/posts/modules/ipapi.md new file mode 100644 index 00000000..e93c3410 --- /dev/null +++ b/_site/content/posts/modules/ipapi.md @@ -0,0 +1,60 @@ +--- +title: "IP API" +date: 2018-06-10T19:41:52-04:00 +draft: false +--- + +Displays your current IP address information, from [IP-APIcom](http://ip-api.com). + +**Note:** IP-API.com has a free-plan rate limit of 120 requests per +minute. + +## Source Code + +```bash +wtf/ipapi/ +``` + +## Required ENV Variables + +None. + +## Keyboard Commands + +None. + +## Configuration + +```yaml +ipinfo: + colors: + name: red + value: white + enabled: true + position: + top: 1 + left: 2 + height: 1 + width: 1 + refreshInterval: 150 +``` +### Attributes + +`colors.name`
        +The default colour for the row names.
        +Values: Any X11 color name. + +`colors.value`
        +The default colour for the row values.
        +Values: Any X11 color name. + +`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`. diff --git a/docs/index.xml b/docs/index.xml index 0259bfcb..0da6f5b2 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 - Sun, 10 Jun 2018 19:26:08 -0400 + Sun, 10 Jun 2018 19:41:52 -0400 + + IP API + https://wtfutil.com/posts/modules/ipapi/ + Sun, 10 Jun 2018 19:41:52 -0400 + + https://wtfutil.com/posts/modules/ipapi/ + Displays your current IP address information, from IP-APIcom. +Note: IP-API.com has a free-plan rate limit of 120 requests per minute. +Source Code wtf/ipapi/ Required ENV Variables None. +Keyboard Commands None. +Configuration ipinfo:colors:name:redvalue:whiteenabled:trueposition:top:1left:2height:1width:1refreshInterval:150 Attributes colors.name The default colour for the row names. Values: Any X11 color name. +colors.value The default colour for the row values. Values: Any X11 color name. +enabled Determines whether or not this module is executed and if its data displayed onscreen. + + CircleCI https://wtfutil.com/posts/modules/circleci/ diff --git a/docs/posts/index.html b/docs/posts/index.html index ddb5c845..80f562b5 100644 --- a/docs/posts/index.html +++ b/docs/posts/index.html @@ -101,6 +101,13 @@

        Posts

        • + + IP API + + + + +
        • CircleCI diff --git a/docs/posts/index.xml b/docs/posts/index.xml index 9a86c641..f5d9073c 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 - Sun, 10 Jun 2018 19:26:08 -0400 + Sun, 10 Jun 2018 19:41:52 -0400 + + IP API + https://wtfutil.com/posts/modules/ipapi/ + Sun, 10 Jun 2018 19:41:52 -0400 + + https://wtfutil.com/posts/modules/ipapi/ + Displays your current IP address information, from IP-APIcom. +Note: IP-API.com has a free-plan rate limit of 120 requests per minute. +Source Code wtf/ipapi/ Required ENV Variables None. +Keyboard Commands None. +Configuration ipinfo:colors:name:redvalue:whiteenabled:trueposition:top:1left:2height:1width:1refreshInterval:150 Attributes colors.name The default colour for the row names. Values: Any X11 color name. +colors.value The default colour for the row values. Values: Any X11 color name. +enabled Determines whether or not this module is executed and if its data displayed onscreen. + + CircleCI https://wtfutil.com/posts/modules/circleci/ diff --git a/docs/posts/modules/ipapi/index.html b/docs/posts/modules/ipapi/index.html new file mode 100644 index 00000000..d8fca0a5 --- /dev/null +++ b/docs/posts/modules/ipapi/index.html @@ -0,0 +1,177 @@ + + + + + + + + + + + + +IP API | WTF - A Terminal Dashboard + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          +
          +

          IP API

          + +
          + +
          + + + +

          Displays your current IP address information, from IP-APIcom.

          + +

          Note: IP-API.com has a free-plan rate limit of 120 requests per +minute.

          + +

          Source Code

          +
          wtf/ipapi/
          +

          Required ENV Variables

          + +

          None.

          + +

          Keyboard Commands

          + +

          None.

          + +

          Configuration

          +
          ipinfo:
          +  colors:
          +    name: red
          +    value: white
          +  enabled: true
          +  position:
          +    top: 1
          +    left: 2
          +    height: 1
          +    width: 1
          +  refreshInterval: 150
          +

          Attributes

          + +

          colors.name
          +The default colour for the row names.
          +Values: Any X11 color name.

          + +

          colors.value
          +The default colour for the row values.
          +Values: Any X11 color name.

          + +

          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.

          + +
          + + +
          + + + + diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 6a767e8e..c1512d5f 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -2,6 +2,11 @@ + + https://wtfutil.com/posts/modules/ipapi/ + 2018-06-10T19:41:52-04:00 + + https://wtfutil.com/posts/modules/circleci/ 2018-06-10T19:26:08-04:00 @@ -144,7 +149,7 @@ https://wtfutil.com/posts/ - 2018-06-10T19:26:08-04:00 + 2018-06-10T19:41:52-04:00 0 @@ -155,7 +160,7 @@ https://wtfutil.com/ - 2018-06-10T19:26:08-04:00 + 2018-06-10T19:41:52-04:00 0 From 00e5014d886b84d33d40c66aa7322e2e3dd43381 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Sun, 10 Jun 2018 19:46:10 -0400 Subject: [PATCH 09/37] Add sidebar link for IP-API --- _site/content/posts/modules/ipapi.md | 2 +- _site/themes/hyde-hyde/layouts/partials/sidebar.html | 1 + docs/404.html | 1 + docs/categories/index.html | 1 + docs/index.html | 1 + docs/index.xml | 2 +- docs/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 | 3 ++- docs/posts/index.xml | 2 +- docs/posts/installation/index.html | 1 + docs/posts/modules/bamboohr/index.html | 1 + docs/posts/modules/circleci/index.html | 1 + docs/posts/modules/clocks/index.html | 1 + docs/posts/modules/cmdrunner/index.html | 1 + docs/posts/modules/cryptocurrencies/bittrex/index.html | 1 + docs/posts/modules/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/gspreadsheet/index.html | 1 + docs/posts/modules/index.html | 1 + docs/posts/modules/ipapi/index.html | 7 ++++--- 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/tags/index.html | 1 + 37 files changed, 41 insertions(+), 7 deletions(-) diff --git a/_site/content/posts/modules/ipapi.md b/_site/content/posts/modules/ipapi.md index e93c3410..39182853 100644 --- a/_site/content/posts/modules/ipapi.md +++ b/_site/content/posts/modules/ipapi.md @@ -1,5 +1,5 @@ --- -title: "IP API" +title: "IP-API" date: 2018-06-10T19:41:52-04:00 draft: false --- diff --git a/_site/themes/hyde-hyde/layouts/partials/sidebar.html b/_site/themes/hyde-hyde/layouts/partials/sidebar.html index b8c698a8..9db8a4a9 100644 --- a/_site/themes/hyde-hyde/layouts/partials/sidebar.html +++ b/_site/themes/hyde-hyde/layouts/partials/sidebar.html @@ -32,6 +32,7 @@
        • + diff --git a/docs/404.html b/docs/404.html index a41fa335..e19cb9c0 100644 --- a/docs/404.html +++ b/docs/404.html @@ -73,6 +73,7 @@ + diff --git a/docs/categories/index.html b/docs/categories/index.html index 1e98fb7b..f4593c08 100644 --- a/docs/categories/index.html +++ b/docs/categories/index.html @@ -75,6 +75,7 @@ + diff --git a/docs/index.html b/docs/index.html index 8e536b3a..e046f569 100644 --- a/docs/index.html +++ b/docs/index.html @@ -74,6 +74,7 @@ + diff --git a/docs/index.xml b/docs/index.xml index 0da6f5b2..93293c31 100644 --- a/docs/index.xml +++ b/docs/index.xml @@ -12,7 +12,7 @@ - IP API + IP-API https://wtfutil.com/posts/modules/ipapi/ Sun, 10 Jun 2018 19:41:52 -0400 diff --git a/docs/posts/configuration/attributes/index.html b/docs/posts/configuration/attributes/index.html index 69f954ac..628e04e5 100644 --- a/docs/posts/configuration/attributes/index.html +++ b/docs/posts/configuration/attributes/index.html @@ -73,6 +73,7 @@ + diff --git a/docs/posts/configuration/index.html b/docs/posts/configuration/index.html index ded17263..ee1cb00f 100644 --- a/docs/posts/configuration/index.html +++ b/docs/posts/configuration/index.html @@ -73,6 +73,7 @@ + diff --git a/docs/posts/configuration/iterm2/index.html b/docs/posts/configuration/iterm2/index.html index 9f554d43..719ea690 100644 --- a/docs/posts/configuration/iterm2/index.html +++ b/docs/posts/configuration/iterm2/index.html @@ -73,6 +73,7 @@ + diff --git a/docs/posts/glossary/index.html b/docs/posts/glossary/index.html index 6ed76f0b..da0b1301 100644 --- a/docs/posts/glossary/index.html +++ b/docs/posts/glossary/index.html @@ -73,6 +73,7 @@ + diff --git a/docs/posts/index.html b/docs/posts/index.html index 80f562b5..4bd3b2d9 100644 --- a/docs/posts/index.html +++ b/docs/posts/index.html @@ -75,6 +75,7 @@ + @@ -102,7 +103,7 @@
          • - IP API + IP-API diff --git a/docs/posts/index.xml b/docs/posts/index.xml index f5d9073c..50b3850b 100644 --- a/docs/posts/index.xml +++ b/docs/posts/index.xml @@ -12,7 +12,7 @@ - IP API + IP-API https://wtfutil.com/posts/modules/ipapi/ Sun, 10 Jun 2018 19:41:52 -0400 diff --git a/docs/posts/installation/index.html b/docs/posts/installation/index.html index 74bc87db..5a04af98 100644 --- a/docs/posts/installation/index.html +++ b/docs/posts/installation/index.html @@ -73,6 +73,7 @@
          • + diff --git a/docs/posts/modules/bamboohr/index.html b/docs/posts/modules/bamboohr/index.html index a93f5425..8d0a4040 100644 --- a/docs/posts/modules/bamboohr/index.html +++ b/docs/posts/modules/bamboohr/index.html @@ -73,6 +73,7 @@ + diff --git a/docs/posts/modules/circleci/index.html b/docs/posts/modules/circleci/index.html index f2dd6ca0..0f7c29ec 100644 --- a/docs/posts/modules/circleci/index.html +++ b/docs/posts/modules/circleci/index.html @@ -73,6 +73,7 @@ + diff --git a/docs/posts/modules/clocks/index.html b/docs/posts/modules/clocks/index.html index 8440038e..ed003c8e 100644 --- a/docs/posts/modules/clocks/index.html +++ b/docs/posts/modules/clocks/index.html @@ -73,6 +73,7 @@ + diff --git a/docs/posts/modules/cmdrunner/index.html b/docs/posts/modules/cmdrunner/index.html index 4ea15884..fce2a66b 100644 --- a/docs/posts/modules/cmdrunner/index.html +++ b/docs/posts/modules/cmdrunner/index.html @@ -73,6 +73,7 @@ + diff --git a/docs/posts/modules/cryptocurrencies/bittrex/index.html b/docs/posts/modules/cryptocurrencies/bittrex/index.html index 354041a2..d500939c 100644 --- a/docs/posts/modules/cryptocurrencies/bittrex/index.html +++ b/docs/posts/modules/cryptocurrencies/bittrex/index.html @@ -73,6 +73,7 @@ + diff --git a/docs/posts/modules/cryptocurrencies/cryptolive/index.html b/docs/posts/modules/cryptocurrencies/cryptolive/index.html index 6a61168a..55033842 100644 --- a/docs/posts/modules/cryptocurrencies/cryptolive/index.html +++ b/docs/posts/modules/cryptocurrencies/cryptolive/index.html @@ -73,6 +73,7 @@ + diff --git a/docs/posts/modules/gcal/index.html b/docs/posts/modules/gcal/index.html index 6289bfaf..e50ddb96 100644 --- a/docs/posts/modules/gcal/index.html +++ b/docs/posts/modules/gcal/index.html @@ -73,6 +73,7 @@ + diff --git a/docs/posts/modules/git/index.html b/docs/posts/modules/git/index.html index def87518..b9c4c3b0 100644 --- a/docs/posts/modules/git/index.html +++ b/docs/posts/modules/git/index.html @@ -73,6 +73,7 @@ + diff --git a/docs/posts/modules/github/index.html b/docs/posts/modules/github/index.html index b912baef..65374300 100644 --- a/docs/posts/modules/github/index.html +++ b/docs/posts/modules/github/index.html @@ -73,6 +73,7 @@ + diff --git a/docs/posts/modules/gspreadsheet/index.html b/docs/posts/modules/gspreadsheet/index.html index 4baf7c3a..e1f4f6c3 100644 --- a/docs/posts/modules/gspreadsheet/index.html +++ b/docs/posts/modules/gspreadsheet/index.html @@ -73,6 +73,7 @@ + diff --git a/docs/posts/modules/index.html b/docs/posts/modules/index.html index af454f60..80d1d57b 100644 --- a/docs/posts/modules/index.html +++ b/docs/posts/modules/index.html @@ -73,6 +73,7 @@ + diff --git a/docs/posts/modules/ipapi/index.html b/docs/posts/modules/ipapi/index.html index d8fca0a5..1f75dfce 100644 --- a/docs/posts/modules/ipapi/index.html +++ b/docs/posts/modules/ipapi/index.html @@ -10,8 +10,8 @@ -IP API | WTF - A Terminal Dashboard - +IP-API | WTF - A Terminal Dashboard + @@ -73,6 +73,7 @@ + @@ -97,7 +98,7 @@
            -

            IP API

            +

            IP-API

            +
          • + + Modules: Jenkins + + +
          • diff --git a/docs/posts/index.xml b/docs/posts/index.xml index 0ecc040d..e83b1289 100644 --- a/docs/posts/index.xml +++ b/docs/posts/index.xml @@ -56,6 +56,21 @@ cells.names cells.addresses enabled Whether or not this module is executed and i position Where in the grid this module&rsquo;s widget will be displayed. + + Modules: Jenkins + https://wtfutil.com/posts/modules/jenkins/ + Sat, 09 Jun 2018 20:53:35 -0700 + + https://wtfutil.com/posts/modules/jenkins/ + Added in v0.0.8. +Displays jenkins status of given builds in a project or view +Source Code wtf/jenkins/ Required ENV Variables Key: WTF_JENKINS_API_KEY Value: Your Jenkins API key. +Keyboard Commands None. +Configuration jenkins:enabled:trueposition:top:2left:3height:2width:3refreshInterval:300url:&#34;https://jenkins.domain.com/jenkins/view_url&#34;user:&#34;username&#34; 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&rsquo;s widget will be displayed. +refreshInterval How often, in seconds, this module will update its data. + + Gitlab https://wtfutil.com/posts/modules/gitlab/ diff --git a/docs/posts/installation/index.html b/docs/posts/installation/index.html index 6e077b0a..680b39a0 100644 --- a/docs/posts/installation/index.html +++ b/docs/posts/installation/index.html @@ -76,6 +76,7 @@
          • + diff --git a/docs/posts/modules/bamboohr/index.html b/docs/posts/modules/bamboohr/index.html index 72d55e24..0cea882b 100644 --- a/docs/posts/modules/bamboohr/index.html +++ b/docs/posts/modules/bamboohr/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/circleci/index.html b/docs/posts/modules/circleci/index.html index be92bead..b9e0690a 100644 --- a/docs/posts/modules/circleci/index.html +++ b/docs/posts/modules/circleci/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/clocks/index.html b/docs/posts/modules/clocks/index.html index 39870f33..26d11768 100644 --- a/docs/posts/modules/clocks/index.html +++ b/docs/posts/modules/clocks/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/cmdrunner/index.html b/docs/posts/modules/cmdrunner/index.html index d146dc78..c4242623 100644 --- a/docs/posts/modules/cmdrunner/index.html +++ b/docs/posts/modules/cmdrunner/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/cryptocurrencies/bittrex/index.html b/docs/posts/modules/cryptocurrencies/bittrex/index.html index 9a4b90da..8f126198 100644 --- a/docs/posts/modules/cryptocurrencies/bittrex/index.html +++ b/docs/posts/modules/cryptocurrencies/bittrex/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/cryptocurrencies/cryptolive/index.html b/docs/posts/modules/cryptocurrencies/cryptolive/index.html index 7e9776ca..67108e87 100644 --- a/docs/posts/modules/cryptocurrencies/cryptolive/index.html +++ b/docs/posts/modules/cryptocurrencies/cryptolive/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/gcal/index.html b/docs/posts/modules/gcal/index.html index e145f5a2..29823703 100644 --- a/docs/posts/modules/gcal/index.html +++ b/docs/posts/modules/gcal/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/git/index.html b/docs/posts/modules/git/index.html index 84503c00..cc166eed 100644 --- a/docs/posts/modules/git/index.html +++ b/docs/posts/modules/git/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/github/index.html b/docs/posts/modules/github/index.html index 993eebfa..43de01e6 100644 --- a/docs/posts/modules/github/index.html +++ b/docs/posts/modules/github/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/gitlab/index.html b/docs/posts/modules/gitlab/index.html index 096390ec..048e11a2 100644 --- a/docs/posts/modules/gitlab/index.html +++ b/docs/posts/modules/gitlab/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/gspreadsheet/index.html b/docs/posts/modules/gspreadsheet/index.html index c63af76c..235186c3 100644 --- a/docs/posts/modules/gspreadsheet/index.html +++ b/docs/posts/modules/gspreadsheet/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/index.html b/docs/posts/modules/index.html index 8e30d931..525d9991 100644 --- a/docs/posts/modules/index.html +++ b/docs/posts/modules/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/ipapi/index.html b/docs/posts/modules/ipapi/index.html index ed349da0..f768e9d9 100644 --- a/docs/posts/modules/ipapi/index.html +++ b/docs/posts/modules/ipapi/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/ipinfo/index.html b/docs/posts/modules/ipinfo/index.html index b13c8c60..fd8393a7 100644 --- a/docs/posts/modules/ipinfo/index.html +++ b/docs/posts/modules/ipinfo/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/jenkins/index.html b/docs/posts/modules/jenkins/index.html new file mode 100644 index 00000000..5b3bb5f9 --- /dev/null +++ b/docs/posts/modules/jenkins/index.html @@ -0,0 +1,181 @@ + + + + + + + + + + + + +Modules: Jenkins | WTF - A Terminal Dashboard + + + + + + + + + + + + + + + + + + + + + + + + + + + +
            +
            +

            Modules: Jenkins

            + +
            + +
            + + + +

            Added in v0.0.8.

            + +

            Displays jenkins status of given builds in a project or view

            + +

            jenkins screenshot

            + +

            Source Code

            +
            wtf/jenkins/
            +

            Required ENV Variables

            + +

            Key: WTF_JENKINS_API_KEY
            +Value: Your Jenkins API key.

            + +

            Keyboard Commands

            + +

            None.

            + +

            Configuration

            +
            jenkins:
            +  enabled: true
            +  position:
            +    top: 2
            +    left: 3
            +    height: 2
            +    width: 3
            +  refreshInterval: 300
            +  url: "https://jenkins.domain.com/jenkins/view_url"
            +  user: "username"
            +

            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.

            + +

            user
            +Your Jenkins username.

            + +

            url
            +The url to your Jenkins project or view.
            +Values: A valid URI.

            + +
            + + +
            + + + + diff --git a/docs/posts/modules/jira/index.html b/docs/posts/modules/jira/index.html index 84d8a680..cece665e 100644 --- a/docs/posts/modules/jira/index.html +++ b/docs/posts/modules/jira/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/newrelic/index.html b/docs/posts/modules/newrelic/index.html index 49fb2bf0..8b333652 100644 --- a/docs/posts/modules/newrelic/index.html +++ b/docs/posts/modules/newrelic/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/opsgenie/index.html b/docs/posts/modules/opsgenie/index.html index 242e200f..58db2554 100644 --- a/docs/posts/modules/opsgenie/index.html +++ b/docs/posts/modules/opsgenie/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/power/index.html b/docs/posts/modules/power/index.html index 36eeceee..9523486b 100644 --- a/docs/posts/modules/power/index.html +++ b/docs/posts/modules/power/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/prettyweather/index.html b/docs/posts/modules/prettyweather/index.html index 4a20f1a5..995cf7be 100644 --- a/docs/posts/modules/prettyweather/index.html +++ b/docs/posts/modules/prettyweather/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/security/index.html b/docs/posts/modules/security/index.html index 8139a7ca..d2bd19ef 100644 --- a/docs/posts/modules/security/index.html +++ b/docs/posts/modules/security/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/textfile/index.html b/docs/posts/modules/textfile/index.html index 2720af7d..bfb11fc1 100644 --- a/docs/posts/modules/textfile/index.html +++ b/docs/posts/modules/textfile/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/todo/index.html b/docs/posts/modules/todo/index.html index 02d67c70..d67881e8 100644 --- a/docs/posts/modules/todo/index.html +++ b/docs/posts/modules/todo/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/modules/weather/index.html b/docs/posts/modules/weather/index.html index 5142c248..78a449ee 100644 --- a/docs/posts/modules/weather/index.html +++ b/docs/posts/modules/weather/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/posts/overview/index.html b/docs/posts/overview/index.html index 6b68ea81..0cd74e6d 100644 --- a/docs/posts/overview/index.html +++ b/docs/posts/overview/index.html @@ -76,6 +76,7 @@ + diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 8200ef65..577c3a23 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -17,6 +17,11 @@ 2018-06-10T18:26:26-04:00 + + https://wtfutil.com/posts/modules/jenkins/ + 2018-06-09T20:53:35-07:00 + + https://wtfutil.com/posts/modules/gitlab/ 2018-06-08T13:14:11-07:00 diff --git a/docs/tags/index.html b/docs/tags/index.html index 87abb6ad..04a1ff12 100644 --- a/docs/tags/index.html +++ b/docs/tags/index.html @@ -78,6 +78,7 @@ +