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:
commit
3c696e6d01
23
Gopkg.lock
generated
23
Gopkg.lock
generated
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
187
internal/jira/jira.go
Normal 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
|
||||||
|
}
|
9
internal/jira/jira_test.go
Normal file
9
internal/jira/jira_test.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package jira
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJira(t *testing.T) {
|
||||||
|
createOne()
|
||||||
|
}
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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))
|
||||||
|
@ -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
29
vendor/github.com/andygrunwald/go-jira/.gitignore
generated
vendored
Normal 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
17
vendor/github.com/andygrunwald/go-jira/.travis.yml
generated
vendored
Normal 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
36
vendor/github.com/andygrunwald/go-jira/Gopkg.lock
generated
vendored
Normal 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
46
vendor/github.com/andygrunwald/go-jira/Gopkg.toml
generated
vendored
Normal 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
22
vendor/github.com/andygrunwald/go-jira/LICENSE
generated
vendored
Normal 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
2
vendor/github.com/andygrunwald/go-jira/Makefile
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
test:
|
||||||
|
go test -v ./...
|
271
vendor/github.com/andygrunwald/go-jira/README.md
generated
vendored
Normal file
271
vendor/github.com/andygrunwald/go-jira/README.md
generated
vendored
Normal 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).
|
187
vendor/github.com/andygrunwald/go-jira/authentication.go
generated
vendored
Normal file
187
vendor/github.com/andygrunwald/go-jira/authentication.go
generated
vendored
Normal 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
166
vendor/github.com/andygrunwald/go-jira/board.go
generated
vendored
Normal 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
82
vendor/github.com/andygrunwald/go-jira/error.go
generated
vendored
Normal 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
153
vendor/github.com/andygrunwald/go-jira/group.go
generated
vendored
Normal 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
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
432
vendor/github.com/andygrunwald/go-jira/jira.go
generated
vendored
Normal 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
194
vendor/github.com/andygrunwald/go-jira/metaissue.go
generated
vendored
Normal 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
162
vendor/github.com/andygrunwald/go-jira/project.go
generated
vendored
Normal 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
106
vendor/github.com/andygrunwald/go-jira/sprint.go
generated
vendored
Normal 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
119
vendor/github.com/andygrunwald/go-jira/user.go
generated
vendored
Normal 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
96
vendor/github.com/andygrunwald/go-jira/version.go
generated
vendored
Normal 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
23
vendor/github.com/fatih/structs/.gitignore
generated
vendored
Normal 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
11
vendor/github.com/fatih/structs/.travis.yml
generated
vendored
Normal 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
21
vendor/github.com/fatih/structs/LICENSE
generated
vendored
Normal 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
163
vendor/github.com/fatih/structs/README.md
generated
vendored
Normal 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
141
vendor/github.com/fatih/structs/field.go
generated
vendored
Normal 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
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
32
vendor/github.com/fatih/structs/tags.go
generated
vendored
Normal 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
202
vendor/github.com/trivago/tgo/LICENSE
generated
vendored
Normal 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
113
vendor/github.com/trivago/tgo/tcontainer/arrays.go
generated
vendored
Normal 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
157
vendor/github.com/trivago/tgo/tcontainer/bytepool.go
generated
vendored
Normal 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
464
vendor/github.com/trivago/tgo/tcontainer/marshalmap.go
generated
vendored
Normal 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
227
vendor/github.com/trivago/tgo/tcontainer/trie.go
generated
vendored
Normal 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
373
vendor/github.com/trivago/tgo/treflect/reflection.go
generated
vendored
Normal 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
97
vendor/github.com/trivago/tgo/treflect/typeregistry.go
generated
vendored
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user