1
0
mirror of https://github.com/strongdm/comply synced 2024-11-25 17:14:53 +00:00

Merge pull request #41 from strongdm/feature/jira

Initial Jira implementation
This commit is contained in:
Justin McCarthy 2018-06-01 17:37:41 -07:00 committed by GitHub
commit 3c696e6d01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 6158 additions and 23 deletions

23
Gopkg.lock generated
View File

@ -7,6 +7,12 @@
revision = "7da180ee92d8bd8bb8c37fc560e673e6557c392f" revision = "7da180ee92d8bd8bb8c37fc560e673e6557c392f"
version = "v0.4.7" version = "v0.4.7"
[[projects]]
name = "github.com/andygrunwald/go-jira"
packages = ["."]
revision = "5cfdb85cc91c6299f75b6504a1d0ec174c21be39"
version = "v1.3.0"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "github.com/chzyer/readline" name = "github.com/chzyer/readline"
@ -79,6 +85,12 @@
revision = "507f6050b8568533fb3f5504de8e5205fa62a114" revision = "507f6050b8568533fb3f5504de8e5205fa62a114"
version = "v1.6.0" version = "v1.6.0"
[[projects]]
name = "github.com/fatih/structs"
packages = ["."]
revision = "a720dfa8df582c51dee1b36feabb906bde1588bd"
version = "v1.0"
[[projects]] [[projects]]
name = "github.com/fsnotify/fsnotify" name = "github.com/fsnotify/fsnotify"
packages = ["."] packages = ["."]
@ -194,6 +206,15 @@
packages = ["open"] packages = ["open"]
revision = "75fb7ed4208cf72d323d7d02fd1a5964a7a9073c" revision = "75fb7ed4208cf72d323d7d02fd1a5964a7a9073c"
[[projects]]
name = "github.com/trivago/tgo"
packages = [
"tcontainer",
"treflect"
]
revision = "e4d1ddd28c17dd89ed26327cf69fded22060671b"
version = "v1.0.1"
[[projects]] [[projects]]
name = "github.com/urfave/cli" name = "github.com/urfave/cli"
packages = ["."] packages = ["."]
@ -257,6 +278,6 @@
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "4fd2ff9f9869c3f3e30601504f4b00fce69d282ae8df42583a1c60848bfd0766" inputs-digest = "b8eb855eeef730f7fcaabe3acceb26a99b7bce186d815c3f654d7a1cbce97f2a"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View File

@ -18,6 +18,7 @@ import (
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/strongdm/comply/internal/config" "github.com/strongdm/comply/internal/config"
"github.com/strongdm/comply/internal/jira"
"github.com/strongdm/comply/internal/plugin/github" "github.com/strongdm/comply/internal/plugin/github"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -55,6 +56,7 @@ func newApp() *cli.App {
// Plugins // Plugins
github.Register() github.Register()
jira.Register()
return app return app
} }
@ -104,12 +106,18 @@ func pandocMustExist(c *cli.Context) error {
pandocExistErr := pandocBinaryMustExist(c) pandocExistErr := pandocBinaryMustExist(c)
dockerExistErr := dockerMustExist(c) dockerExistErr := dockerMustExist(c)
config.SetPandoc(pandocExistErr == nil, dockerExistErr == nil) config.SetPandoc(pandocExistErr == nil, dockerExistErr == nil)
if pandocExistErr != nil && dockerExistErr != nil { if pandocExistErr != nil && dockerExistErr != nil {
return eitherMustExistErr return eitherMustExistErr
} }
// if we don't have pandoc, but we do have docker, execute a pull
if (pandocExistErr != nil && dockerExistErr == nil) || config.WhichPandoc() == config.UseDocker {
dockerPull(c)
}
return nil return nil
} }
@ -168,6 +176,23 @@ func dockerMustExist(c *cli.Context) error {
return dockerErr return dockerErr
} }
_, err = cli.Ping(ctx)
if err != nil {
return dockerErr
}
return nil
}
func dockerPull(c *cli.Context) error {
dockerErr := fmt.Errorf("Docker must be available in order to run `%s`", c.Command.Name)
ctx := context.Background()
cli, err := client.NewEnvClient()
if err != nil {
return dockerErr
}
done := make(chan struct{}) done := make(chan struct{})
defer close(done) defer close(done)

View File

@ -116,8 +116,7 @@ func initAction(c *cli.Context) error {
case 0: case 0:
ticketing = model.GitHub ticketing = model.GitHub
case 1: case 1:
fmt.Println("\nHello Jira user! The Jira ticketing plugin is currently in development, please join us on Slack for a status update.") ticketing = model.Jira
ticketing = model.NoTickets
default: default:
ticketing = model.NoTickets ticketing = model.NoTickets
} }

View File

