1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-19 13:19:43 +02:00

[WIP] Basic LDAP connectivity

This commit is contained in:
sauls8t 2018-08-28 10:19:22 +01:00
parent f28b7497fa
commit 8d3dfcc3c7
72 changed files with 5039 additions and 3548 deletions

View file

@ -1,29 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Don't check in vendor
vendor/
# 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
*.iml
.idea

View file

@ -1,17 +0,0 @@
language: go
sudo: false
go:
- 1.4
- 1.5
- 1.6
- 1.7
- 1.8
- 1.9
before_install:
- go get -t ./...
script:
- GOMAXPROCS=4 GORACE="halt_on_error=1" go test -race -v ./...

View file

@ -1,36 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/fatih/structs"
packages = ["."]
revision = "a720dfa8df582c51dee1b36feabb906bde1588bd"
version = "v1.0"
[[projects]]
branch = "master"
name = "github.com/google/go-querystring"
packages = ["query"]
revision = "53e6ce116135b80d037921a7fdd5138cf32d7a8a"
[[projects]]
name = "github.com/pkg/errors"
packages = ["."]
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
name = "github.com/trivago/tgo"
packages = [
"tcontainer",
"treflect"
]
revision = "e4d1ddd28c17dd89ed26327cf69fded22060671b"
version = "v1.0.1"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "e84ca9eea6d233e0947b0d760913db2983fd4cbf6fd0d8690c737a71affb635c"
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -1,46 +0,0 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/fatih/structs"
version = "1.0.0"
[[constraint]]
branch = "master"
name = "github.com/google/go-querystring"
[[constraint]]
name = "github.com/pkg/errors"
version = "0.8.0"
[[constraint]]
name = "github.com/trivago/tgo"
version = "1.0.1"
[prune]
go-tests = true
unused-packages = true

View file

@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Andy Grunwald
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,2 +0,0 @@
test:
go test -v ./...

View file