@ -3,6 +3,7 @@ package cli
import ( import (
"fmt" "fmt"
"github.com/strongdm/comply/internal/config"
"github.com/strongdm/comply/internal/model" "github.com/strongdm/comply/internal/model"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -28,14 +29,22 @@ func procedureAction(c *cli.Context) error {
procedureID := c.Args().First() procedureID := c.Args().First()
ts, err := config.Config().TicketSystem()
if err != nil {
return cli.NewExitError("error in ticket system configuration", 1)
}
tp := model.GetPlugin(model.TicketSystem(ts))
for _, procedure := range procedures { for _, procedure := range procedures {
if procedure.ID == procedureID { if procedure.ID == procedureID {
// TODO: don't hardcode GH err = tp.Create(&model.Ticket{
tp := model.GetPlugin(model.GitHub)
tp.Create(&model.Ticket{
Name: procedure.Name, Name: procedure.Name,
Body: fmt.Sprintf("%s\n\n\n---\nProcedure-ID: %s", procedure.Body, procedure.ID), Body: fmt.Sprintf("%s\n\n\n---\nProcedure-ID: %s", procedure.Body, procedure.ID),
}, []string{"comply", "comply-procedure"}) }, []string{"comply", "comply-procedure"})
if err != nil {
return err
}
return nil return nil
} }
} }

View File

@ -1,6 +1,7 @@
package cli package cli
import ( import (
"github.com/strongdm/comply/internal/config"
"github.com/strongdm/comply/internal/model" "github.com/strongdm/comply/internal/model"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -13,8 +14,12 @@ var syncCommand = cli.Command{
} }
func syncAction(c *cli.Context) error { func syncAction(c *cli.Context) error {
// TODO: unhardcode plugin ts, err := config.Config().TicketSystem()
tp := model.GetPlugin(model.GitHub) if err != nil {
return cli.NewExitError("error in ticket system configuration", 1)
}
tp := model.GetPlugin(model.TicketSystem(ts))
tickets, err := tp.FindByTagName("comply") tickets, err := tp.FindByTagName("comply")
if err != nil { if err != nil {
return err return err

View File

@ -1,6 +1,7 @@
package config package config
import ( import (
"errors"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -12,6 +13,12 @@ var projectRoot string
var dockerAvailable, pandocAvailable bool var dockerAvailable, pandocAvailable bool
const (
Jira = "jira"
GitHub = "github"
NoTickets = "none"
)
const ( const (
// UseDocker invokes pandoc within Docker // UseDocker invokes pandoc within Docker
UseDocker = "docker" UseDocker = "docker"
@ -73,14 +80,14 @@ func Exists() bool {
} }
// Config is the parsed contents of ProjectRoot()/config.yml. // Config is the parsed contents of ProjectRoot()/config.yml.
func Config() Project { func Config() *Project {
p := Project{} p := Project{}
cfgBytes, err := ioutil.ReadFile(filepath.Join(ProjectRoot(), "comply.yml")) cfgBytes, err := ioutil.ReadFile(filepath.Join(ProjectRoot(), "comply.yml"))
if err != nil { if err != nil {
panic("unable to load config.yml: " + err.Error()) panic("unable to load config.yml: " + err.Error())
} }
yaml.Unmarshal(cfgBytes, &p) yaml.Unmarshal(cfgBytes, &p)
return p return &p
} }
// ProjectRoot is the fully-qualified path to the root directory. // ProjectRoot is the fully-qualified path to the root directory.
@ -95,3 +102,27 @@ func ProjectRoot() string {
return projectRoot return projectRoot
} }
// TicketSystem indicates the type of the configured ticket system
func (p *Project) TicketSystem() (string, error) {
if len(p.Tickets) > 1 {
return NoTickets, errors.New("multiple ticket systems configured")
}
for k := range p.Tickets {
switch k {
case GitHub:
return GitHub, nil
case Jira:
return Jira, nil
case NoTickets:
return NoTickets, nil
default:
// explicit error for this case
return "", errors.New("unrecognized ticket system configured")
}
}
// no ticket block configured
return NoTickets, nil
}

187
internal/jira/jira.go Normal file
View File

@ -0,0 +1,187 @@
package jira
import (
"fmt"
"sync"
"time"
"github.com/pkg/errors"
"github.com/strongdm/comply/internal/model"
jira "github.com/andygrunwald/go-jira"
)
const (
cfgUsername = "username"
cfgPassword = "password"
cfgURL = "url"
cfgProject = "project"
)
var prompts = map[string]string{
cfgUsername: "Jira Username",
cfgPassword: "Jira Password",
cfgURL: "Jira URL",
cfgProject: "Jira Project Code",
}
// Prompts are human-readable configuration element names
func (j *jiraPlugin) Prompts() map[string]string {
return prompts
}
// Register causes the Github plugin to register itself
func Register() {
model.Register(model.Jira, &jiraPlugin{})
}
type jiraPlugin struct {
username string
password string
url string
project string
clientMu sync.Mutex
client *jira.Client
}
func (j *jiraPlugin) api() *jira.Client {
j.clientMu.Lock()
defer j.clientMu.Unlock()
if j.client == nil {
tp := jira.BasicAuthTransport{
Username: j.username,
Password: j.password,
}
client, _ := jira.NewClient(tp.Client(), j.url)
j.client = client
}
return j.client
}
func (j *jiraPlugin) Get(ID string) (*model.Ticket, error) {
return nil, nil
}
func (j *jiraPlugin) Configured() bool {
return j.username != "" && j.password != "" && j.url != "" && j.project != ""
}
func (j *jiraPlugin) Links() model.TicketLinks {
links := model.TicketLinks{}
links.ProcedureAll = fmt.Sprintf("%s/issues/?jql=labels+=+comply-procedure", j.url)
links.ProcedureOpen = fmt.Sprintf("%s/issues/?jql=labels+=+comply-procedure+AND+resolution+=+Unresolved", j.url)
// links.AuditAll = fmt.Sprintf("%s/issues?q=is%3Aissue+is%3Aopen+label%3Acomply+label%3Aaudit", j.url)
// links.AuditOpen = fmt.Sprintf("%s/issues?q=is%3Aissue+is%3Aopen+label%3Acomply+label%3Aaudit", j.url)
return links
}
func (j *jiraPlugin) Configure(cfg map[string]interface{}) error {
var err error
if j.username, err = getCfg(cfg, cfgUsername); err != nil {
return err
}
if j.password, err = getCfg(cfg, cfgPassword); err != nil {
return err
}
if j.url, err = getCfg(cfg, cfgURL); err != nil {
return err
}
if j.project, err = getCfg(cfg, cfgProject); err != nil {
return err
}
return nil
}
func getCfg(cfg map[string]interface{}, k string) (string, error) {
v, ok := cfg[k]
if !ok {
return "", errors.New("Missing key: " + k)
}
vS, ok := v.(string)
if !ok {
return "", errors.New("Malformatted key: " + k)
}
return vS, nil
}
func (j *jiraPlugin) FindOpen() ([]*model.Ticket, error) {
panic("not implemented")
}
func (j *jiraPlugin) FindByTag(name, value string) ([]*model.Ticket, error) {
panic("not implemented")
}
func (j *jiraPlugin) FindByTagName(name string) ([]*model.Ticket, error) {
issues, _, err := j.api().Issue.Search("labels=comply", &jira.SearchOptions{MaxResults: 1000})
if err != nil {
return nil, errors.Wrap(err, "unable to fetch Jira issues")
}
return toTickets(issues), nil
}
func (j *jiraPlugin) LinkFor(t *model.Ticket) string {
panic("not implemented")
}
func (j *jiraPlugin) Create(ticket *model.Ticket, labels []string) error {
i := jira.Issue{
Fields: &jira.IssueFields{
Type: jira.IssueType{
Name: "Task",
},
Project: jira.Project{
Key: j.project,
},
Summary: ticket.Name,
Description: ticket.Body,
Labels: labels,
},
}
_, _, err := j.api().Issue.Create(&i)
if err != nil {
return errors.Wrap(err, "unable to create ticket")
}
return nil
}
func toTickets(issues []jira.Issue) []*model.Ticket {
var tickets []*model.Ticket
for _, i := range issues {
tickets = append(tickets, toTicket(&i))
}
return tickets
}
func toTicket(i *jira.Issue) *model.Ticket {
t := &model.Ticket{Attributes: make(map[string]interface{})}
t.ID = i.ID
t.Name = i.Fields.Description
t.Body = i.Fields.Summary
createdAt := time.Time(i.Fields.Created)
t.CreatedAt = &createdAt
t.State = toState(i.Fields.Resolution)
for _, l := range i.Fields.Labels {
t.SetBool(l)
}
return t
}
func toState(status *jira.Resolution) model.TicketState {
if status == nil {
return model.Open
}
switch status.Name {
case "Done":
return model.Closed
}
return model.Open
}

View File

@ -0,0 +1,9 @@
package jira
import (
"testing"
)
func TestJira(t *testing.T) {
createOne()
}

View File

@ -17,11 +17,11 @@ type TicketSystem string
const ( const (
// Jira from Atlassian. // Jira from Atlassian.
Jira = TicketSystem("jira") Jira = TicketSystem(config.Jira)
// GitHub from GitHub. // GitHub from GitHub.
GitHub = TicketSystem("github") GitHub = TicketSystem(config.GitHub)
// NoTickets indicates no ticketing system integration. // NoTickets indicates no ticketing system integration.
NoTickets = TicketSystem("none") NoTickets = TicketSystem(config.NoTickets)
) )
type TicketLinks struct { type TicketLinks struct {

View File

@ -135,7 +135,8 @@ func (g *githubPlugin) FindByTagName(name string) ([]*model.Ticket, error) {
} }
func (g *githubPlugin) LinkFor(t *model.Ticket) string { func (g *githubPlugin) LinkFor(t *model.Ticket) string {
return fmt.Sprintf("https://github.com/strongdm/comply/issues/%s", t.ID) // return fmt.Sprintf("https://github.com/strongdm/comply/issues/%s", t.ID)
panic("not implemented")
} }
func (g *githubPlugin) Create(ticket *model.Ticket, labels []string) error { func (g *githubPlugin) Create(ticket *model.Ticket, labels []string) error {

View File

@ -5,6 +5,7 @@ import (
"sort" "sort"
"time" "time"
"github.com/pkg/errors"
"github.com/strongdm/comply/internal/config" "github.com/strongdm/comply/internal/config"
"github.com/strongdm/comply/internal/model" "github.com/strongdm/comply/internal/model"
) )
@ -93,8 +94,12 @@ func load() (*model.Data, *renderData, error) {
rd.Name = project.OrganizationName rd.Name = project.OrganizationName
rd.Controls = controls rd.Controls = controls
// TODO: unhardcode plugin ts, err := config.Config().TicketSystem()
tp := model.GetPlugin(model.GitHub) if err != nil {
return nil, nil, errors.Wrap(err, "error in ticket system configuration")
}
tp := model.GetPlugin(model.TicketSystem(ts))
if tp.Configured() { if tp.Configured() {
links := tp.Links() links := tp.Links()
rd.Links = &links rd.Links = &links
@ -133,7 +138,7 @@ func addStats(modelData *model.Data, renderData *renderData) {
} }
if t.State == model.Open { if t.State == model.Open {
if t.Bool("procedure") { if t.Bool("comply-procedure") {
stats.ProcedureOpen++ stats.ProcedureOpen++
if t.CreatedAt != nil { if t.CreatedAt != nil {
age := int(time.Since(*t.CreatedAt).Hours() / float64(24)) age := int(time.Since(*t.CreatedAt).Hours() / float64(24))

View File

@ -5,7 +5,9 @@ import (
"sort" "sort"
"time" "time"
"github.com/pkg/errors"
"github.com/robfig/cron" "github.com/robfig/cron"
"github.com/strongdm/comply/internal/config"
"github.com/strongdm/comply/internal/model" "github.com/strongdm/comply/internal/model"
) )
@ -68,7 +70,10 @@ func TriggerScheduled() error {
// in the future, nothing to do // in the future, nothing to do
continue continue
} }
trigger(procedure) err = trigger(procedure)
if err != nil {
return err
}
} else { } else {
// don't go back further than 13 months // don't go back further than 13 months
tooOld := time.Now().Add(-1 * time.Hour * 24 * (365 + 30)) tooOld := time.Now().Add(-1 * time.Hour * 24 * (365 + 30))
@ -88,7 +93,10 @@ func TriggerScheduled() error {
} }
// is in the past? then trigger. // is in the past? then trigger.
trigger(procedure) err = trigger(procedure)
if err != nil {
return err
}
break SEARCH break SEARCH
} }
} }
@ -97,13 +105,18 @@ func TriggerScheduled() error {
return nil return nil
} }
func trigger(procedure *model.Procedure) { func trigger(procedure *model.Procedure) error {
fmt.Printf("triggering procedure %s (cron expression: %s)\n", procedure.Name, procedure.Cron) fmt.Printf("triggering procedure %s (cron expression: %s)\n", procedure.Name, procedure.Cron)
// TODO: don't hardcode GH ts, err := config.Config().TicketSystem()
tp := model.GetPlugin(model.GitHub) if err != nil {
tp.Create(&model.Ticket{ return errors.Wrap(err, "error in ticket system configuration")
}
tp := model.GetPlugin(model.TicketSystem(ts))
err = tp.Create(&model.Ticket{
Name: procedure.Name, Name: procedure.Name,
Body: fmt.Sprintf("%s\n\n\n---\nProcedure-ID: %s", procedure.Body, procedure.ID), Body: fmt.Sprintf("%s\n\n\n---\nProcedure-ID: %s", procedure.Body, procedure.ID),
}, []string{"comply", "comply-procedure"}) }, []string{"comply", "comply-procedure"})
return err
} }

29
vendor/github.com/andygrunwald/go-jira/.gitignore generated vendored Normal file
View File

@ -0,0 +1,29 @@
# 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

17
vendor/github.com/andygrunwald/go-jira/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,17 @@
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 ./...

36
vendor/github.com/andygrunwald/go-jira/Gopkg.lock generated vendored Normal file
View File

@ -0,0 +1,36 @@
# 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

46
vendor/github.com/andygrunwald/go-jira/Gopkg.toml generated vendored Normal file
View File

@ -0,0 +1,46 @@
# 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

22
vendor/github.com/andygrunwald/go-jira/LICENSE generated vendored Normal file
View File

@ -0,0 +1,22 @@
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.

2
vendor/github.com/andygrunwald/go-jira/Makefile generated vendored Normal file
View File

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

271
vendor/github.com/andygrunwald/go-jira/README.md generated vendored Normal file
View File

@ -0,0 +1,271 @@
# 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

@ -0,0 +1,187 @@
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
}

166
vendor/github.com/andygrunwald/go-jira/board.go generated vendored Normal file
View File

@ -0,0 +1,166 @@
package jira
import (
"fmt"
"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
}
// Wrapper struct for search result
type sprintsResult struct {
Sprints []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)
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 returns 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) {
apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%s/sprint", boardID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
result := new(sprintsResult)
resp, err := s.client.Do(req, result)
if err != nil {
err = NewJiraError(resp, err)
}
return result.Sprints, resp, err
}

82
vendor/github.com/andygrunwald/go-jira/error.go generated vendored Normal file
View File

@ -0,0 +1,82 @@
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()
}

153
vendor/github.com/andygrunwald/go-jira/group.go generated vendored Normal file
View File

@ -0,0 +1,153 @@
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"`
}
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
}
// Get 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
}

1100
vendor/github.com/andygrunwald/go-jira/issue.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

432
vendor/github.com/andygrunwald/go-jira/jira.go generated vendored Normal file
View File

@ -0,0 +1,432 @@
package jira
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"reflect"
"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
}
// 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
}
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}
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.
// Relative URLs should always be specified without a preceding slash.
// 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
}
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.
// Relative URLs 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, urlStr string, body interface{}) (*http.Request, error) {
rel, err := url.Parse(urlStr)
if err != nil {
return nil, err
}
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.
// Relative URLs should always be specified without a preceding slash.
// 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
}
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
}