@ -1,271 +0,0 @@
# go-jira
[![GoDoc](https://godoc.org/github.com/andygrunwald/go-jira?status.svg)](https://godoc.org/github.com/andygrunwald/go-jira)
[![Build Status](https://travis-ci.org/andygrunwald/go-jira.svg?branch=master)](https://travis-ci.org/andygrunwald/go-jira)
[![Go Report Card](https://goreportcard.com/badge/github.com/andygrunwald/go-jira)](https://goreportcard.com/report/github.com/andygrunwald/go-jira)
[Go](https://golang.org/) client library for [Atlassian JIRA](https://www.atlassian.com/software/jira).
![Go client library for Atlassian JIRA](./img/logo_small.png "Go client library for Atlassian JIRA.")
## Features
* Authentication (HTTP Basic, OAuth, Session Cookie)
* Create and retrieve issues
* Create and retrieve issue transitions (status updates)
* Call every API endpoint of the JIRA, even if it is not directly implemented in this library
This package is not JIRA API complete (yet), but you can call every API endpoint you want. See [Call a not implemented API endpoint](#call-a-not-implemented-api-endpoint) how to do this. For all possible API endpoints of JIRA have a look at [latest JIRA REST API documentation](https://docs.atlassian.com/jira/REST/latest/).
## Compatible JIRA versions
This package was tested against JIRA v6.3.4 and v7.1.2.
## Installation
It is go gettable
$ go get github.com/andygrunwald/go-jira
For stable versions you can use one of our tags with [gopkg.in](http://labix.org/gopkg.in). E.g.
```go
package main
import (
jira "gopkg.in/andygrunwald/go-jira.v1"
)
...
```
(optional) to run unit / example tests:
$ cd $GOPATH/src/github.com/andygrunwald/go-jira
$ go test -v ./...
## API
Please have a look at the [GoDoc documentation](https://godoc.org/github.com/andygrunwald/go-jira) for a detailed API description.
The [latest JIRA REST API documentation](https://docs.atlassian.com/jira/REST/latest/) was the base document for this package.
## Examples
Further a few examples how the API can be used.
A few more examples are available in the [GoDoc examples section](https://godoc.org/github.com/andygrunwald/go-jira#pkg-examples).
### Get a single issue
Lets retrieve [MESOS-3325](https://issues.apache.org/jira/browse/MESOS-3325) from the [Apache Mesos](http://mesos.apache.org/) project.
```go
package main
import (
"fmt"
"github.com/andygrunwald/go-jira"
)
func main() {
jiraClient, _ := jira.NewClient(nil, "https://issues.apache.org/jira/")
issue, _, _ := jiraClient.Issue.Get("MESOS-3325", nil)
fmt.Printf("%s: %+v\n", issue.Key, issue.Fields.Summary)
fmt.Printf("Type: %s\n", issue.Fields.Type.Name)
fmt.Printf("Priority: %s\n", issue.Fields.Priority.Name)
// MESOS-3325: Running mesos-slave@0.23 in a container causes slave to be lost after a restart
// Type: Bug
// Priority: Critical
}
```
### Authentication
The `go-jira` library does not handle most authentication directly. Instead, authentication should be handled within
an `http.Client`. That client can then be passed into the `NewClient` function when creating a jira client.
For convenience, capability for basic and cookie-based authentication is included in the main library.
#### Basic auth example
A more thorough, [runnable example](examples/basicauth/main.go) is provided in the examples directory.
```go
func main() {
tp := jira.BasicAuthTransport{
Username: "username",
Password: "password",
}
client, err := jira.NewClient(tp.Client(), "https://my.jira.com")
u, _, err := client.User.Get("some_user")
fmt.Printf("\nEmail: %v\nSuccess!\n", u.EmailAddress)
}
```
#### Authenticate with session cookie
A more thorough, [runnable example](examples/cookieauth/main.go) is provided in the examples directory.
Note: The `AuthURL` is almost always going to have the path `/rest/auth/1/session`
```go
tp := jira.CookieAuthTransport{
Username: "username",
Password: "password",
AuthURL: "https://my.jira.com/rest/auth/1/session",
}
client, err := jira.NewClient(tp.Client(), "https://my.jira.com")
u, _, err := client.User.Get("admin")
fmt.Printf("\nEmail: %v\nSuccess!\n", u.EmailAddress)
}
```
#### Authenticate with OAuth
If you want to connect via OAuth to your JIRA Cloud instance checkout the [example of using OAuth authentication with JIRA in Go](https://gist.github.com/Lupus/edafe9a7c5c6b13407293d795442fe67) by [@Lupus](https://github.com/Lupus).
For more details have a look at the [issue #56](https://github.com/andygrunwald/go-jira/issues/56).
### Create an issue
Example how to create an issue.
```go
package main
import (
"fmt"
"github.com/andygrunwald/go-jira"
)
func main() {
base := "https://my.jira.com"
tp := jira.CookieAuthTransport{
Username: "username",
Password: "password",
AuthURL: fmt.Sprintf("%s/rest/auth/1/session", base),
}
jiraClient, err := jira.NewClient(tp.Client(), base)
if err != nil {
panic(err)
}
i := jira.Issue{
Fields: &jira.IssueFields{
Assignee: &jira.User{
Name: "myuser",
},
Reporter: &jira.User{
Name: "youruser",
},
Description: "Test Issue",
Type: jira.IssueType{
Name: "Bug",
},
Project: jira.Project{
Key: "PROJ1",
},
Summary: "Just a demo issue",
},
}
issue, _, err := jiraClient.Issue.Create(&i)
if err != nil {
panic(err)
}
fmt.Printf("%s: %+v\n", issue.Key, issue.Fields.Summary)
}
```
### Call a not implemented API endpoint
Not all API endpoints of the JIRA API are implemented into *go-jira*.
But you can call them anyway:
Lets get all public projects of [Atlassian`s JIRA instance](https://jira.atlassian.com/).
```go
package main
import (
"fmt"
"github.com/andygrunwald/go-jira"
)
func main() {
base := "https://my.jira.com"
tp := jira.CookieAuthTransport{
Username: "username",
Password: "password",
AuthURL: fmt.Sprintf("%s/rest/auth/1/session", base),
}
jiraClient, err := jira.NewClient(tp.Client(), base)
req, _ := jiraClient.NewRequest("GET", "rest/api/2/project", nil)
projects := new([]jira.Project)
_, err := jiraClient.Do(req, projects)
if err != nil {
panic(err)
}
for _, project := range *projects {
fmt.Printf("%s: %s\n", project.Key, project.Name)
}
// ...
// BAM: Bamboo
// BAMJ: Bamboo JIRA Plugin
// CLOV: Clover
// CONF: Confluence
// ...
}
```
## Implementations
* [andygrunwald/jitic](https://github.com/andygrunwald/jitic) - The JIRA Ticket Checker
## Code structure
The code structure of this package was inspired by [google/go-github](https://github.com/google/go-github).
There is one main part (the client).
Based on this main client the other endpoints, like Issues or Authentication are extracted in services. E.g. `IssueService` or `AuthenticationService`.
These services own a responsibility of the single endpoints / usecases of JIRA.
## Contribution
Contribution, in any kind of way, is highly welcome!
It doesn't matter if you are not able to write code.
Creating issues or holding talks and help other people to use [go-jira](https://github.com/andygrunwald/go-jira) is contribution, too!
A few examples:
* Correct typos in the README / documentation
* Reporting bugs
* Implement a new feature or endpoint
* Sharing the love if [go-jira](https://github.com/andygrunwald/go-jira) and help people to get use to it
If you are new to pull requests, checkout [Collaborating on projects using issues and pull requests / Creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
### Dependency management
`go-jira` uses `dep` for dependency management. After cloning the repo, it's easy to make sure you have the correct dependencies by running `dep ensure`.
For adding new dependencies, updating dependencies, and other operations, the [Daily Dep](https://golang.github.io/dep/docs/daily-dep.html) is a good place to start.
### Sandbox environment for testing
Jira offers sandbox test environments at http://go.atlassian.com/cloud-dev.
You can read more about them at https://developer.atlassian.com/blog/2016/04/cloud-ecosystem-dev-env/.
## License
This project is released under the terms of the [MIT license](http://en.wikipedia.org/wiki/MIT_License).

View file

@ -1,187 +0,0 @@
package jira
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
const (
// HTTP Basic Authentication
authTypeBasic = 1
// HTTP Session Authentication
authTypeSession = 2
)
// AuthenticationService handles authentication for the JIRA instance / API.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#authentication
type AuthenticationService struct {
client *Client
// Authentication type
authType int
// Basic auth username
username string
// Basic auth password
password string
}
// Session represents a Session JSON response by the JIRA API.
type Session struct {
Self string `json:"self,omitempty"`
Name string `json:"name,omitempty"`
Session struct {
Name string `json:"name"`
Value string `json:"value"`
} `json:"session,omitempty"`
LoginInfo struct {
FailedLoginCount int `json:"failedLoginCount"`
LoginCount int `json:"loginCount"`
LastFailedLoginTime string `json:"lastFailedLoginTime"`
PreviousLoginTime string `json:"previousLoginTime"`
} `json:"loginInfo"`
Cookies []*http.Cookie
}
// AcquireSessionCookie creates a new session for a user in JIRA.
// Once a session has been successfully created it can be used to access any of JIRA's remote APIs and also the web UI by passing the appropriate HTTP Cookie header.
// The header will by automatically applied to every API request.
// Note that it is generally preferrable to use HTTP BASIC authentication with the REST API.
// However, this resource may be used to mimic the behaviour of JIRA's log-in page (e.g. to display log-in errors to a user).
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session
//
// Deprecated: Use CookieAuthTransport instead
func (s *AuthenticationService) AcquireSessionCookie(username, password string) (bool, error) {
apiEndpoint := "rest/auth/1/session"
body := struct {
Username string `json:"username"`
Password string `json:"password"`
}{
username,
password,
}
req, err := s.client.NewRequest("POST", apiEndpoint, body)
if err != nil {
return false, err
}
session := new(Session)
resp, err := s.client.Do(req, session)
if resp != nil {
session.Cookies = resp.Cookies()
}
if err != nil {
return false, fmt.Errorf("Auth at JIRA instance failed (HTTP(S) request). %s", err)
}
if resp != nil && resp.StatusCode != 200 {
return false, fmt.Errorf("Auth at JIRA instance failed (HTTP(S) request). Status code: %d", resp.StatusCode)
}
s.client.session = session
s.authType = authTypeSession
return true, nil
}
// SetBasicAuth sets username and password for the basic auth against the JIRA instance.
//
// Deprecated: Use BasicAuthTransport instead
func (s *AuthenticationService) SetBasicAuth(username, password string) {
s.username = username
s.password = password
s.authType = authTypeBasic
}
// Authenticated reports if the current Client has authentication details for JIRA
func (s *AuthenticationService) Authenticated() bool {
if s != nil {
if s.authType == authTypeSession {
return s.client.session != nil
} else if s.authType == authTypeBasic {
return s.username != ""
}
}
return false
}
// Logout logs out the current user that has been authenticated and the session in the client is destroyed.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session
//
// Deprecated: Use CookieAuthTransport to create base client. Logging out is as simple as not using the
// client anymore
func (s *AuthenticationService) Logout() error {
if s.authType != authTypeSession || s.client.session == nil {
return fmt.Errorf("no user is authenticated")
}
apiEndpoint := "rest/auth/1/session"
req, err := s.client.NewRequest("DELETE", apiEndpoint, nil)
if err != nil {
return fmt.Errorf("Creating the request to log the user out failed : %s", err)
}
resp, err := s.client.Do(req, nil)
if err != nil {
return fmt.Errorf("Error sending the logout request: %s", err)
}
if resp.StatusCode != 204 {
return fmt.Errorf("The logout was unsuccessful with status %d", resp.StatusCode)
}
// If logout successful, delete session
s.client.session = nil
return nil
}
// GetCurrentUser gets the details of the current user.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session
func (s *AuthenticationService) GetCurrentUser() (*Session, error) {
if s == nil {
return nil, fmt.Errorf("AUthenticaiton Service is not instantiated")
}
if s.authType != authTypeSession || s.client.session == nil {
return nil, fmt.Errorf("No user is authenticated yet")
}
apiEndpoint := "rest/auth/1/session"
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, fmt.Errorf("Could not create request for getting user info : %s", err)
}
resp, err := s.client.Do(req, nil)
if err != nil {
return nil, fmt.Errorf("Error sending request to get user info : %s", err)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("Getting user info failed with status : %d", resp.StatusCode)
}
defer resp.Body.Close()
ret := new(Session)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("Couldn't read body from the response : %s", err)
}
err = json.Unmarshal(data, &ret)
if err != nil {
return nil, fmt.Errorf("Could not unmarshall received user info : %s", err)
}
return ret, nil
}

View file

@ -1,204 +0,0 @@
package jira
import (
"fmt"
"strconv"
"time"
)
// BoardService handles Agile Boards for the JIRA instance / API.
//
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/server/
type BoardService struct {
client *Client
}
// BoardsList reflects a list of agile boards
type BoardsList struct {
MaxResults int `json:"maxResults" structs:"maxResults"`
StartAt int `json:"startAt" structs:"startAt"`
Total int `json:"total" structs:"total"`
IsLast bool `json:"isLast" structs:"isLast"`
Values []Board `json:"values" structs:"values"`
}
// Board represents a JIRA agile board
type Board struct {
ID int `json:"id,omitempty" structs:"id,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"`
Name string `json:"name,omitempty" structs:"name,omitemtpy"`
Type string `json:"type,omitempty" structs:"type,omitempty"`
FilterID int `json:"filterId,omitempty" structs:"filterId,omitempty"`
}
// BoardListOptions specifies the optional parameters to the BoardService.GetList
type BoardListOptions struct {
// BoardType filters results to boards of the specified type.
// Valid values: scrum, kanban.
BoardType string `url:"boardType,omitempty"`
// Name filters results to boards that match or partially match the specified name.
Name string `url:"name,omitempty"`
// ProjectKeyOrID filters results to boards that are relevant to a project.
// Relevance meaning that the JQL filter defined in board contains a reference to a project.
ProjectKeyOrID string `url:"projectKeyOrId,omitempty"`
SearchOptions
}
// GetAllSprintsOptions specifies the optional parameters to the BoardService.GetList
type GetAllSprintsOptions struct {
// State filters results to sprints in the specified states, comma-separate list
State string `url:"state,omitempty"`
SearchOptions
}
// SprintsList reflects a list of agile sprints
type SprintsList struct {
MaxResults int `json:"maxResults" structs:"maxResults"`
StartAt int `json:"startAt" structs:"startAt"`
Total int `json:"total" structs:"total"`
IsLast bool `json:"isLast" structs:"isLast"`
Values []Sprint `json:"values" structs:"values"`
}
// Sprint represents a sprint on JIRA agile board
type Sprint struct {
ID int `json:"id" structs:"id"`
Name string `json:"name" structs:"name"`
CompleteDate *time.Time `json:"completeDate" structs:"completeDate"`
EndDate *time.Time `json:"endDate" structs:"endDate"`
StartDate *time.Time `json:"startDate" structs:"startDate"`
OriginBoardID int `json:"originBoardId" structs:"originBoardId"`
Self string `json:"self" structs:"self"`
State string `json:"state" structs:"state"`
}
// GetAllBoards will returns all boards. This only includes boards that the user has permission to view.
//
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getAllBoards
func (s *BoardService) GetAllBoards(opt *BoardListOptions) (*BoardsList, *Response, error) {
apiEndpoint := "rest/agile/1.0/board"
url, err := addOptions(apiEndpoint, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", url, nil)
if err != nil {
return nil, nil, err
}
boards := new(BoardsList)
resp, err := s.client.Do(req, boards)
if err != nil {
jerr := NewJiraError(resp, err)
return nil, resp, jerr
}
return boards, resp, err
}
// GetBoard will returns the board for the given boardID.
// This board will only be returned if the user has permission to view it.
//
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getBoard
func (s *BoardService) GetBoard(boardID int) (*Board, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
board := new(Board)
resp, err := s.client.Do(req, board)
if err != nil {
jerr := NewJiraError(resp, err)
return nil, resp, jerr
}
return board, resp, nil
}
// CreateBoard creates a new board. Board name, type and filter Id is required.
// name - Must be less than 255 characters.
// type - Valid values: scrum, kanban
// filterId - Id of a filter that the user has permissions to view.
// Note, if the user does not have the 'Create shared objects' permission and tries to create a shared board, a private
// board will be created instead (remember that board sharing depends on the filter sharing).
//
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-createBoard
func (s *BoardService) CreateBoard(board *Board) (*Board, *Response, error) {
apiEndpoint := "rest/agile/1.0/board"
req, err := s.client.NewRequest("POST", apiEndpoint, board)
if err != nil {
return nil, nil, err
}
responseBoard := new(Board)
resp, err := s.client.Do(req, responseBoard)
if err != nil {
jerr := NewJiraError(resp, err)
return nil, resp, jerr
}
return responseBoard, resp, nil
}
// DeleteBoard will delete an agile board.
//
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-deleteBoard
func (s *BoardService) DeleteBoard(boardID int) (*Board, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID)
req, err := s.client.NewRequest("DELETE", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
resp, err := s.client.Do(req, nil)
if err != nil {
err = NewJiraError(resp, err)
}
return nil, resp, err
}
// GetAllSprints will return all sprints from a board, for a given board Id.
// This only includes sprints that the user has permission to view.
//
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint
func (s *BoardService) GetAllSprints(boardID string) ([]Sprint, *Response, error) {
id, err := strconv.Atoi(boardID)
if err != nil {
return nil, nil, err
}
result, response, err := s.GetAllSprintsWithOptions(id, &GetAllSprintsOptions{})
if err != nil {
return nil, nil, err
}
return result.Values, response, nil
}
// GetAllSprintsWithOptions will return sprints from a board, for a given board Id and filtering options
// This only includes sprints that the user has permission to view.
//
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint
func (s *BoardService) GetAllSprintsWithOptions(boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/sprint", boardID)
url, err := addOptions(apiEndpoint, options)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", url, nil)
if err != nil {
return nil, nil, err
}
result := new(SprintsList)
resp, err := s.client.Do(req, result)
if err != nil {
err = NewJiraError(resp, err)
}
return result, resp, err
}

View file

@ -1,38 +0,0 @@
package jira
// ComponentService handles components for the JIRA instance / API.
//
// JIRA API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.10.1/#api/2/component
type ComponentService struct {
client *Client
}
// CreateComponentOptions are passed to the ComponentService.Create function to create a new JIRA component
type CreateComponentOptions struct {
Name string `json:"name,omitempty" structs:"name,omitempty"`
Description string `json:"description,omitempty" structs:"description,omitempty"`
Lead *User `json:"lead,omitempty" structs:"lead,omitempty"`
LeadUserName string `json:"leadUserName,omitempty" structs:"leadUserName,omitempty"`
AssigneeType string `json:"assigneeType,omitempty" structs:"assigneeType,omitempty"`
Assignee *User `json:"assignee,omitempty" structs:"assignee,omitempty"`
Project string `json:"project,omitempty" structs:"project,omitempty"`
ProjectID int `json:"projectId,omitempty" structs:"projectId,omitempty"`
}
// Create creates a new JIRA component based on the given options.
func (s *ComponentService) Create(options *CreateComponentOptions) (*ProjectComponent, *Response, error) {
apiEndpoint := "rest/api/2/component"
req, err := s.client.NewRequest("POST", apiEndpoint, options)
if err != nil {
return nil, nil, err
}
component := new(ProjectComponent)
resp, err := s.client.Do(req, component)
if err != nil {
return nil, resp, NewJiraError(resp, err)
}
return component, resp, nil
}

View file

@ -1,82 +0,0 @@
package jira
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"github.com/pkg/errors"
)
// Error message from JIRA
// See https://docs.atlassian.com/jira/REST/cloud/#error-responses
type Error struct {
HTTPError error
ErrorMessages []string `json:"errorMessages"`
Errors map[string]string `json:"errors"`
}
// NewJiraError creates a new jira Error
func NewJiraError(resp *Response, httpError error) error {
if resp == nil {
return errors.Wrap(httpError, "No response returned")
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return errors.Wrap(err, httpError.Error())
}
jerr := Error{HTTPError: httpError}
err = json.Unmarshal(body, &jerr)
if err != nil {
httpError = errors.Wrap(errors.New("Could not parse JSON"), httpError.Error())
return errors.Wrap(err, httpError.Error())
}
return &jerr
}
// Error is a short string representing the error
func (e *Error) Error() string {
if len(e.ErrorMessages) > 0 {
// return fmt.Sprintf("%v", e.HTTPError)
return fmt.Sprintf("%s: %v", e.ErrorMessages[0], e.HTTPError)
}
if len(e.Errors) > 0 {
for key, value := range e.Errors {
return fmt.Sprintf("%s - %s: %v", key, value, e.HTTPError)
}
}
return e.HTTPError.Error()
}
// LongError is a full representation of the error as a string
func (e *Error) LongError() string {
var msg bytes.Buffer
if e.HTTPError != nil {
msg.WriteString("Original:\n")
msg.WriteString(e.HTTPError.Error())
msg.WriteString("\n")
}
if len(e.ErrorMessages) > 0 {
msg.WriteString("Messages:\n")
for _, v := range e.ErrorMessages {
msg.WriteString(" - ")
msg.WriteString(v)
msg.WriteString("\n")
}
}
if len(e.Errors) > 0 {
for key, value := range e.Errors {
msg.WriteString(" - ")
msg.WriteString(key)
msg.WriteString(" - ")
msg.WriteString(value)
msg.WriteString("\n")
}
}
return msg.String()
}

View file

@ -1,43 +0,0 @@
package jira
// FieldService handles fields for the JIRA instance / API.
//
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Field
type FieldService struct {
client *Client
}
// Field represents a field of a JIRA issue.
type Field struct {
ID string `json:"id,omitempty" structs:"id,omitempty"`
Key string `json:"key,omitempty" structs:"key,omitempty"`
Name string `json:"name,omitempty" structs:"name,omitempty"`
Custom bool `json:"custom,omitempty" structs:"custom,omitempty"`
Navigable bool `json:"navigable,omitempty" structs:"navigable,omitempty"`
Searchable bool `json:"searchable,omitempty" structs:"searchable,omitempty"`
ClauseNames []string `json:"clauseNames,omitempty" structs:"clauseNames,omitempty"`
Schema FieldSchema `json:"schema,omitempty" structs:"schema,omitempty"`
}
type FieldSchema struct {
Type string `json:"type,omitempty" structs:"type,omitempty"`
System string `json:"system,omitempty" structs:"system,omitempty"`
}
// GetList gets all fields from JIRA
//
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-field-get
func (s *FieldService) GetList() ([]Field, *Response, error) {
apiEndpoint := "rest/api/2/field"
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
fieldList := []Field{}
resp, err := s.client.Do(req, &fieldList)
if err != nil {
return nil, resp, NewJiraError(resp, err)
}
return fieldList, resp, nil
}

View file

@ -1,154 +0,0 @@
package jira
import (
"fmt"
"net/url"
)
// GroupService handles Groups for the JIRA instance / API.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group
type GroupService struct {
client *Client
}
// groupMembersResult is only a small wrapper around the Group* methods
// to be able to parse the results
type groupMembersResult struct {
StartAt int `json:"startAt"`
MaxResults int `json:"maxResults"`
Total int `json:"total"`
Members []GroupMember `json:"values"`
}
// Group represents a JIRA group
type Group struct {
ID string `json:"id"`
Title string `json:"title"`
Type string `json:"type"`
Properties groupProperties `json:"properties"`
AdditionalProperties bool `json:"additionalProperties"`
}
type groupProperties struct {
Name groupPropertiesName `json:"name"`
}
type groupPropertiesName struct {
Type string `json:"type"`
}
// GroupMember reflects a single member of a group
type GroupMember struct {
Self string `json:"self,omitempty"`
Name string `json:"name,omitempty"`
Key string `json:"key,omitempty"`
EmailAddress string `json:"emailAddress,omitempty"`
DisplayName string `json:"displayName,omitempty"`
Active bool `json:"active,omitempty"`
TimeZone string `json:"timeZone,omitempty"`
}
// GroupSearchOptions specifies the optional parameters for the Get Group methods
type GroupSearchOptions struct {
StartAt int
MaxResults int
IncludeInactiveUsers bool
}
// Get returns a paginated list of users who are members of the specified group and its subgroups.
// Users in the page are ordered by user names.
// User of this resource is required to have sysadmin or admin permissions.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup
//
// WARNING: This API only returns the first page of group members
func (s *GroupService) Get(name string) ([]GroupMember, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name))
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
group := new(groupMembersResult)
resp, err := s.client.Do(req, group)
if err != nil {
return nil, resp, err
}
return group.Members, resp, nil
}
// GetWithOptions returns a paginated list of members of the specified group and its subgroups.
// Users in the page are ordered by user names.
// User of this resource is required to have sysadmin or admin permissions.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup
func (s *GroupService) GetWithOptions(name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) {
var apiEndpoint string
if options == nil {
apiEndpoint = fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name))
} else {
apiEndpoint = fmt.Sprintf(
"/rest/api/2/group/member?groupname=%s&startAt=%d&maxResults=%d&includeInactiveUsers=%t",
url.QueryEscape(name),
options.StartAt,
options.MaxResults,
options.IncludeInactiveUsers,
)
}
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
group := new(groupMembersResult)
resp, err := s.client.Do(req, group)
if err != nil {
return nil, resp, err
}
return group.Members, resp, nil
}
// Add adds user to group
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-addUserToGroup
func (s *GroupService) Add(groupname string, username string) (*Group, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s", groupname)
var user struct {
Name string `json:"name"`
}
user.Name = username
req, err := s.client.NewRequest("POST", apiEndpoint, &user)
if err != nil {
return nil, nil, err
}
responseGroup := new(Group)
resp, err := s.client.Do(req, responseGroup)
if err != nil {
jerr := NewJiraError(resp, err)
return nil, resp, jerr
}
return responseGroup, resp, nil
}
// Remove removes user from group
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-removeUserFromGroup
func (s *GroupService) Remove(groupname string, username string) (*Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s&username=%s", groupname, username)
req, err := s.client.NewRequest("DELETE", apiEndpoint, nil)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
if err != nil {
jerr := NewJiraError(resp, err)
return resp, jerr
}
return resp, nil
}

File diff suppressed because it is too large Load diff

View file

@ -1,451 +0,0 @@
package jira
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"reflect"
"strings"
"time"
"github.com/google/go-querystring/query"
"github.com/pkg/errors"
)
// A Client manages communication with the JIRA API.
type Client struct {
// HTTP client used to communicate with the API.
client *http.Client
// Base URL for API requests.
baseURL *url.URL
// Session storage if the user authentificate with a Session cookie
session *Session
// Services used for talking to different parts of the JIRA API.
Authentication *AuthenticationService
Issue *IssueService
Project *ProjectService
Board *BoardService
Sprint *SprintService
User *UserService
Group *GroupService
Version *VersionService
Priority *PriorityService
Field *FieldService
Component *ComponentService
Resolution *ResolutionService
StatusCategory *StatusCategoryService
}
// NewClient returns a new JIRA API client.
// If a nil httpClient is provided, http.DefaultClient will be used.
// To use API methods which require authentication you can follow the preferred solution and
// provide an http.Client that will perform the authentication for you with OAuth and HTTP Basic (such as that provided by the golang.org/x/oauth2 library).
// As an alternative you can use Session Cookie based authentication provided by this package as well.
// See https://docs.atlassian.com/jira/REST/latest/#authentication
// baseURL is the HTTP endpoint of your JIRA instance and should always be specified with a trailing slash.
func NewClient(httpClient *http.Client, baseURL string) (*Client, error) {
if httpClient == nil {
httpClient = http.DefaultClient
}
// ensure the baseURL contains a trailing slash so that all paths are preserved in later calls
if !strings.HasSuffix(baseURL, "/") {
baseURL += "/"
}
parsedBaseURL, err := url.Parse(baseURL)
if err != nil {
return nil, err
}
c := &Client{
client: httpClient,
baseURL: parsedBaseURL,
}
c.Authentication = &AuthenticationService{client: c}
c.Issue = &IssueService{client: c}
c.Project = &ProjectService{client: c}
c.Board = &BoardService{client: c}
c.Sprint = &SprintService{client: c}
c.User = &UserService{client: c}
c.Group = &GroupService{client: c}
c.Version = &VersionService{client: c}
c.Priority = &PriorityService{client: c}
c.Field = &FieldService{client: c}
c.Component = &ComponentService{client: c}
c.Resolution = &ResolutionService{client: c}
c.StatusCategory = &StatusCategoryService{client: c}
return c, nil
}
// NewRawRequest creates an API request.
// A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client.
// Allows using an optional native io.Reader for sourcing the request body.
func (c *Client) NewRawRequest(method, urlStr string, body io.Reader) (*http.Request, error) {
rel, err := url.Parse(urlStr)
if err != nil {
return nil, err
}
// Relative URLs should be specified without a preceding slash since baseURL will have the trailing slash
rel.Path = strings.TrimLeft(rel.Path, "/")
u := c.baseURL.ResolveReference(rel)
req, err := http.NewRequest(method, u.String(), body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
// Set authentication information
if c.Authentication.authType == authTypeSession {
// Set session cookie if there is one
if c.session != nil {
for _, cookie := range c.session.Cookies {
req.AddCookie(cookie)
}
}
} else if c.Authentication.authType == authTypeBasic {
// Set basic auth information
if c.Authentication.username != "" {
req.SetBasicAuth(c.Authentication.username, c.Authentication.password)
}
}
return req, nil
}
// NewRequest creates an API request.
// A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client.
// If specified, the value pointed to by body is JSON encoded and included as the request body.
func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
rel, err := url.Parse(urlStr)
if err != nil {
return nil, err
}
// Relative URLs should be specified without a preceding slash since baseURL will have the trailing slash
rel.Path = strings.TrimLeft(rel.Path, "/")
u := c.baseURL.ResolveReference(rel)
var buf io.ReadWriter
if body != nil {
buf = new(bytes.Buffer)
err = json.NewEncoder(buf).Encode(body)
if err != nil {
return nil, err
}
}
req, err := http.NewRequest(method, u.String(), buf)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
// Set authentication information
if c.Authentication.authType == authTypeSession {
// Set session cookie if there is one
if c.session != nil {
for _, cookie := range c.session.Cookies {
req.AddCookie(cookie)
}
}
} else if c.Authentication.authType == authTypeBasic {
// Set basic auth information
if c.Authentication.username != "" {
req.SetBasicAuth(c.Authentication.username, c.Authentication.password)
}
}
return req, nil
}
// addOptions adds the parameters in opt as URL query parameters to s. opt
// must be a struct whose fields may contain "url" tags.
func addOptions(s string, opt interface{}) (string, error) {
v := reflect.ValueOf(opt)
if v.Kind() == reflect.Ptr && v.IsNil() {
return s, nil
}
u, err := url.Parse(s)
if err != nil {
return s, err
}
qs, err := query.Values(opt)
if err != nil {
return s, err
}
u.RawQuery = qs.Encode()
return u.String(), nil
}
// NewMultiPartRequest creates an API request including a multi-part file.
// A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client.
// If specified, the value pointed to by buf is a multipart form.
func (c *Client) NewMultiPartRequest(method, urlStr string, buf *bytes.Buffer) (*http.Request, error) {
rel, err := url.Parse(urlStr)
if err != nil {
return nil, err
}
// Relative URLs should be specified without a preceding slash since baseURL will have the trailing slash
rel.Path = strings.TrimLeft(rel.Path, "/")
u := c.baseURL.ResolveReference(rel)
req, err := http.NewRequest(method, u.String(), buf)
if err != nil {
return nil, err
}
// Set required headers
req.Header.Set("X-Atlassian-Token", "nocheck")
// Set authentication information
if c.Authentication.authType == authTypeSession {
// Set session cookie if there is one
if c.session != nil {
for _, cookie := range c.session.Cookies {
req.AddCookie(cookie)
}
}
} else if c.Authentication.authType == authTypeBasic {
// Set basic auth information
if c.Authentication.username != "" {
req.SetBasicAuth(c.Authentication.username, c.Authentication.password)
}
}
return req, nil
}
// 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.
func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
httpResp, err := c.client.Do(req)
if err != nil {
return nil, err
}
err = CheckResponse(httpResp)
if err != nil {
// Even though there was an error, we still return the response
// in case the caller wants to inspect it further
return newResponse(httpResp, nil), err
}
if v != nil {
// Open a NewDecoder and defer closing the reader only if there is a provided interface to decode to
defer httpResp.Body.Close()
err = json.NewDecoder(httpResp.Body).Decode(v)
}
resp := newResponse(httpResp, v)
return resp, err
}
// CheckResponse checks the API response for errors, and returns them if present.
// A response is considered an error if it has a status code outside the 200 range.
// The caller is responsible to analyze the response body.
// The body can contain JSON (if the error is intended) or xml (sometimes JIRA just failes).
func CheckResponse(r *http.Response) error {
if c := r.StatusCode; 200 <= c && c <= 299 {
return nil
}
err := fmt.Errorf("Request failed. Please analyze the request body for more details. Status code: %d", r.StatusCode)
return err
}
// GetBaseURL will return you the Base URL.
// This is the same URL as in the NewClient constructor
func (c *Client) GetBaseURL() url.URL {
return *c.baseURL
}
// Response represents JIRA API response. It wraps http.Response returned from
// API and provides information about paging.
type Response struct {
*http.Response
StartAt int
MaxResults int
Total int
}
func newResponse(r *http.Response, v interface{}) *Response {
resp := &Response{Response: r}
resp.populatePageValues(v)
return resp
}
// Sets paging values if response json was parsed to searchResult type
// (can be extended with other types if they also need paging info)
func (r *Response) populatePageValues(v interface{}) {
switch value := v.(type) {
case *searchResult:
r.StartAt = value.StartAt
r.MaxResults = value.MaxResults
r.Total = value.Total
case *groupMembersResult:
r.StartAt = value.StartAt
r.MaxResults = value.MaxResults
r.Total = value.Total
}
return
}
// BasicAuthTransport is an http.RoundTripper that authenticates all requests
// using HTTP Basic Authentication with the provided username and password.
type BasicAuthTransport struct {
Username string
Password string
// Transport is the underlying HTTP transport to use when making requests.
// It will default to http.DefaultTransport if nil.
Transport http.RoundTripper
}
// RoundTrip implements the RoundTripper interface. We just add the
// basic auth and return the RoundTripper for this transport type.
func (t *BasicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req2 := cloneRequest(req) // per RoundTripper contract
req2.SetBasicAuth(t.Username, t.Password)
return t.transport().RoundTrip(req2)
}
// Client returns an *http.Client that makes requests that are authenticated
// using HTTP Basic Authentication. This is a nice little bit of sugar
// so we can just get the client instead of creating the client in the calling code.
// If it's necessary to send more information on client init, the calling code can
// always skip this and set the transport itself.
func (t *BasicAuthTransport) Client() *http.Client {
return &http.Client{Transport: t}
}
func (t *BasicAuthTransport) transport() http.RoundTripper {
if t.Transport != nil {
return t.Transport
}
return http.DefaultTransport
}
// CookieAuthTransport is an http.RoundTripper that authenticates all requests
// using Jira's cookie-based authentication.
//
// Note that it is generally preferrable to use HTTP BASIC authentication with the REST API.
// However, this resource may be used to mimic the behaviour of JIRA's log-in page (e.g. to display log-in errors to a user).
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session
type CookieAuthTransport struct {
Username string
Password string
AuthURL string
// SessionObject is the authenticated cookie string.s
// It's passed in each call to prove the client is authenticated.
SessionObject []*http.Cookie
// Transport is the underlying HTTP transport to use when making requests.
// It will default to http.DefaultTransport if nil.
Transport http.RoundTripper
}
// RoundTrip adds the session object to the request.
func (t *CookieAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if t.SessionObject == nil {
err := t.setSessionObject()
if err != nil {
return nil, errors.Wrap(err, "cookieauth: no session object has been set")
}
}
req2 := cloneRequest(req) // per RoundTripper contract
for _, cookie := range t.SessionObject {
req2.AddCookie(cookie)
}
return t.transport().RoundTrip(req2)
}
// Client returns an *http.Client that makes requests that are authenticated
// using cookie authentication
func (t *CookieAuthTransport) Client() *http.Client {
return &http.Client{Transport: t}
}
// setSessionObject attempts to authenticate the user and set
// the session object (e.g. cookie)
func (t *CookieAuthTransport) setSessionObject() error {
req, err := t.buildAuthRequest()
if err != nil {
return err
}
var authClient = &http.Client{
Timeout: time.Second * 60,
}
resp, err := authClient.Do(req)
if err != nil {
return err
}
t.SessionObject = resp.Cookies()
return nil
}
// getAuthRequest assembles the request to get the authenticated cookie
func (t *CookieAuthTransport) buildAuthRequest() (*http.Request, error) {
body := struct {
Username string `json:"username"`
Password string `json:"password"`
}{
t.Username,
t.Password,
}
b := new(bytes.Buffer)
json.NewEncoder(b).Encode(body)
req, err := http.NewRequest("POST", t.AuthURL, b)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
return req, nil
}
func (t *CookieAuthTransport) transport() http.RoundTripper {
if t.Transport != nil {
return t.Transport
}
return http.DefaultTransport
}
// cloneRequest returns a clone of the provided *http.Request.
// The clone is a shallow copy of the struct and its Header map.
func cloneRequest(r *http.Request) *http.Request {
// shallow copy of the struct
r2 := new(http.Request)
*r2 = *r
// deep copy of the Header
r2.Header = make(http.Header, len(r.Header))
for k, s := range r.Header {
r2.Header[k] = append([]string(nil), s...)
}
return r2
}

View file

@ -1,194 +0,0 @@
package jira
import (
"fmt"
"strings"
"github.com/google/go-querystring/query"
"github.com/trivago/tgo/tcontainer"
)
// CreateMetaInfo contains information about fields and their attributed to create a ticket.
type CreateMetaInfo struct {
Expand string `json:"expand,omitempty"`
Projects []*MetaProject `json:"projects,omitempty"`
}
// MetaProject is the meta information about a project returned from createmeta api
type MetaProject struct {
Expand string `json:"expand,omitempty"`
Self string `json:"self,omitempty"`
Id string `json:"id,omitempty"`
Key string `json:"key,omitempty"`
Name string `json:"name,omitempty"`
// omitted avatarUrls
IssueTypes []*MetaIssueType `json:"issuetypes,omitempty"`
}
// MetaIssueType represents the different issue types a project has.
//
// Note: Fields is interface because this is an object which can
// have arbitraty keys related to customfields. It is not possible to
// expect these for a general way. This will be returning a map.
// Further processing must be done depending on what is required.
type MetaIssueType struct {
Self string `json:"self,omitempty"`
Id string `json:"id,omitempty"`
Description string `json:"description,omitempty"`
IconUrl string `json:"iconurl,omitempty"`
Name string `json:"name,omitempty"`
Subtasks bool `json:"subtask,omitempty"`
Expand string `json:"expand,omitempty"`
Fields tcontainer.MarshalMap `json:"fields,omitempty"`
}
// GetCreateMeta makes the api call to get the meta information required to create a ticket
func (s *IssueService) GetCreateMeta(projectkeys string) (*CreateMetaInfo, *Response, error) {
return s.GetCreateMetaWithOptions(&GetQueryOptions{ProjectKeys: projectkeys, Expand: "projects.issuetypes.fields"})
}
// GetCreateMetaWithOptions makes the api call to get the meta information without requiring to have a projectKey
func (s *IssueService) GetCreateMetaWithOptions(options *GetQueryOptions) (*CreateMetaInfo, *Response, error) {
apiEndpoint := "rest/api/2/issue/createmeta"
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
if options != nil {
q, err := query.Values(options)
if err != nil {
return nil, nil, err
}
req.URL.RawQuery = q.Encode()
}
meta := new(CreateMetaInfo)
resp, err := s.client.Do(req, meta)
if err != nil {
return nil, resp, err
}
return meta, resp, nil
}
// GetProjectWithName returns a project with "name" from the meta information received. If not found, this returns nil.
// The comparison of the name is case insensitive.
func (m *CreateMetaInfo) GetProjectWithName(name string) *MetaProject {
for _, m := range m.Projects {
if strings.ToLower(m.Name) == strings.ToLower(name) {
return m
}
}
return nil
}
// GetProjectWithKey returns a project with "name" from the meta information received. If not found, this returns nil.
// The comparison of the name is case insensitive.
func (m *CreateMetaInfo) GetProjectWithKey(key string) *MetaProject {
for _, m := range m.Projects {
if strings.ToLower(m.Key) == strings.ToLower(key) {
return m
}
}
return nil
}
// GetIssueTypeWithName returns an IssueType with name from a given MetaProject. If not found, this returns nil.
// The comparison of the name is case insensitive
func (p *MetaProject) GetIssueTypeWithName(name string) *MetaIssueType {
for _, m := range p.IssueTypes {
if strings.ToLower(m.Name) == strings.ToLower(name) {
return m
}
}
return nil
}
// GetMandatoryFields returns a map of all the required fields from the MetaIssueTypes.
// if a field returned by the api was:
// "customfield_10806": {
// "required": true,
// "schema": {
// "type": "any",
// "custom": "com.pyxis.greenhopper.jira:gh-epic-link",
// "customId": 10806
// },
// "name": "Epic Link",
// "hasDefaultValue": false,
// "operations": [
// "set"
// ]
// }
// the returned map would have "Epic Link" as the key and "customfield_10806" as value.
// This choice has been made so that the it is easier to generate the create api request later.
func (t *MetaIssueType) GetMandatoryFields() (map[string]string, error) {
ret := make(map[string]string)
for key := range t.Fields {
required, err := t.Fields.Bool(key + "/required")
if err != nil {
return nil, err
}
if required {
name, err := t.Fields.String(key + "/name")
if err != nil {
return nil, err
}
ret[name] = key
}
}
return ret, nil
}
// GetAllFields returns a map of all the fields for an IssueType. This includes all required and not required.
// The key of the returned map is what you see in the form and the value is how it is representated in the jira schema.
func (t *MetaIssueType) GetAllFields() (map[string]string, error) {
ret := make(map[string]string)
for key := range t.Fields {
name, err := t.Fields.String(key + "/name")
if err != nil {
return nil, err
}
ret[name] = key
}
return ret, nil
}
// CheckCompleteAndAvailable checks if the given fields satisfies the mandatory field required to create a issue for the given type
// And also if the given fields are available.
func (t *MetaIssueType) CheckCompleteAndAvailable(config map[string]string) (bool, error) {
mandatory, err := t.GetMandatoryFields()
if err != nil {
return false, err
}
all, err := t.GetAllFields()
if err != nil {
return false, err
}
// check templateconfig against mandatory fields
for key := range mandatory {
if _, okay := config[key]; !okay {
var requiredFields []string
for name := range mandatory {
requiredFields = append(requiredFields, name)
}
return false, fmt.Errorf("Required field not found in provided jira.fields. Required are: %#v", requiredFields)
}
}
// check templateConfig against all fields to verify they are available
for key := range config {
if _, okay := all[key]; !okay {
var availableFields []string
for name := range all {
availableFields = append(availableFields, name)
}
return false, fmt.Errorf("Fields in jira.fields are not available in jira. Available are: %#v", availableFields)
}
}
return true, nil
}

View file

@ -1,37 +0,0 @@
package jira
// PriorityService handles priorities for the JIRA instance / API.
//
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Priority
type PriorityService struct {
client *Client
}
// Priority represents a priority of a JIRA issue.
// Typical types are "Normal", "Moderate", "Urgent", ...
type Priority struct {
Self string `json:"self,omitempty" structs:"self,omitempty"`
IconURL string `json:"iconUrl,omitempty" structs:"iconUrl,omitempty"`
Name string `json:"name,omitempty" structs:"name,omitempty"`
ID string `json:"id,omitempty" structs:"id,omitempty"`
StatusColor string `json:"statusColor,omitempty" structs:"statusColor,omitempty"`
Description string `json:"description,omitempty" structs:"description,omitempty"`
}
// GetList gets all priorities from JIRA
//
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-priority-get
func (s *PriorityService) GetList() ([]Priority, *Response, error) {
apiEndpoint := "rest/api/2/priority"
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
priorityList := []Priority{}
resp, err := s.client.Do(req, &priorityList)
if err != nil {
return nil, resp, NewJiraError(resp, err)
}
return priorityList, resp, nil
}

View file

@ -1,162 +0,0 @@
package jira
import (
"fmt"
"github.com/google/go-querystring/query"
)
// ProjectService handles projects for the JIRA instance / API.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project
type ProjectService struct {
client *Client
}
// ProjectList represent a list of Projects
type ProjectList []struct {
Expand string `json:"expand" structs:"expand"`
Self string `json:"self" structs:"self"`
ID string `json:"id" structs:"id"`
Key string `json:"key" structs:"key"`
Name string `json:"name" structs:"name"`
AvatarUrls AvatarUrls `json:"avatarUrls" structs:"avatarUrls"`
ProjectTypeKey string `json:"projectTypeKey" structs:"projectTypeKey"`
ProjectCategory ProjectCategory `json:"projectCategory,omitempty" structs:"projectsCategory,omitempty"`
IssueTypes []IssueType `json:"issueTypes,omitempty" structs:"issueTypes,omitempty"`
}
// ProjectCategory represents a single project category
type ProjectCategory struct {
Self string `json:"self" structs:"self,omitempty"`
ID string `json:"id" structs:"id,omitempty"`
Name string `json:"name" structs:"name,omitempty"`
Description string `json:"description" structs:"description,omitempty"`
}
// Project represents a JIRA Project.
type Project struct {
Expand string `json:"expand,omitempty" structs:"expand,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"`
ID string `json:"id,omitempty" structs:"id,omitempty"`
Key string `json:"key,omitempty" structs:"key,omitempty"`
Description string `json:"description,omitempty" structs:"description,omitempty"`
Lead User `json:"lead,omitempty" structs:"lead,omitempty"`
Components []ProjectComponent `json:"components,omitempty" structs:"components,omitempty"`
IssueTypes []IssueType `json:"issueTypes,omitempty" structs:"issueTypes,omitempty"`
URL string `json:"url,omitempty" structs:"url,omitempty"`
Email string `json:"email,omitempty" structs:"email,omitempty"`
AssigneeType string `json:"assigneeType,omitempty" structs:"assigneeType,omitempty"`
Versions []Version `json:"versions,omitempty" structs:"versions,omitempty"`
Name string `json:"name,omitempty" structs:"name,omitempty"`
Roles struct {
Developers string `json:"Developers,omitempty" structs:"Developers,omitempty"`
} `json:"roles,omitempty" structs:"roles,omitempty"`
AvatarUrls AvatarUrls `json:"avatarUrls,omitempty" structs:"avatarUrls,omitempty"`
ProjectCategory ProjectCategory `json:"projectCategory,omitempty" structs:"projectCategory,omitempty"`
}
// ProjectComponent represents a single component of a project
type ProjectComponent struct {
Self string `json:"self" structs:"self,omitempty"`
ID string `json:"id" structs:"id,omitempty"`
Name string `json:"name" structs:"name,omitempty"`
Description string `json:"description" structs:"description,omitempty"`
Lead User `json:"lead,omitempty" structs:"lead,omitempty"`
AssigneeType string `json:"assigneeType" structs:"assigneeType,omitempty"`
Assignee User `json:"assignee" structs:"assignee,omitempty"`
RealAssigneeType string `json:"realAssigneeType" structs:"realAssigneeType,omitempty"`
RealAssignee User `json:"realAssignee" structs:"realAssignee,omitempty"`
IsAssigneeTypeValid bool `json:"isAssigneeTypeValid" structs:"isAssigneeTypeValid,omitempty"`
Project string `json:"project" structs:"project,omitempty"`
ProjectID int `json:"projectId" structs:"projectId,omitempty"`
}
// PermissionScheme represents the permission scheme for the project
type PermissionScheme struct {
Expand string `json:"expand" structs:"expand,omitempty"`
Self string `json:"self" structs:"self,omitempty"`
ID int `json:"id" structs:"id,omitempty"`
Name string `json:"name" structs:"name,omitempty"`
Description string `json:"description" structs:"description,omitempty"`
}
// GetList gets all projects form JIRA
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects
func (s *ProjectService) GetList() (*ProjectList, *Response, error) {
return s.ListWithOptions(&GetQueryOptions{})
}
// ListWithOptions gets all projects form JIRA with optional query params, like &GetQueryOptions{Expand: "issueTypes"} to get
// a list of all projects and their supported issuetypes
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects
func (s *ProjectService) ListWithOptions(options *GetQueryOptions) (*ProjectList, *Response, error) {
apiEndpoint := "rest/api/2/project"
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
if options != nil {
q, err := query.Values(options)
if err != nil {
return nil, nil, err
}
req.URL.RawQuery = q.Encode()
}
projectList := new(ProjectList)
resp, err := s.client.Do(req, projectList)
if err != nil {
jerr := NewJiraError(resp, err)
return nil, resp, jerr
}
return projectList, resp, nil
}
// Get returns a full representation of the project for the given issue key.
// JIRA will attempt to identify the project by the projectIdOrKey path parameter.
// This can be an project id, or an project key.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject
func (s *ProjectService) Get(projectID string) (*Project, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/api/2/project/%s", projectID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
project := new(Project)
resp, err := s.client.Do(req, project)
if err != nil {
jerr := NewJiraError(resp, err)
return nil, resp, jerr
}
return project, resp, nil
}
// GetPermissionScheme returns a full representation of the permission scheme for the project
// JIRA will attempt to identify the project by the projectIdOrKey path parameter.
// This can be an project id, or an project key.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject
func (s *ProjectService) GetPermissionScheme(projectID string) (*PermissionScheme, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/project/%s/permissionscheme", projectID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
ps := new(PermissionScheme)
resp, err := s.client.Do(req, ps)
if err != nil {
jerr := NewJiraError(resp, err)
return nil, resp, jerr
}
return ps, resp, nil
}

View file

@ -1,35 +0,0 @@
package jira
// ResolutionService handles resolutions for the JIRA instance / API.
//
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Resolution
type ResolutionService struct {
client *Client
}
// Resolution represents a resolution of a JIRA issue.
// Typical types are "Fixed", "Suspended", "Won't Fix", ...
type Resolution struct {
Self string `json:"self" structs:"self"`
ID string `json:"id" structs:"id"`
Description string `json:"description" structs:"description"`
Name string `json:"name" structs:"name"`
}
// GetList gets all resolutions from JIRA
//
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-resolution-get
func (s *ResolutionService) GetList() ([]Resolution, *Response, error) {
apiEndpoint := "rest/api/2/resolution"
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
resolutionList := []Resolution{}
resp, err := s.client.Do(req, &resolutionList)
if err != nil {
return nil, resp, NewJiraError(resp, err)
}
return resolutionList, resp, nil
}

View file

@ -1,107 +0,0 @@
package jira
import (
"fmt"
"github.com/google/go-querystring/query"
)
// SprintService handles sprints in JIRA Agile API.
// See https://docs.atlassian.com/jira-software/REST/cloud/
type SprintService struct {
client *Client
}
// IssuesWrapper represents a wrapper struct for moving issues to sprint
type IssuesWrapper struct {
Issues []string `json:"issues"`
}
// IssuesInSprintResult represents a wrapper struct for search result
type IssuesInSprintResult struct {
Issues []Issue `json:"issues"`
}
// MoveIssuesToSprint moves issues to a sprint, for a given sprint Id.
// Issues can only be moved to open or active sprints.
// The maximum number of issues that can be moved in one operation is 50.
//
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-moveIssuesToSprint
func (s *SprintService) MoveIssuesToSprint(sprintID int, issueIDs []string) (*Response, error) {
apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID)
payload := IssuesWrapper{Issues: issueIDs}
req, err := s.client.NewRequest("POST", apiEndpoint, payload)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
if err != nil {
err = NewJiraError(resp, err)
}
return resp, err
}
// GetIssuesForSprint returns all issues in a sprint, for a given sprint Id.
// This only includes issues that the user has permission to view.
// By default, the returned issues are ordered by rank.
//
// JIRA API Docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-getIssuesForSprint
func (s *SprintService) GetIssuesForSprint(sprintID int) ([]Issue, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
result := new(IssuesInSprintResult)
resp, err := s.client.Do(req, result)
if err != nil {
err = NewJiraError(resp, err)
}
return result.Issues, resp, err
}
// GetIssue returns a full representation of the issue for the given issue key.
// JIRA will attempt to identify the issue by the issueIdOrKey path parameter.
// This can be an issue id, or an issue key.
// If the issue cannot be found via an exact match, JIRA will also look for the issue in a case-insensitive way, or by looking to see if the issue was moved.
//
// The given options will be appended to the query string
//
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/7.3.1/#agile/1.0/issue-getIssue
//
// TODO: create agile service for holding all agile apis' implementation
func (s *SprintService) GetIssue(issueID string, options *GetQueryOptions) (*Issue, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/agile/1.0/issue/%s", issueID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
if options != nil {
q, err := query.Values(options)
if err != nil {
return nil, nil, err
}
req.URL.RawQuery = q.Encode()
}
issue := new(Issue)
resp, err := s.client.Do(req, issue)
if err != nil {
jerr := NewJiraError(resp, err)
return nil, resp, jerr
}
return issue, resp, nil
}

View file

@ -1,44 +0,0 @@
package jira
// StatusCategoryService handles status categories for the JIRA instance / API.
//
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Statuscategory
type StatusCategoryService struct {
client *Client
}
// StatusCategory represents the category a status belongs to.
// Those categories can be user defined in every JIRA instance.
type StatusCategory struct {
Self string `json:"self" structs:"self"`
ID int `json:"id" structs:"id"`
Name string `json:"name" structs:"name"`
Key string `json:"key" structs:"key"`
ColorName string `json:"colorName" structs:"colorName"`
}
// These constants are the keys of the default JIRA status categories
const (
StatusCategoryComplete = "done"
StatusCategoryInProgress = "indeterminate"
StatusCategoryToDo = "new"
StatusCategoryUndefined = "undefined"
)
// GetList gets all status categories from JIRA
//
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-statuscategory-get
func (s *StatusCategoryService) GetList() ([]StatusCategory, *Response, error) {
apiEndpoint := "rest/api/2/statuscategory"
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
statusCategoryList := []StatusCategory{}
resp, err := s.client.Do(req, &statusCategoryList)
if err != nil {
return nil, resp, NewJiraError(resp, err)
}
return statusCategoryList, resp, nil
}

View file

@ -1,136 +0,0 @@
package jira
import (
"encoding/json"
"fmt"
"io/ioutil"
)
// UserService handles users for the JIRA instance / API.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user
type UserService struct {
client *Client
}
// User represents a JIRA user.
type User struct {
Self string `json:"self,omitempty" structs:"self,omitempty"`
Name string `json:"name,omitempty" structs:"name,omitempty"`
Password string `json:"-"`
Key string `json:"key,omitempty" structs:"key,omitempty"`
EmailAddress string `json:"emailAddress,omitempty" structs:"emailAddress,omitempty"`
AvatarUrls AvatarUrls `json:"avatarUrls,omitempty" structs:"avatarUrls,omitempty"`
DisplayName string `json:"displayName,omitempty" structs:"displayName,omitempty"`
Active bool `json:"active,omitempty" structs:"active,omitempty"`
TimeZone string `json:"timeZone,omitempty" structs:"timeZone,omitempty"`
ApplicationKeys []string `json:"applicationKeys,omitempty" structs:"applicationKeys,omitempty"`
}
// UserGroup represents the group list
type UserGroup struct {
Self string `json:"self,omitempty" structs:"self,omitempty"`
Name string `json:"name,omitempty" structs:"name,omitempty"`
}
// Get gets user info from JIRA
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUser
func (s *UserService) Get(username string) (*User, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/user?username=%s", username)
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
user := new(User)
resp, err := s.client.Do(req, user)
if err != nil {
return nil, resp, NewJiraError(resp, err)
}
return user, resp, nil
}
// Create creates an user in JIRA.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-createUser
func (s *UserService) Create(user *User) (*User, *Response, error) {
apiEndpoint := "/rest/api/2/user"
req, err := s.client.NewRequest("POST", apiEndpoint, user)
if err != nil {
return nil, nil, err
}
resp, err := s.client.Do(req, nil)
if err != nil {
return nil, resp, err
}
responseUser := new(User)
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
e := fmt.Errorf("Could not read the returned data")
return nil, resp, NewJiraError(resp, e)
}
err = json.Unmarshal(data, responseUser)
if err != nil {
e := fmt.Errorf("Could not unmarshall the data into struct")
return nil, resp, NewJiraError(resp, e)
}
return responseUser, resp, nil
}
// GetGroups returns the groups which the user belongs to
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUserGroups
func (s *UserService) GetGroups(username string) (*[]UserGroup, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/user/groups?username=%s", username)
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
userGroups := new([]UserGroup)
resp, err := s.client.Do(req, userGroups)
if err != nil {
return nil, resp, NewJiraError(resp, err)
}
return userGroups, resp, nil
}
// Get information about the current logged-in user
//
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-myself-get
func (s *UserService) GetSelf() (*User, *Response, error) {
const apiEndpoint = "rest/api/2/myself"
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
var user User
resp, err := s.client.Do(req, &user)
if err != nil {
return nil, resp, NewJiraError(resp, err)
}
return &user, resp, nil
}
// Find searches for user info from JIRA:
// It can find users by email, username or name
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-findUsers
func (s *UserService) Find(property string) ([]User, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/user/search?username=%s", property)
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
users := []User{}
resp, err := s.client.Do(req, &users)
if err != nil {
return nil, resp, NewJiraError(resp, err)
}
return users, resp, nil
}

View file

@ -1,96 +0,0 @@
package jira
import (
"encoding/json"
"fmt"
"io/ioutil"
)
// VersionService handles Versions for the JIRA instance / API.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/version
type VersionService struct {
client *Client
}
// Version represents a single release version of a project
type Version struct {
Self string `json:"self,omitempty" structs:"self,omitempty"`
ID string `json:"id,omitempty" structs:"id,omitempty"`
Name string `json:"name,omitempty" structs:"name,omitempty"`
Description string `json:"description,omitempty" structs:"name,omitempty"`
Archived bool `json:"archived,omitempty" structs:"archived,omitempty"`
Released bool `json:"released,omitempty" structs:"released,omitempty"`
ReleaseDate string `json:"releaseDate,omitempty" structs:"releaseDate,omitempty"`
UserReleaseDate string `json:"userReleaseDate,omitempty" structs:"userReleaseDate,omitempty"`
ProjectID int `json:"projectId,omitempty" structs:"projectId,omitempty"` // Unlike other IDs, this is returned as a number
}
// Get gets version info from JIRA
//
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-get
func (s *VersionService) Get(versionID int) (*Version, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/version/%v", versionID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
version := new(Version)
resp, err := s.client.Do(req, version)
if err != nil {
return nil, resp, NewJiraError(resp, err)
}
return version, resp, nil
}
// Create creates a version in JIRA.
//
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-post
func (s *VersionService) Create(version *Version) (*Version, *Response, error) {
apiEndpoint := "/rest/api/2/version"
req, err := s.client.NewRequest("POST", apiEndpoint, version)
if err != nil {
return nil, nil, err
}
resp, err := s.client.Do(req, nil)
if err != nil {
return nil, resp, err
}
responseVersion := new(Version)
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
e := fmt.Errorf("Could not read the returned data")
return nil, resp, NewJiraError(resp, e)
}
err = json.Unmarshal(data, responseVersion)
if err != nil {
e := fmt.Errorf("Could not unmarshall the data into struct")
return nil, resp, NewJiraError(resp, e)
}
return responseVersion, resp, nil
}
// Update updates a version from a JSON representation.
//
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-put
func (s *VersionService) Update(version *Version) (*Version, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/api/2/version/%v", version.ID)
req, err := s.client.NewRequest("PUT", apiEndpoint, version)
if err != nil {
return nil, nil, err
}
resp, err := s.client.Do(req, nil)
if err != nil {
jerr := NewJiraError(resp, err)
return nil, resp, jerr
}
// This is just to follow the rest of the API's convention of returning a version.
// Returning the same pointer here is pointless, so we return a copy instead.
ret := *version
return &ret, resp, nil
}