194
vendor/github.com/andygrunwald/go-jira/metaissue.go generated vendored Normal file
View File

@ -0,0 +1,194 @@
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
}

162
vendor/github.com/andygrunwald/go-jira/project.go generated vendored Normal file
View File

@ -0,0 +1,162 @@
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
}

106
vendor/github.com/andygrunwald/go-jira/sprint.go generated vendored Normal file
View File

@ -0,0 +1,106 @@
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
}
// Get 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
}

119
vendor/github.com/andygrunwald/go-jira/user.go generated vendored Normal file
View File

@ -0,0 +1,119 @@
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
}
// 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
}

96
vendor/github.com/andygrunwald/go-jira/version.go generated vendored Normal file
View File

@ -0,0 +1,96 @@
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
}

23
vendor/github.com/fatih/structs/.gitignore generated vendored Normal file
View File

@ -0,0 +1,23 @@
# 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

11
vendor/github.com/fatih/structs/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,11 @@
language: go
go:
- 1.7.x
- tip
sudo: false
before_install:
- go get github.com/axw/gocov/gocov
- go get github.com/mattn/goveralls
- if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
script:
- $HOME/gopath/bin/goveralls -service=travis-ci

21
vendor/github.com/fatih/structs/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Fatih Arslan
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.

163
vendor/github.com/fatih/structs/README.md generated vendored Normal file
View File

@ -0,0 +1,163 @@
# Structs [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/fatih/structs) [![Build Status](http://img.shields.io/travis/fatih/structs.svg?style=flat-square)](https://travis-ci.org/fatih/structs) [![Coverage Status](http://img.shields.io/coveralls/fatih/structs.svg?style=flat-square)](https://coveralls.io/r/fatih/structs)
Structs contains various utilities to work with Go (Golang) structs. It was
initially used by me to convert a struct into a `map[string]interface{}`. With
time I've added other utilities for structs. It's basically a high level
package based on primitives from the reflect package. Feel free to add new
functions or improve the existing code.
## Install
```bash
go get github.com/fatih/structs
```
## Usage and Examples
Just like the standard lib `strings`, `bytes` and co packages, `structs` has
many global functions to manipulate or organize your struct data. Lets define
and declare a struct:
```go
type Server struct {
Name string `json:"name,omitempty"`
ID int
Enabled bool
users []string // not exported
http.Server // embedded
}
server := &Server{
Name: "gopher",
ID: 123456,
Enabled: true,
}
```
```go
// Convert a struct to a map[string]interface{}
// => {"Name":"gopher", "ID":123456, "Enabled":true}
m := structs.Map(server)
// Convert the values of a struct to a []interface{}
// => ["gopher", 123456, true]
v := structs.Values(server)
// Convert the names of a struct to a []string
// (see "Names methods" for more info about fields)
n := structs.Names(server)
// Convert the values of a struct to a []*Field
// (see "Field methods" for more info about fields)
f := structs.Fields(server)
// Return the struct name => "Server"
n := structs.Name(server)
// Check if any field of a struct is initialized or not.
h := structs.HasZero(server)
// Check if all fields of a struct is initialized or not.
z := structs.IsZero(server)
// Check if server is a struct or a pointer to struct
i := structs.IsStruct(server)
```
### Struct methods
The structs functions can be also used as independent methods by creating a new
`*structs.Struct`. This is handy if you want to have more control over the
structs (such as retrieving a single Field).
```go
// Create a new struct type:
s := structs.New(server)
m := s.Map() // Get a map[string]interface{}
v := s.Values() // Get a []interface{}
f := s.Fields() // Get a []*Field
n := s.Names() // Get a []string
f := s.Field(name) // Get a *Field based on the given field name
f, ok := s.FieldOk(name) // Get a *Field based on the given field name
n := s.Name() // Get the struct name
h := s.HasZero() // Check if any field is initialized
z := s.IsZero() // Check if all fields are initialized
```
### Field methods
We can easily examine a single Field for more detail. Below you can see how we
get and interact with various field methods:
```go
s := structs.New(server)
// Get the Field struct for the "Name" field
name := s.Field("Name")
// Get the underlying value, value => "gopher"
value := name.Value().(string)
// Set the field's value
name.Set("another gopher")
// Get the field's kind, kind => "string"
name.Kind()
// Check if the field is exported or not
if name.IsExported() {
fmt.Println("Name field is exported")
}
// Check if the value is a zero value, such as "" for string, 0 for int
if !name.IsZero() {
fmt.Println("Name is initialized")
}
// Check if the field is an anonymous (embedded) field
if !name.IsEmbedded() {
fmt.Println("Name is not an embedded field")
}
// Get the Field's tag value for tag name "json", tag value => "name,omitempty"
tagValue := name.Tag("json")
```
Nested structs are supported too:
```go
addrField := s.Field("Server").Field("Addr")
// Get the value for addr
a := addrField.Value().(string)
// Or get all fields
httpServer := s.Field("Server").Fields()
```
We can also get a slice of Fields from the Struct type to iterate over all
fields. This is handy if you wish to examine all fields:
```go
s := structs.New(server)
for _, f := range s.Fields() {
fmt.Printf("field name: %+v\n", f.Name())
if f.IsExported() {
fmt.Printf("value : %+v\n", f.Value())
fmt.Printf("is zero : %+v\n", f.IsZero())
}
}
```
## Credits
* [Fatih Arslan](https://github.com/fatih)
* [Cihangir Savas](https://github.com/cihangir)
## License
The MIT License (MIT) - see LICENSE.md for more details

141
vendor/github.com/fatih/structs/field.go generated vendored Normal file
View File

@ -0,0 +1,141 @@
package structs
import (
"errors"
"fmt"
"reflect"
)
var (
errNotExported = errors.New("field is not exported")
errNotSettable = errors.New("field is not settable")
)
// Field represents a single struct field that encapsulates high level
// functions around the field.
type Field struct {
value reflect.Value
field reflect.StructField
defaultTag string
}
// Tag returns the value associated with key in the tag string. If there is no
// such key in the tag, Tag returns the empty string.
func (f *Field) Tag(key string) string {
return f.field.Tag.Get(key)
}
// Value returns the underlying value of the field. It panics if the field
// is not exported.
func (f *Field) Value() interface{} {
return f.value.Interface()
}
// IsEmbedded returns true if the given field is an anonymous field (embedded)
func (f *Field) IsEmbedded() bool {
return f.field.Anonymous
}
// IsExported returns true if the given field is exported.
func (f *Field) IsExported() bool {
return f.field.PkgPath == ""
}
// IsZero returns true if the given field is not initialized (has a zero value).
// It panics if the field is not exported.
func (f *Field) IsZero() bool {
zero := reflect.Zero(f.value.Type()).Interface()
current := f.Value()
return reflect.DeepEqual(current, zero)
}
// Name returns the name of the given field
func (f *Field) Name() string {
return f.field.Name
}
// Kind returns the fields kind, such as "string", "map", "bool", etc ..
func (f *Field) Kind() reflect.Kind {
return f.value.Kind()
}
// Set sets the field to given value v. It returns an error if the field is not
// settable (not addressable or not exported) or if the given value's type
// doesn't match the fields type.
func (f *Field) Set(val interface{}) error {
// we can't set unexported fields, so be sure this field is exported
if !f.IsExported() {
return errNotExported
}
// do we get here? not sure...
if !f.value.CanSet() {
return errNotSettable
}
given := reflect.ValueOf(val)
if f.value.Kind() != given.Kind() {
return fmt.Errorf("wrong kind. got: %s want: %s", given.Kind(), f.value.Kind())
}
f.value.Set(given)
return nil
}
// Zero sets the field to its zero value. It returns an error if the field is not
// settable (not addressable or not exported).
func (f *Field) Zero() error {
zero := reflect.Zero(f.value.Type()).Interface()
return f.Set(zero)
}
// Fields returns a slice of Fields. This is particular handy to get the fields
// of a nested struct . A struct tag with the content of "-" ignores the
// checking of that particular field. Example:
//
// // Field is ignored by this package.
// Field *http.Request `structs:"-"`
//
// It panics if field is not exported or if field's kind is not struct
func (f *Field) Fields() []*Field {
return getFields(f.value, f.defaultTag)
}
// Field returns the field from a nested struct. It panics if the nested struct
// is not exported or if the field was not found.
func (f *Field) Field(name string) *Field {
field, ok := f.FieldOk(name)
if !ok {
panic("field not found")
}
return field
}
// FieldOk returns the field from a nested struct. The boolean returns whether
// the field was found (true) or not (false).
func (f *Field) FieldOk(name string) (*Field, bool) {
value := &f.value
// value must be settable so we need to make sure it holds the address of the
// variable and not a copy, so we can pass the pointer to strctVal instead of a
// copy (which is not assigned to any variable, hence not settable).
// see "https://blog.golang.org/laws-of-reflection#TOC_8."
if f.value.Kind() != reflect.Ptr {
a := f.value.Addr()
value = &a
}
v := strctVal(value.Interface())
t := v.Type()
field, ok := t.FieldByName(name)
if !ok {
return nil, false
}
return &Field{
field: field,
value: v.FieldByName(name),
}, true
}

586
vendor/github.com/fatih/structs/structs.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

32
vendor/github.com/fatih/structs/tags.go generated vendored Normal file
View File

@ -0,0 +1,32 @@
package structs
import "strings"
// tagOptions contains a slice of tag options
type tagOptions []string
// Has returns true if the given optiton is available in tagOptions
func (t tagOptions) Has(opt string) bool {
for _, tagOpt := range t {
if tagOpt == opt {
return true
}
}
return false
}
// parseTag splits a struct field's tag into its name and a list of options
// which comes after a name. A tag is in the form of: "name,option1,option2".
// The name can be neglectected.
func parseTag(tag string) (string, tagOptions) {
// tag is one of followings:
// ""
// "name"
// "name,opt"
// "name,opt,opt2"
// ",opt"
res := strings.Split(tag, ",")
return res[0], res[1:]
}

202
vendor/github.com/trivago/tgo/LICENSE generated vendored Normal file
View File

@ -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.

113
vendor/github.com/trivago/tgo/tcontainer/arrays.go generated vendored Normal file
View File

@ -0,0 +1,113 @@
// Copyright 2015-2016 trivago GmbH
//
// 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 tcontainer
import "sort"
// Int64Slice is a typedef to allow sortable int64 slices
type Int64Slice []int64
func (s Int64Slice) Len() int {
return len(s)
}
func (s Int64Slice) Less(i, j int) bool {
return s[i] < s[j]
}
func (s Int64Slice) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Sort is a shortcut for sort.Sort(s)
func (s Int64Slice) Sort() {
sort.Sort(s)
}
// IsSorted is a shortcut for sort.IsSorted(s)
func (s Int64Slice) IsSorted() bool {
return sort.IsSorted(s)
}
// Set sets all values in this slice to the given value
func (s Int64Slice) Set(v int64) {
for i := range s {
s[i] = v
}
}
// Uint64Slice is a typedef to allow sortable uint64 slices
type Uint64Slice []uint64
func (s Uint64Slice) Len() int {
return len(s)
}
func (s Uint64Slice) Less(i, j int) bool {
return s[i] < s[j]
}
func (s Uint64Slice) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Sort is a shortcut for sort.Sort(s)
func (s Uint64Slice) Sort() {
sort.Sort(s)
}
// IsSorted is a shortcut for sort.IsSorted(s)
func (s Uint64Slice) IsSorted() bool {
return sort.IsSorted(s)
}
// Set sets all values in this slice to the given value
func (s Uint64Slice) Set(v uint64) {
for i := range s {
s[i] = v
}
}
// Float32Slice is a typedef to allow sortable float32 slices
type Float32Slice []float32
func (s Float32Slice) Len() int {
return len(s)
}
func (s Float32Slice) Less(i, j int) bool {
return s[i] < s[j]
}
func (s Float32Slice) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Sort is a shortcut for sort.Sort(s)
func (s Float32Slice) Sort() {
sort.Sort(s)
}
// IsSorted is a shortcut for sort.IsSorted(s)
func (s Float32Slice) IsSorted() bool {
return sort.IsSorted(s)
}
// Set sets all values in this slice to the given value
func (s Float32Slice) Set(v float32) {
for i := range s {
s[i] = v
}
}

157
vendor/github.com/trivago/tgo/tcontainer/bytepool.go generated vendored Normal file
View File

@ -0,0 +1,157 @@
// Copyright 2015-2016 trivago GmbH
//
// 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 tcontainer
import (
"reflect"
"runtime"
"sync/atomic"
"unsafe"
)
const (
tiny = 64
small = 512
medium = 1024
large = 1024 * 10
huge = 1024 * 100
tinyCount = 16384 // 1 MB
smallCount = 2048 // 1 MB
mediumCount = 1024 // 1 MB
largeCount = 102 // ~1 MB
hugeCount = 10 // ~1 MB
)
type byteSlab struct {
buffer []byte
bufferSize uintptr
stride uintptr
basePtr *uintptr
nextPtr *uintptr
}
// BytePool is a fragmentation friendly way to allocated byte slices.
type BytePool struct {
tinySlab byteSlab
smallSlab byteSlab
mediumSlab byteSlab
largeSlab byteSlab
hugeSlab byteSlab
}
func newByteSlab(size, count int) byteSlab {
bufferSize := count * size
buffer := make([]byte, bufferSize)
basePtr := (*reflect.SliceHeader)(unsafe.Pointer(&buffer)).Data
nextPtr := basePtr + uintptr(bufferSize)
return byteSlab{
buffer: buffer,
bufferSize: uintptr(bufferSize),
stride: uintptr(size),
basePtr: &basePtr,
nextPtr: &nextPtr,
}
}
func (slab *byteSlab) getSlice(size int) (chunk []byte) {
chunkHeader := (*reflect.SliceHeader)(unsafe.Pointer(&chunk))
chunkHeader.Len = size
chunkHeader.Cap = int(slab.stride)
for {
// WARNING: The following two lines are order sensitive
basePtr := atomic.LoadUintptr(slab.basePtr)
nextPtr := atomic.AddUintptr(slab.nextPtr, -slab.stride)
lastPtr := basePtr + slab.bufferSize
switch {
case nextPtr < basePtr || nextPtr >= lastPtr:
// out of range either means alloc while realloc or race between
// base and next during realloc. In the latter case we lose a chunk.
runtime.Gosched()
case nextPtr == basePtr:
// Last item: realloc
slab.buffer = make([]byte, slab.bufferSize)
dataPtr := (*reflect.SliceHeader)(unsafe.Pointer(&slab.buffer)).Data
// WARNING: The following two lines are order sensitive
atomic.StoreUintptr(slab.nextPtr, dataPtr+slab.bufferSize)
atomic.StoreUintptr(slab.basePtr, dataPtr)
fallthrough
default:
chunkHeader.Data = nextPtr
return
}
}
}
// NewBytePool creates a new BytePool with each slab using 1 MB of storage.
// The pool contains 5 slabs of different sizes: 64B, 512B, 1KB, 10KB and 100KB.
// Allocations above 100KB will be allocated directly.
func NewBytePool() BytePool {
return BytePool{
tinySlab: newByteSlab(tiny, tinyCount),
smallSlab: newByteSlab(small, smallCount),
mediumSlab: newByteSlab(medium, mediumCount),
largeSlab: newByteSlab(large, largeCount),
hugeSlab: newByteSlab(huge, hugeCount),
}
}
// NewBytePoolWithSize creates a new BytePool with each slab size using n MB of
// storage. See NewBytePool() for slab size details.
func NewBytePoolWithSize(n int) BytePool {
if n <= 0 {
n = 1
}
return BytePool{
tinySlab: newByteSlab(tiny, tinyCount*n),
smallSlab: newByteSlab(small, smallCount*n),
mediumSlab: newByteSlab(medium, mediumCount*n),
largeSlab: newByteSlab(large, largeCount*n),
hugeSlab: newByteSlab(huge, hugeCount*n),
}
}
// Get returns a slice allocated to a normalized size.
// Sizes are organized in evenly sized buckets so that fragmentation is kept low.
func (b *BytePool) Get(size int) []byte {
switch {
case size == 0:
return []byte{}
case size <= tiny:
return b.tinySlab.getSlice(size)
case size <= small:
return b.smallSlab.getSlice(size)
case size <= medium:
return b.mediumSlab.getSlice(size)
case size <= large:
return b.largeSlab.getSlice(size)
case size <= huge:
return b.hugeSlab.getSlice(size)
default:
return make([]byte, size)
}
}

464
vendor/github.com/trivago/tgo/tcontainer/marshalmap.go generated vendored Normal file
View File

@ -0,0 +1,464 @@
// Copyright 2015-2016 trivago GmbH
//
// 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 tcontainer
import (
"fmt"
"github.com/trivago/tgo/treflect"
"reflect"
"strconv"
"strings"
"time"
)
// MarshalMap is a wrapper type to attach converter methods to maps normally
// returned by marshalling methods, i.e. key/value parsers.
// All methods that do a conversion will return an error if the value stored
// behind key is not of the expected type or if the key is not existing in the
// map.
type MarshalMap map[string]interface{}
const (
// MarshalMapSeparator defines the rune used for path separation
MarshalMapSeparator = '/'
// MarshalMapArrayBegin defines the rune starting array index notation
MarshalMapArrayBegin = '['
// MarshalMapArrayEnd defines the rune ending array index notation
MarshalMapArrayEnd = ']'
)
// NewMarshalMap creates a new marshal map (string -> interface{})
func NewMarshalMap() MarshalMap {
return make(map[string]interface{})
}
// TryConvertToMarshalMap converts collections to MarshalMap if possible.
// This is a deep conversion, i.e. each element in the collection will be
// traversed. You can pass a formatKey function that will be applied to all
// string keys that are detected.
func TryConvertToMarshalMap(value interface{}, formatKey func(string) string) interface{} {
valueMeta := reflect.ValueOf(value)
switch valueMeta.Kind() {
default:
return value
case reflect.Array, reflect.Slice:
arrayLen := valueMeta.Len()
converted := make([]interface{}, arrayLen)
for i := 0; i < arrayLen; i++ {
converted[i] = TryConvertToMarshalMap(valueMeta.Index(i).Interface(), formatKey)
}
return converted
case reflect.Map:
converted := NewMarshalMap()
keys := valueMeta.MapKeys()
for _, keyMeta := range keys {
strKey, isString := keyMeta.Interface().(string)
if !isString {
continue
}
if formatKey != nil {
strKey = formatKey(strKey)
}
val := valueMeta.MapIndex(keyMeta).Interface()
converted[strKey] = TryConvertToMarshalMap(val, formatKey)
}
return converted // ### return, converted MarshalMap ###
}
}
// ConvertToMarshalMap tries to convert a compatible map type to a marshal map.
// Compatible types are map[interface{}]interface{}, map[string]interface{} and of
// course MarshalMap. The same rules as for ConvertValueToMarshalMap apply.
func ConvertToMarshalMap(value interface{}, formatKey func(string) string) (MarshalMap, error) {
converted := TryConvertToMarshalMap(value, formatKey)
if result, isMap := converted.(MarshalMap); isMap {
return result, nil
}
return nil, fmt.Errorf("Root value cannot be converted to MarshalMap")
}
// Bool returns a value at key that is expected to be a boolean
func (mmap MarshalMap) Bool(key string) (bool, error) {
val, exists := mmap.Value(key)
if !exists {
return false, fmt.Errorf(`"%s" is not set`, key)
}
boolValue, isBool := val.(bool)
if !isBool {
return false, fmt.Errorf(`"%s" is expected to be a boolean`, key)
}
return boolValue, nil
}
// Uint returns a value at key that is expected to be an uint64 or compatible
// integer value.
func (mmap MarshalMap) Uint(key string) (uint64, error) {
val, exists := mmap.Value(key)
if !exists {
return 0, fmt.Errorf(`"%s" is not set`, key)
}
if intVal, isNumber := treflect.Uint64(val); isNumber {
return intVal, nil
}
return 0, fmt.Errorf(`"%s" is expected to be an unsigned number type`, key)
}
// Int returns a value at key that is expected to be an int64 or compatible
// integer value.
func (mmap MarshalMap) Int(key string) (int64, error) {
val, exists := mmap.Value(key)
if !exists {
return 0, fmt.Errorf(`"%s" is not set`, key)
}
if intVal, isNumber := treflect.Int64(val); isNumber {
return intVal, nil
}
return 0, fmt.Errorf(`"%s" is expected to be a signed number type`, key)
}
// Float returns a value at key that is expected to be a float64 or compatible
// float value.
func (mmap MarshalMap) Float(key string) (float64, error) {
val, exists := mmap.Value(key)
if !exists {
return 0, fmt.Errorf(`"%s" is not set`, key)
}
if floatVal, isNumber := treflect.Float64(val); isNumber {
return floatVal, nil
}
return 0, fmt.Errorf(`"%s" is expected to be a signed number type`, key)
}
// Duration returns a value at key that is expected to be a string
func (mmap MarshalMap) Duration(key string) (time.Duration, error) {
val, exists := mmap.Value(key)
if !exists {
return time.Duration(0), fmt.Errorf(`"%s" is not set`, key)
}
switch val.(type) {
case time.Duration:
return val.(time.Duration), nil
case string:
return time.ParseDuration(val.(string))
}
return time.Duration(0), fmt.Errorf(`"%s" is expected to be a duration or string`, key)
}
// String returns a value at key that is expected to be a string
func (mmap MarshalMap) String(key string) (string, error) {
val, exists := mmap.Value(key)
if !exists {
return "", fmt.Errorf(`"%s" is not set`, key)
}
strValue, isString := val.(string)
if !isString {
return "", fmt.Errorf(`"%s" is expected to be a string`, key)
}
return strValue, nil
}
// Array returns a value at key that is expected to be a []interface{}
func (mmap MarshalMap) Array(key string) ([]interface{}, error) {
val, exists := mmap.Value(key)
if !exists {
return nil, fmt.Errorf(`"%s" is not set`, key)
}
arrayValue, isArray := val.([]interface{})
if !isArray {
return nil, fmt.Errorf(`"%s" is expected to be an array`, key)
}
return arrayValue, nil
}
// Map returns a value at key that is expected to be a
// map[interface{}]interface{}.
func (mmap MarshalMap) Map(key string) (map[interface{}]interface{}, error) {
val, exists := mmap.Value(key)
if !exists {
return nil, fmt.Errorf(`"%s" is not set`, key)
}
mapValue, isMap := val.(map[interface{}]interface{})
if !isMap {
return nil, fmt.Errorf(`"%s" is expected to be a map`, key)
}
return mapValue, nil
}
func castToStringArray(key string, value interface{}) ([]string, error) {
switch value.(type) {
case string:
return []string{value.(string)}, nil
case []interface{}:
arrayVal := value.([]interface{})
stringArray := make([]string, 0, len(arrayVal))
for _, val := range arrayVal {
strValue, isString := val.(string)
if !isString {
return nil, fmt.Errorf(`"%s" does not contain string keys`, key)
}
stringArray = append(stringArray, strValue)
}
return stringArray, nil
case []string:
return value.([]string), nil
default:
return nil, fmt.Errorf(`"%s" is not a valid string array type`, key)
}
}
// StringArray returns a value at key that is expected to be a []string
// This function supports conversion (by copy) from
// * []interface{}
func (mmap MarshalMap) StringArray(key string) ([]string, error) {
val, exists := mmap.Value(key)
if !exists {
return nil, fmt.Errorf(`"%s" is not set`, key)
}
return castToStringArray(key, val)
}
func castToInt64Array(key string, value interface{}) ([]int64, error) {
switch value.(type) {
case int:
return []int64{value.(int64)}, nil
case []interface{}:
arrayVal := value.([]interface{})
intArray := make([]int64, 0, len(arrayVal))
for _, val := range arrayVal {
intValue, isInt := val.(int64)
if !isInt {
return nil, fmt.Errorf(`"%s" does not contain int keys`, key)
}
intArray = append(intArray, intValue)
}
return intArray, nil
case []int64:
return value.([]int64), nil
default:
return nil, fmt.Errorf(`"%s" is not a valid string array type`, key)
}
}
// IntArray returns a value at key that is expected to be a []int64
// This function supports conversion (by copy) from
// * []interface{}
func (mmap MarshalMap) Int64Array(key string) ([]int64, error) {
val, exists := mmap.Value(key)
if !exists {
return nil, fmt.Errorf(`"%s" is not set`, key)
}
return castToInt64Array(key, val)
}
// StringMap returns a value at key that is expected to be a map[string]string.
// This function supports conversion (by copy) from
// * map[interface{}]interface{}
// * map[string]interface{}
func (mmap MarshalMap) StringMap(key string) (map[string]string, error) {
val, exists := mmap.Value(key)
if !exists {
return nil, fmt.Errorf(`"%s" is not set`, key)
}
switch val.(type) {
case map[string]string:
return val.(map[string]string), nil
default:
valueMeta := reflect.ValueOf(val)
if valueMeta.Kind() != reflect.Map {
return nil, fmt.Errorf(`"%s" is expected to be a map[string]string but is %T`, key, val)
}
result := make(map[string]string)
for _, keyMeta := range valueMeta.MapKeys() {
strKey, isString := keyMeta.Interface().(string)
if !isString {
return nil, fmt.Errorf(`"%s" is expected to be a map[string]string. Key is not a string`, key)
}
value := valueMeta.MapIndex(keyMeta)
strValue, isString := value.Interface().(string)
if !isString {
return nil, fmt.Errorf(`"%s" is expected to be a map[string]string. Value is not a string`, key)
}
result[strKey] = strValue
}
return result, nil
}
}
// StringArrayMap returns a value at key that is expected to be a
// map[string][]string. This function supports conversion (by copy) from
// * map[interface{}][]interface{}
// * map[interface{}]interface{}
// * map[string]interface{}
func (mmap MarshalMap) StringArrayMap(key string) (map[string][]string, error) {
val, exists := mmap.Value(key)
if !exists {
return nil, fmt.Errorf(`"%s" is not set`, key)
}
switch val.(type) {
case map[string][]string:
return val.(map[string][]string), nil
default:
valueMeta := reflect.ValueOf(val)
if valueMeta.Kind() != reflect.Map {
return nil, fmt.Errorf(`"%s" is expected to be a map[string][]string but is %T`, key, val)
}
result := make(map[string][]string)
for _, keyMeta := range valueMeta.MapKeys() {
strKey, isString := keyMeta.Interface().(string)
if !isString {
return nil, fmt.Errorf(`"%s" is expected to be a map[string][]string. Key is not a string`, key)
}
value := valueMeta.MapIndex(keyMeta)
arrayValue, err := castToStringArray(strKey, value.Interface())
if err != nil {
return nil, fmt.Errorf(`"%s" is expected to be a map[string][]string. Value is not a []string`, key)
}
result[strKey] = arrayValue
}
return result, nil
}
}
// MarshalMap returns a value at key that is expected to be another MarshalMap
// This function supports conversion (by copy) from
// * map[interface{}]interface{}
func (mmap MarshalMap) MarshalMap(key string) (MarshalMap, error) {
val, exists := mmap.Value(key)
if !exists {
return nil, fmt.Errorf(`"%s" is not set`, key)
}
return ConvertToMarshalMap(val, nil)
}
// Value returns a value from a given value path.
// Fields can be accessed by their name. Nested fields can be accessed by using
// "/" as a separator. Arrays can be addressed using the standard array
// notation "[<index>]".
// Examples:
// "key" -> mmap["key"] single value
// "key1/key2" -> mmap["key1"]["key2"] nested map
// "key1[0]" -> mmap["key1"][0] nested array
// "key1[0]key2" -> mmap["key1"][0]["key2"] nested array, nested map
func (mmap MarshalMap) Value(key string) (interface{}, bool) {
return mmap.resolvePath(key, mmap)
}
func (mmap MarshalMap) resolvePathKey(key string) (int, int) {
keyEnd := len(key)
nextKeyStart := keyEnd
pathIdx := strings.IndexRune(key, MarshalMapSeparator)
arrayIdx := strings.IndexRune(key, MarshalMapArrayBegin)
if pathIdx > -1 && pathIdx < keyEnd {
keyEnd = pathIdx
nextKeyStart = pathIdx + 1 // don't include slash
}
if arrayIdx > -1 && arrayIdx < keyEnd {
keyEnd = arrayIdx
nextKeyStart = arrayIdx // include bracket because of multidimensional arrays
}
// a -> key: "a", remain: "" -- value
// a/b/c -> key: "a", remain: "b/c" -- nested map
// a[1]b/c -> key: "a", remain: "[1]b/c" -- nested array
return keyEnd, nextKeyStart
}
func (mmap MarshalMap) resolvePath(key string, value interface{}) (interface{}, bool) {
if len(key) == 0 {
return value, true // ### return, found requested value ###
}
valueMeta := reflect.ValueOf(value)
switch valueMeta.Kind() {
case reflect.Array, reflect.Slice:
startIdx := strings.IndexRune(key, MarshalMapArrayBegin) // Must be first char, otherwise malformed
endIdx := strings.IndexRune(key, MarshalMapArrayEnd) // Must be > startIdx, otherwise malformed
if startIdx == -1 || endIdx == -1 {
return nil, false
}
if startIdx == 0 && endIdx > startIdx {
index, err := strconv.Atoi(key[startIdx+1 : endIdx])
// [1] -> index: "1", remain: "" -- value
// [1]a/b -> index: "1", remain: "a/b" -- nested map
// [1][2] -> index: "1", remain: "[2]" -- nested array
if err == nil && index < valueMeta.Len() {
item := valueMeta.Index(index).Interface()
key := key[endIdx+1:]
return mmap.resolvePath(key, item) // ### return, nested array ###
}
}
case reflect.Map:
keyMeta := reflect.ValueOf(key)
if storedValue := valueMeta.MapIndex(keyMeta); storedValue.IsValid() {
return storedValue.Interface(), true
}
keyEnd, nextKeyStart := mmap.resolvePathKey(key)
pathKey := key[:keyEnd]
keyMeta = reflect.ValueOf(pathKey)
if storedValue := valueMeta.MapIndex(keyMeta); storedValue.IsValid() {
remain := key[nextKeyStart:]
return mmap.resolvePath(remain, storedValue.Interface()) // ### return, nested map ###
}
}
return nil, false
}

227
vendor/github.com/trivago/tgo/tcontainer/trie.go generated vendored Normal file
View File

@ -0,0 +1,227 @@
// Copyright 2015-2016 trivago GmbH
//
// 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 tcontainer
// TrieNode represents a single node inside a trie.
// Each node can contain a payload which can be retrieved after a successfull
// match. In addition to that PathLen will contain the length of the match.
type TrieNode struct {
suffix []byte
children []*TrieNode
longestPath int
PathLen int
Payload interface{}
}
// NewTrie creates a new root TrieNode
func NewTrie(data []byte, payload interface{}) *TrieNode {
return &TrieNode{
suffix: data,
children: []*TrieNode{},
longestPath: len(data),
PathLen: len(data),
Payload: payload,
}
}
func (node *TrieNode) addNewChild(data []byte, payload interface{}, pathLen int) {
if node.longestPath < pathLen {
node.longestPath = pathLen
}
idx := len(node.children)
node.children = append(node.children, nil)
for idx > 0 {
nextIdx := idx - 1
if node.children[nextIdx].longestPath > pathLen {
break
}
node.children[idx] = node.children[nextIdx]
idx = nextIdx
}
node.children[idx] = &TrieNode{
suffix: data,
children: []*TrieNode{},
longestPath: pathLen,
PathLen: pathLen,
Payload: payload,
}
}
func (node *TrieNode) replace(oldChild *TrieNode, newChild *TrieNode) {
for i, child := range node.children {
if child == oldChild {
node.children[i] = newChild
return // ### return, replaced ###
}
}
}
// ForEach applies a function to each node in the tree including and below the
// passed node.
func (node *TrieNode) ForEach(callback func(*TrieNode)) {
callback(node)
for _, child := range node.children {
child.ForEach(callback)
}
}
// Add adds a new data path to the trie.
// The TrieNode returned is the (new) root node so you should always reassign
// the root with the return value of Add.
func (node *TrieNode) Add(data []byte, payload interface{}) *TrieNode {
return node.addPath(data, payload, len(data), nil)
}
func (node *TrieNode) addPath(data []byte, payload interface{}, pathLen int, parent *TrieNode) *TrieNode {
dataLen := len(data)
suffixLen := len(node.suffix)
testLen := suffixLen
if dataLen < suffixLen {
testLen = dataLen
}
var splitIdx int
for splitIdx = 0; splitIdx < testLen; splitIdx++ {
if data[splitIdx] != node.suffix[splitIdx] {
break // ### break, split found ###
}
}
if splitIdx == suffixLen {
// Continue down or stop here (full suffix match)
if splitIdx == dataLen {
node.Payload = payload // may overwrite
return node // ### return, path already stored ###
}
data = data[splitIdx:]
if suffixLen > 0 {
for _, child := range node.children {
if child.suffix[0] == data[0] {
child.addPath(data, payload, pathLen, node)
return node // ### return, continue on path ###
}
}
}
node.addNewChild(data, payload, pathLen)
return node // ### return, new leaf ###
}
if splitIdx == dataLen {
// Make current node a subpath of new data node (full data match)
// This case implies that dataLen < suffixLen as splitIdx == suffixLen
// did not match.
node.suffix = node.suffix[splitIdx:]
newParent := NewTrie(data, payload)
newParent.PathLen = pathLen
newParent.longestPath = node.longestPath
newParent.children = []*TrieNode{node}
if parent != nil {
parent.replace(node, newParent)
}
return newParent // ### return, rotation ###
}
// New parent required with both nodes as children (partial match)
node.suffix = node.suffix[splitIdx:]
newParent := NewTrie(data[:splitIdx], nil)
newParent.PathLen = 0
newParent.longestPath = node.longestPath
newParent.children = []*TrieNode{node}
newParent.addNewChild(data[splitIdx:], payload, pathLen)
if parent != nil {
parent.replace(node, newParent)
}
return newParent // ### return, new parent ###
}
// Match compares the trie to the given data stream.
// Match returns true if data can be completely matched to the trie.
func (node *TrieNode) Match(data []byte) *TrieNode {
dataLen := len(data)
suffixLen := len(node.suffix)
if dataLen < suffixLen {
return nil // ### return, cannot be fully matched ###
}
for i := 0; i < suffixLen; i++ {
if data[i] != node.suffix[i] {
return nil // ### return, no match ###
}
}
if dataLen == suffixLen {
if node.PathLen > 0 {
return node // ### return, full match ###
}
return nil // ### return, invalid match ###
}
data = data[suffixLen:]
numChildren := len(node.children)
for i := 0; i < numChildren; i++ {
matchedNode := node.children[i].Match(data)
if matchedNode != nil {
return matchedNode // ### return, match found ###
}
}
return nil // ### return, no valid path ###
}
// MatchStart compares the trie to the beginning of the given data stream.
// MatchStart returns true if the beginning of data can be matched to the trie.
func (node *TrieNode) MatchStart(data []byte) *TrieNode {
dataLen := len(data)
suffixLen := len(node.suffix)
if dataLen < suffixLen {
return nil // ### return, cannot be fully matched ###
}
for i := 0; i < suffixLen; i++ {
if data[i] != node.suffix[i] {
return nil // ### return, no match ###
}
}
// Match longest path first
data = data[suffixLen:]
numChildren := len(node.children)
for i := 0; i < numChildren; i++ {
matchedNode := node.children[i].MatchStart(data)
if matchedNode != nil {
return matchedNode // ### return, match found ###
}
}
// May be only a part of data but we have a valid match
if node.PathLen > 0 {
return node // ### return, full match ###
}
return nil // ### return, no valid path ###
}

373
vendor/github.com/trivago/tgo/treflect/reflection.go generated vendored Normal file
View File

@ -0,0 +1,373 @@
// Copyright 2015-2016 trivago GmbH
//
// 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 treflect
import (
"fmt"
"reflect"
"unsafe"
)
// GetMissingMethods checks if a given object implements all methods of a
// given interface. It returns the interface coverage [0..1] as well as an array
// of error messages. If the interface is correctly implemented the coverage is
// 1 and the error message array is empty.
func GetMissingMethods(objType reflect.Type, ifaceType reflect.Type) (float32, []string) {
missing := []string{}
if objType.Implements(ifaceType) {
return 1.0, missing
}
methodCount := ifaceType.NumMethod()
for mIdx := 0; mIdx < methodCount; mIdx++ {
ifaceMethod := ifaceType.Method(mIdx)
objMethod, exists := objType.MethodByName(ifaceMethod.Name)
signatureMismatch := false
switch {
case !exists:
missing = append(missing, fmt.Sprintf("Missing: \"%s\" %v", ifaceMethod.Name, ifaceMethod.Type))
continue // ### continue, error found ###
case ifaceMethod.Type.NumOut() != objMethod.Type.NumOut():
signatureMismatch = true
case ifaceMethod.Type.NumIn()+1 != objMethod.Type.NumIn():
signatureMismatch = true
default:
for oIdx := 0; !signatureMismatch && oIdx < ifaceMethod.Type.NumOut(); oIdx++ {
signatureMismatch = ifaceMethod.Type.Out(oIdx) != objMethod.Type.Out(oIdx)
}
for iIdx := 0; !signatureMismatch && iIdx < ifaceMethod.Type.NumIn(); iIdx++ {
signatureMismatch = ifaceMethod.Type.In(iIdx) != objMethod.Type.In(iIdx+1)
}
}
if signatureMismatch {
missing = append(missing, fmt.Sprintf("Invalid: \"%s\" %v is not %v", ifaceMethod.Name, objMethod.Type, ifaceMethod.Type))
}
}
return float32(methodCount-len(missing)) / float32(methodCount), missing
}
// Int64 converts any signed number type to an int64.
// The second parameter is returned as false if a non-number type was given.
func Int64(v interface{}) (int64, bool) {
switch reflect.TypeOf(v).Kind() {
case reflect.Int:
return int64(v.(int)), true
case reflect.Int8:
return int64(v.(int8)), true
case reflect.Int16:
return int64(v.(int16)), true
case reflect.Int32:
return int64(v.(int32)), true
case reflect.Int64:
return v.(int64), true
case reflect.Float32:
return int64(v.(float32)), true
case reflect.Float64:
return int64(v.(float64)), true
}
fmt.Printf("%t\n%#v\n%#v\n", v, v, reflect.TypeOf(v).Kind())
return 0, false
}
// Uint64 converts any unsigned number type to an uint64.
// The second parameter is returned as false if a non-number type was given.
func Uint64(v interface{}) (uint64, bool) {
switch reflect.TypeOf(v).Kind() {
case reflect.Uint:
return uint64(v.(uint)), true
case reflect.Uint8:
return uint64(v.(uint8)), true
case reflect.Uint16:
return uint64(v.(uint16)), true
case reflect.Uint32:
return uint64(v.(uint32)), true
case reflect.Uint64:
return v.(uint64), true
}
return 0, false
}
// Float32 converts any number type to an float32.
// The second parameter is returned as false if a non-number type was given.
func Float32(v interface{}) (float32, bool) {
switch reflect.TypeOf(v).Kind() {
case reflect.Int:
return float32(v.(int)), true
case reflect.Uint:
return float32(v.(uint)), true
case reflect.Int8:
return float32(v.(int8)), true
case reflect.Uint8:
return float32(v.(uint8)), true
case reflect.Int16:
return float32(v.(int16)), true
case reflect.Uint16:
return float32(v.(uint16)), true
case reflect.Int32:
return float32(v.(int32)), true
case reflect.Uint32:
return float32(v.(uint32)), true
case reflect.Int64:
return float32(v.(int64)), true
case reflect.Uint64:
return float32(v.(uint64)), true
case reflect.Float32:
return v.(float32), true
case reflect.Float64:
return float32(v.(float64)), true
}
return 0, false
}
// Float64 converts any number type to an float64.
// The second parameter is returned as false if a non-number type was given.
func Float64(v interface{}) (float64, bool) {
switch reflect.TypeOf(v).Kind() {
case reflect.Int:
return float64(v.(int)), true
case reflect.Uint:
return float64(v.(uint)), true
case reflect.Int8:
return float64(v.(int8)), true
case reflect.Uint8:
return float64(v.(uint8)), true
case reflect.Int16:
return float64(v.(int16)), true
case reflect.Uint16:
return float64(v.(uint16)), true
case reflect.Int32:
return float64(v.(int32)), true
case reflect.Uint32:
return float64(v.(uint32)), true
case reflect.Int64:
return float64(v.(int64)), true
case reflect.Uint64:
return float64(v.(uint64)), true
case reflect.Float32:
return float64(v.(float32)), true
case reflect.Float64:
return v.(float64), true
}
return 0, false
}
// RemovePtrFromType will return the type of t and strips away any pointer(s)
// in front of the actual type.
func RemovePtrFromType(t interface{}) reflect.Type {
var v reflect.Type
if rt, isType := t.(reflect.Type); isType {
v = rt
} else {
v = reflect.TypeOf(t)
}
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
return v
}
// RemovePtrFromValue will return the value of t and strips away any pointer(s)
// in front of the actual type.
func RemovePtrFromValue(t interface{}) reflect.Value {
var v reflect.Value
if rv, isValue := t.(reflect.Value); isValue {
v = rv
} else {
v = reflect.ValueOf(t)
}
for v.Type().Kind() == reflect.Ptr {
v = v.Elem()
}
return v
}
// UnsafeCopy will copy data from src to dst while ignoring type information.
// Both types need to be of the same size and dst and src have to be pointers.
// UnsafeCopy will panic if these requirements are not met.
func UnsafeCopy(dst, src interface{}) {
dstValue := reflect.ValueOf(dst)
srcValue := reflect.ValueOf(src)
UnsafeCopyValue(dstValue, srcValue)
}
// UnsafeCopyValue will copy data from src to dst while ignoring type
// information. Both types need to be of the same size or this function will
// panic. Also both types must support dereferencing via reflect.Elem()
func UnsafeCopyValue(dstValue reflect.Value, srcValue reflect.Value) {
dstType := dstValue.Elem().Type()
srcType := srcValue.Type()
var srcPtr uintptr
if srcValue.Kind() != reflect.Ptr {
// If we don't get a pointer to our source data we need to forcefully
// retrieve it by accessing the interface pointer. This is ok as we
// only read from it.
iface := srcValue.Interface()
srcPtr = reflect.ValueOf(&iface).Elem().InterfaceData()[1] // Pointer to data
} else {
srcType = srcValue.Elem().Type()
srcPtr = srcValue.Pointer()
}
if dstType.Size() != srcType.Size() {
panic("Type size mismatch between " + dstType.String() + " and " + srcType.String())
}
dstAsSlice := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: dstValue.Pointer(),
Len: int(dstType.Size()),
Cap: int(dstType.Size()),
}))
srcAsSlice := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: srcPtr,
Len: int(srcType.Size()),
Cap: int(srcType.Size()),
}))
copy(dstAsSlice, srcAsSlice)
}
// SetMemberByName sets member name of the given pointer-to-struct to the data
// passed to this function. The member may be private, too.
func SetMemberByName(ptrToStruct interface{}, name string, data interface{}) {
structVal := reflect.Indirect(reflect.ValueOf(ptrToStruct))
member := structVal.FieldByName(name)
SetValue(member, data)
}
// SetMemberByIndex sets member idx of the given pointer-to-struct to the data
// passed to this function. The member may be private, too.
func SetMemberByIndex(ptrToStruct interface{}, idx int, data interface{}) {
structVal := reflect.Indirect(reflect.ValueOf(ptrToStruct))
member := structVal.Field(idx)
SetValue(member, data)
}
// SetValue sets an addressable value to the data passed to this function.
// In contrast to golangs reflect package this will also work with private
// variables. Please note that this function may not support all types, yet.
func SetValue(member reflect.Value, data interface{}) {
if member.CanSet() {
member.Set(reflect.ValueOf(data).Convert(member.Type()))
return // ### return, easy way ###
}
if !member.CanAddr() {
panic("SetValue requires addressable member type")
}
ptrToMember := unsafe.Pointer(member.UnsafeAddr())
dataValue := reflect.ValueOf(data)
switch member.Kind() {
case reflect.Bool:
*(*bool)(ptrToMember) = dataValue.Bool()
case reflect.Uint:
*(*uint)(ptrToMember) = uint(dataValue.Uint())
case reflect.Uint8:
*(*uint8)(ptrToMember) = uint8(dataValue.Uint())
case reflect.Uint16:
*(*uint16)(ptrToMember) = uint16(dataValue.Uint())
case reflect.Uint32:
*(*uint32)(ptrToMember) = uint32(dataValue.Uint())
case reflect.Uint64:
*(*uint64)(ptrToMember) = dataValue.Uint()
case reflect.Int:
*(*int)(ptrToMember) = int(dataValue.Int())
case reflect.Int8:
*(*int8)(ptrToMember) = int8(dataValue.Int())
case reflect.Int16:
*(*int16)(ptrToMember) = int16(dataValue.Int())
case reflect.Int32:
*(*int32)(ptrToMember) = int32(dataValue.Int())
case reflect.Int64:
*(*int64)(ptrToMember) = dataValue.Int()
case reflect.Float32:
*(*float32)(ptrToMember) = float32(dataValue.Float())
case reflect.Float64:
*(*float64)(ptrToMember) = dataValue.Float()
case reflect.Complex64:
*(*complex64)(ptrToMember) = complex64(dataValue.Complex())
case reflect.Complex128:
*(*complex128)(ptrToMember) = dataValue.Complex()
case reflect.String:
*(*string)(ptrToMember) = dataValue.String()
case reflect.Map, reflect.Chan:
// Exploit the fact that "map" is actually "*runtime.hmap" and force
// overwrite that pointer in the passed struct.
// Same foes for "chan" which is actually "*runtime.hchan".
// Note: Assigning a map or channel to another variable does NOT copy
// the contents so copying the pointer follows go's standard behavior.
dataAsPtr := unsafe.Pointer(dataValue.Pointer())
*(**uintptr)(ptrToMember) = (*uintptr)(dataAsPtr)
case reflect.Interface:
// Interfaces are basically two pointers, see runtime.iface.
// We want to modify exactly that data, which is returned by
// the InterfaceData() method.
if dataValue.Kind() != reflect.Interface {
// A type reference was passed. In order to overwrite the memory
// Representation of an interface we need to generate it first.
// Reflect does not allow us to do that unless we use the
// InterfaceData method which exposes the internal representation
// of an interface.
interfaceData := reflect.ValueOf(&data).Elem().InterfaceData()
dataValue = reflect.ValueOf(interfaceData)
}
fallthrough
default:
// Complex types are assigned memcpy style.
// Note: This should not break the garbage collector although we cannot
// be 100% sure on this.
UnsafeCopyValue(member.Addr(), dataValue)
}
}

97
vendor/github.com/trivago/tgo/treflect/typeregistry.go generated vendored Normal file
View File

@ -0,0 +1,97 @@
// Copyright 2015-2016 trivago GmbH
//
// 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 treflect
import (
"fmt"
"reflect"
"strings"
)
// TypeRegistry is a name to type registry used to create objects by name.
type TypeRegistry struct {
namedType map[string]reflect.Type
}
// NewTypeRegistry creates a new TypeRegistry. Note that there is a global type
// registry available in the main tgo package (tgo.TypeRegistry).
func NewTypeRegistry() TypeRegistry {
return TypeRegistry{
namedType: make(map[string]reflect.Type),
}
}
// Register a plugin to the TypeRegistry by passing an uninitialized object.
func (registry TypeRegistry) Register(typeInstance interface{}) {
registry.RegisterWithDepth(typeInstance, 1)
}
// RegisterWithDepth to register a plugin to the TypeRegistry by passing an uninitialized object.
func (registry TypeRegistry) RegisterWithDepth(typeInstance interface{}, depth int) {
structType := reflect.TypeOf(typeInstance)
packageName := structType.PkgPath()
typeName := structType.Name()
pathTokens := strings.Split(packageName, "/")
maxDepth := 3
if len(pathTokens) < maxDepth {
maxDepth = len(pathTokens)
}
for n := depth; n <= maxDepth; n++ {
shortTypeName := strings.Join(pathTokens[len(pathTokens)-n:], ".") + "." + typeName
registry.namedType[shortTypeName] = structType
}
}
// New creates an uninitialized object by class name.
// The class name has to be "package.class" or "package/subpackage.class".
// The gollum package is omitted from the package path.
func (registry TypeRegistry) New(typeName string) (interface{}, error) {
structType, exists := registry.namedType[typeName]
if exists {
return reflect.New(structType).Interface(), nil
}
return nil, fmt.Errorf("Unknown class: %s", typeName)
}
// GetTypeOf returns only the type asscociated with the given name.
// If the name is not registered, nil is returned.
// The type returned will be a pointer type.
func (registry TypeRegistry) GetTypeOf(typeName string) reflect.Type {
if structType, exists := registry.namedType[typeName]; exists {
return reflect.PtrTo(structType)
}
return nil
}
// IsTypeRegistered returns true if a type is registered to this registry.
// Note that GetTypeOf can do the same thing by checking for nil but also
// returns the type, so in many cases you will want to call this function.
func (registry TypeRegistry) IsTypeRegistered(typeName string) bool {
_, exists := registry.namedType[typeName]
return exists
}
// GetRegistered returns the names of all registered types for a given package
func (registry TypeRegistry) GetRegistered(packageName string) []string {
var result []string
for key := range registry.namedType {
if strings.HasPrefix(key, packageName) {
result = append(result, key)
}
}
return result
}