mirror of
https://github.com/Threnklyn/jira.git
synced 2026-05-22 14:08:26 +02:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 66596f3aa5 | |||
| 3a68ccdd3f | |||
| 91c57d496c | |||
| 87ab7291e1 | |||
| b72040bfd4 | |||
| 2dcc840a06 | |||
| 10c4aef671 | |||
| b5643e545b | |||
| fc00743095 | |||
| 6a3e2aa4d4 | |||
| abc3953448 | |||
| 52eb7f4ed7 | |||
| a5c7a133c0 | |||
| f42d0b6366 | |||
| 8040746bcf | |||
| 90a8ee7c33 | |||
| 74ae039c37 | |||
| 20a16e2d0c | |||
| 4c23867836 | |||
| b2edc436bc | |||
| cc5878fabc | |||
| 60f07bcdd6 | |||
| 400b53acc8 | |||
| 18cfbb337e | |||
| 923b7f6cc7 | |||
| f0f620f739 | |||
| 71f4a8012d | |||
| 4532f75db3 | |||
| f95aa3d261 | |||
| 4d95bde10f | |||
| b35b8d1fd1 | |||
| fd4ec5e641 | |||
| 0b88d0ad97 | |||
| 8c07442645 | |||
| f3feff796f | |||
| 28bd1dffa5 | |||
| 5f7b46173a | |||
| 39a194b858 | |||
| 4b6329597b |
@@ -0,0 +1,42 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 0.0.7 - 2015-07-01
|
||||||
|
|
||||||
|
* fix "take" command not honouring user option [Andrew Haigh] [[8f1d2b9](https://github.com/Netflix-Skunkworks/go-jira/commit/8f1d2b9)]
|
||||||
|
* fix typo [Cory Bennett] [[06f57fe](https://github.com/Netflix-Skunkworks/go-jira/commit/06f57fe)]
|
||||||
|
|
||||||
|
## 0.0.6 - 2015-02-27
|
||||||
|
|
||||||
|
* allow --sort= to disable sort override [Cory Bennett] [[701f091](https://github.com/Netflix-Skunkworks/go-jira/commit/701f091)]
|
||||||
|
* fix default JIRA_OPERATION env variable [Cory Bennett] [[82fd9b9](https://github.com/Netflix-Skunkworks/go-jira/commit/82fd9b9)]
|
||||||
|
* automatically close duplicate issues with "Duplicate" resolution [Cory Bennett] [[ebf1700](https://github.com/Netflix-Skunkworks/go-jira/commit/ebf1700)]
|
||||||
|
* set JIRA_OPERATION to "view" when no operation used (ie: jira GOJIRA-123) [Cory Bennett] [[050848a](https://github.com/Netflix-Skunkworks/go-jira/commit/050848a)]
|
||||||
|
* add --sort option to "list" command [Cory Bennett] [[f359030](https://github.com/Netflix-Skunkworks/go-jira/commit/f359030)]
|
||||||
|
|
||||||
|
## 0.0.5 - 2015-02-21
|
||||||
|
|
||||||
|
* handle editor having arguments [Cory Bennett] [[7186fb3](https://github.com/Netflix-Skunkworks/go-jira/commit/7186fb3)]
|
||||||
|
* add more template error handling [Cory Bennett] [[3e6f2b3](https://github.com/Netflix-Skunkworks/go-jira/commit/3e6f2b3)]
|
||||||
|
* allow create template to specify defalt watchers with -o watchers=... [Cory Bennett] [[4db2e4e](https://github.com/Netflix-Skunkworks/go-jira/commit/4db2e4e)]
|
||||||
|
* if config files are executable then run them and parse the output [Cory Bennett] [[7a2f7f5](https://github.com/Netflix-Skunkworks/go-jira/commit/7a2f7f5)]
|
||||||
|
|
||||||
|
## 0.0.4 - 2015-02-19
|
||||||
|
|
||||||
|
* add --template option to export-templates to export a single template [Cory Bennett] [[343fbb6](https://github.com/Netflix-Skunkworks/go-jira/commit/343fbb6)]
|
||||||
|
* add "table" template to be used with "list" command [Cory Bennett] [[8954ec1](https://github.com/Netflix-Skunkworks/go-jira/commit/8954ec1)]
|
||||||
|
|
||||||
|
## 0.0.3 - 2015-02-19
|
||||||
|
|
||||||
|
* [issue [#8](https://github.com/Netflix-Skunkworks/go-jira/issues/8)] detect X-Seraph-Loginreason: AUTHENTICATION_DENIED header to catch login failures [Cory Bennett] [[2dcf665](https://github.com/Netflix-Skunkworks/go-jira/commit/2dcf665)]
|
||||||
|
* project should always be uppercase [Jay Buffington] [[1b69d12](https://github.com/Netflix-Skunkworks/go-jira/commit/1b69d12)]
|
||||||
|
* if response is 400, check json for errorMessages and log them [Jay Buffington] [[4924dfa](https://github.com/Netflix-Skunkworks/go-jira/commit/4924dfa)]
|
||||||
|
* validate project [Jay Buffington] [[dc5ae42](https://github.com/Netflix-Skunkworks/go-jira/commit/dc5ae42)]
|
||||||
|
|
||||||
|
## 0.0.2 - 2015-02-18
|
||||||
|
|
||||||
|
* add missing --override options on transition command
|
||||||
|
* add browse command
|
||||||
|
|
||||||
|
## 0.0.1 - 2015-02-18
|
||||||
|
|
||||||
|
* Initial experimental release
|
||||||
@@ -18,13 +18,42 @@ export GOPATH=$(shell pwd)
|
|||||||
|
|
||||||
build:
|
build:
|
||||||
cd src/github.com/Netflix-Skunkworks/go-jira/jira; \
|
cd src/github.com/Netflix-Skunkworks/go-jira/jira; \
|
||||||
go install -v
|
go get -v
|
||||||
|
|
||||||
|
|
||||||
|
cross-setup:
|
||||||
|
for p in $(PLATFORMS); do \
|
||||||
|
echo "Building for $$p"; \
|
||||||
|
cd $(GOROOT)/src && sudo GOOS=$${p/-*/} GOARCH=$${p/*-/} bash ./make.bash --no-clean; \
|
||||||
|
done
|
||||||
|
|
||||||
all:
|
all:
|
||||||
|
rm -rf $(DIST); \
|
||||||
mkdir -p $(DIST); \
|
mkdir -p $(DIST); \
|
||||||
cd src/github.com/Netflix-Skunkworks/go-jira/jira; \
|
cd src/github.com/Netflix-Skunkworks/go-jira/jira; \
|
||||||
go get -d; \
|
go get -d; \
|
||||||
for p in $(PLATFORMS); do \
|
for p in $(PLATFORMS); do \
|
||||||
echo "Building for $$p"; \
|
echo "Building for $$p"; \
|
||||||
GOOS=$${p/-*/} GOARCH=$${p/*-/} go build -v -o $(DIST)/jira-$$p; \
|
GOOS=$${p/-*/} GOARCH=$${p/*-/} go build -v -ldflags -s -o $(DIST)/jira-$$p; \
|
||||||
done
|
done
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
gofmt -s -w jira
|
||||||
|
|
||||||
|
CURVER := $(shell grep '\#\#' CHANGELOG.md | awk '{print $$2; exit}')
|
||||||
|
NEWVER := $(shell awk -F'"' '/docopt.Parse/{print $$2}' jira/main.go)
|
||||||
|
TODAY := $(shell date +%Y-%m-%d)
|
||||||
|
|
||||||
|
changes:
|
||||||
|
@git log --pretty=format:"* %s [%cn] [%h]" --no-merges ^$(CURVER) HEAD jira | grep -v gofmt | grep -v "bump version"
|
||||||
|
|
||||||
|
update-changelog:
|
||||||
|
@echo "# Changelog" > CHANGELOG.md.new; \
|
||||||
|
echo >> CHANGELOG.md.new; \
|
||||||
|
echo "## $(NEWVER) - $(TODAY)" >> CHANGELOG.md.new; \
|
||||||
|
echo >> CHANGELOG.md.new; \
|
||||||
|
$(MAKE) changes | \
|
||||||
|
perl -pe 's{\[([a-f0-9]+)\]}{[[$$1](https://github.com/Netflix-Skunkworks/go-jira/commit/$$1)]}g' | \
|
||||||
|
perl -pe 's{\#(\d+)}{[#$$1](https://github.com/Netflix-Skunkworks/go-jira/issues/$$1)}g' >> CHANGELOG.md.new; \
|
||||||
|
tail +2 CHANGELOG.md >> CHANGELOG.md.new; \
|
||||||
|
mv CHANGELOG.md.new CHANGELOG.md
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# go-jira
|
# go-jira
|
||||||
simple jira command line client in Go
|
simple command line client for Atlassian's Jira service written in Go
|
||||||
|
|
||||||
## Synopsis
|
## Synopsis
|
||||||
|
|
||||||
@@ -8,20 +8,24 @@ jira ls -p GOJIRA # list all unresolved issues for project
|
|||||||
jira ls -p GOJIRA -a mothra # as above also assigned to user mothra
|
jira ls -p GOJIRA -a mothra # as above also assigned to user mothra
|
||||||
jira ls -p GOJIRA -w mothra # lists GOJIRA unresolved issues watched by user mothra
|
jira ls -p GOJIRA -w mothra # lists GOJIRA unresolved issues watched by user mothra
|
||||||
jira ls -p GOJIRA -r mothra # list GOJIRA unresolved issues reported by user mothra
|
jira ls -p GOJIRA -r mothra # list GOJIRA unresolved issues reported by user mothra
|
||||||
|
jira ls -t table -p GOJIRA # list all unresolved issues in pretty table output
|
||||||
|
|
||||||
jira view GOJIRA-321 # print Issue using "view" template
|
jira view GOJIRA-321 # print Issue using "view" template
|
||||||
jira GOJIRA-321 # same as above
|
jira GOJIRA-321 # same as above
|
||||||
|
|
||||||
jira edit GOJIRA-321 # open up the issue in an editor, when you exit the editor
|
jira edit GOJIRA-321 # open up the issue in an editor, when you exit the
|
||||||
# the issue will post the updates to the server
|
# editor the issue will post the updates to the server
|
||||||
|
|
||||||
# edit the issue, using the overirdes on the command line, skip the interactive editor:
|
# edit the issue, using the overirdes on the command line, skip the interactive editor:
|
||||||
jira edit GOJIRA-321 --noedit -o assignee=mothra -o comment="mothra, please take care of this." -o priority=Major
|
jira edit GOJIRA-321 --noedit \
|
||||||
|
-o assignee=mothra \
|
||||||
|
-o comment="mothra, please take care of this." \
|
||||||
|
-o priority=Major
|
||||||
|
|
||||||
jira create -p GOJIRA # create new "Bug" type issue for project GOJIRA
|
jira create -p GOJIRA # create new "Bug" type issue for project GOJIRA
|
||||||
jira create -p GOJIRA -i Task # create new Task type issue
|
jira create -p GOJIRA -i Task # create new Task type issue
|
||||||
|
|
||||||
jira trans close GOJIRA-321 # close issue, with interactive editor to be able to set other fields
|
jira trans close GOJIRA-321 # close issue, with interactive editor to set fields
|
||||||
jira close GOJIRA-321 --edit # same as above
|
jira close GOJIRA-321 --edit # same as above
|
||||||
|
|
||||||
# close the issue, set the resolution, and skip interactive editor:
|
# close the issue, set the resolution, and skip interactive editor:
|
||||||
@@ -47,9 +51,14 @@ jira ls # list all unresolved issues for project
|
|||||||
jira ls -a mothra # as above also assigned to user mothra
|
jira ls -a mothra # as above also assigned to user mothra
|
||||||
jira ls -w mothra # lists GOJIRA unresolved issues watched by user mothra
|
jira ls -w mothra # lists GOJIRA unresolved issues watched by user mothra
|
||||||
jira ls -r mothra # list GOJIRA unresolved issues reported by user mothra
|
jira ls -r mothra # list GOJIRA unresolved issues reported by user mothra
|
||||||
|
jira ls -t table # list all unresolved issues in pretty table output
|
||||||
|
|
||||||
jira create # create new "Bug" type issue for project GOJIRA
|
jira create # create new "Bug" type issue for project GOJIRA
|
||||||
jira create -i Task # create new Task type issue
|
jira create -i Task # create new Task type issue
|
||||||
|
|
||||||
|
# make the table template your default "list" template:
|
||||||
|
jira export-templates -t table
|
||||||
|
mv $HOME/.jira.d/templates/table $HOME/.jira.d/templates/list
|
||||||
```
|
```
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
@@ -108,6 +117,36 @@ endpoint: https://jira.mycompany.com
|
|||||||
EOM
|
EOM
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Dynamic Configuration
|
||||||
|
|
||||||
|
If the **.jira.d/config.yml** file is executable, then **go-jira** will attempt to execute the file and use the stdout for configuration. You can use this to customize templates or other overrides depending on what type of operation you are running. For example if you would like to use the "table" template when ever you run `jira ls`, then you can create a template like this:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo "endpoint: https://jira.mycompany.com"
|
||||||
|
echo "editor: emacs -nw"
|
||||||
|
|
||||||
|
case $JIRA_OPERATION in
|
||||||
|
list)
|
||||||
|
echo "template: table";;
|
||||||
|
esac
|
||||||
|
```
|
||||||
|
|
||||||
|
Or if you always set the same overrides when you create an issue for your project you can do something like this:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/bin/sh
|
||||||
|
echo "project: GOJIRA"
|
||||||
|
|
||||||
|
case $JIRA_OPERATION in
|
||||||
|
create)
|
||||||
|
echo "assignee: $USER"
|
||||||
|
echo "watchers: mothra"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
```
|
||||||
|
|
||||||
### Editing
|
### Editing
|
||||||
|
|
||||||
When you run command like `jira edit` it will open up your favorite editor with the templatized output so you can quickly edit. When the editor
|
When you run command like `jira edit` it will open up your favorite editor with the templatized output so you can quickly edit. When the editor
|
||||||
@@ -132,21 +171,20 @@ hard-coded templates with `jira export-templates` which will write them to **~/.
|
|||||||
|
|
||||||
```
|
```
|
||||||
Usage:
|
Usage:
|
||||||
Usage:
|
jira [-v ...] [-u USER] [-e URI] [-t FILE] (ls|list) ( [-q JQL] | [-p PROJECT] [-c COMPONENT] [-a ASSIGNEE] [-i ISSUETYPE] [-w WATCHER] [-r REPORTER]) [-f FIELDS]
|
||||||
jira [-v ...] [-u USER] [-e URI] [-t FILE] (ls|list) ( [-q JQL] | [-p PROJECT] [-c COMPONENT] [-a ASSIGNEE] [-i ISSUETYPE] [-w WATCHER] [-r REPORTER])
|
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] view ISSUE
|
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] view ISSUE
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] edit ISSUE [--noedit] [-m COMMENT] [-o KEY=VAL]...
|
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] edit ISSUE [--noedit] [-m COMMENT] [-o KEY=VAL]...
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] create [--noedit] [-p PROJECT] [-i ISSUETYPE] [-o KEY=VAL]...
|
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] create [--noedit] [-p PROJECT] [-i ISSUETYPE] [-o KEY=VAL]...
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] DUPLICATE dups ISSUE
|
jira [-v ...] [-u USER] [-e URI] [-b] DUPLICATE dups ISSUE
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] BLOCKER blocks ISSUE
|
jira [-v ...] [-u USER] [-e URI] [-b] BLOCKER blocks ISSUE
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] watch ISSUE [-w WATCHER]
|
jira [-v ...] [-u USER] [-e URI] [-b] watch ISSUE [-w WATCHER]
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] (trans|transition) TRANSITION ISSUE [-m COMMENT] [--noedit]
|
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] (trans|transition) TRANSITION ISSUE [-m COMMENT] [-o KEY=VAL] [--noedit]
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] ack ISSUE [-m COMMENT] [--edit]
|
jira [-v ...] [-u USER] [-e URI] [-b] ack ISSUE [-m COMMENT] [-o KEY=VAL] [--edit]
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] close ISSUE [-m COMMENT] [--edit]
|
jira [-v ...] [-u USER] [-e URI] [-b] close ISSUE [-m COMMENT] [-o KEY=VAL] [--edit]
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] resolve ISSUE [-m COMMENT] [--edit]
|
jira [-v ...] [-u USER] [-e URI] [-b] resolve ISSUE [-m COMMENT] [-o KEY=VAL] [--edit]
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] reopen ISSUE [-m COMMENT] [--edit]
|
jira [-v ...] [-u USER] [-e URI] [-b] reopen ISSUE [-m COMMENT] [-o KEY=VAL] [--edit]
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] start ISSUE [-m COMMENT] [--edit]
|
jira [-v ...] [-u USER] [-e URI] [-b] start ISSUE [-m COMMENT] [-o KEY=VAL] [--edit]
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] stop ISSUE [-m COMMENT] [--edit]
|
jira [-v ...] [-u USER] [-e URI] [-b] stop ISSUE [-m COMMENT] [-o KEY=VAL] [--edit]
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] comment ISSUE [-m COMMENT]
|
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] comment ISSUE [-m COMMENT]
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] take ISSUE
|
jira [-v ...] [-u USER] [-e URI] [-b] take ISSUE
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] (assign|give) ISSUE ASSIGNEE
|
jira [-v ...] [-u USER] [-e URI] [-b] (assign|give) ISSUE ASSIGNEE
|
||||||
@@ -157,7 +195,8 @@ Usage:
|
|||||||
jira [-v ...] [-u USER] [-e URI] [-t FILE] issuetypes [-p PROJECT]
|
jira [-v ...] [-u USER] [-e URI] [-t FILE] issuetypes [-p PROJECT]
|
||||||
jira [-v ...] [-u USER] [-e URI] [-t FILE] createmeta [-p PROJECT] [-i ISSUETYPE]
|
jira [-v ...] [-u USER] [-e URI] [-t FILE] createmeta [-p PROJECT] [-i ISSUETYPE]
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] transitions ISSUE
|
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] transitions ISSUE
|
||||||
jira [-v ...] export-templates [-d DIR]
|
jira [-v ...] export-templates [-d DIR] [-t template]
|
||||||
|
jira [-v ...] [-u USER] [-e URI] (b|browse) ISSUE
|
||||||
jira [-v ...] [-u USER] [-e URI] [-t FILE] login
|
jira [-v ...] [-u USER] [-e URI] [-t FILE] login
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] ISSUE
|
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] ISSUE
|
||||||
|
|
||||||
@@ -174,7 +213,7 @@ Command Options:
|
|||||||
-b --browse Open your browser to the Jira issue
|
-b --browse Open your browser to the Jira issue
|
||||||
-c --component=COMPONENT Component to Search for
|
-c --component=COMPONENT Component to Search for
|
||||||
-d --directory=DIR Directory to export templates to (default: /Users/cbennett/.jira.d/templates)
|
-d --directory=DIR Directory to export templates to (default: /Users/cbennett/.jira.d/templates)
|
||||||
-f --queryfields Fields that are used in "list" template: (default: summary)
|
-f --queryfields=FIELDS Fields that are used in "list" template: (default: summary,created,priority,status,reporter,assignee)
|
||||||
-i --issuetype=ISSUETYPE Jira Issue Type (default: Bug)
|
-i --issuetype=ISSUETYPE Jira Issue Type (default: Bug)
|
||||||
-m --comment=COMMENT Comment message for transition
|
-m --comment=COMMENT Comment message for transition
|
||||||
-o --override=KEY:VAL Set custom key/value pairs
|
-o --override=KEY:VAL Set custom key/value pairs
|
||||||
|
|||||||
+11
-4
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/kballard/go-shellquote"
|
||||||
"github.com/op/go-logging"
|
"github.com/op/go-logging"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -32,6 +33,10 @@ func New(opts map[string]string) *Cli {
|
|||||||
endpoint, _ := opts["endpoint"]
|
endpoint, _ := opts["endpoint"]
|
||||||
url, _ := url.Parse(strings.TrimRight(endpoint, "/"))
|
url, _ := url.Parse(strings.TrimRight(endpoint, "/"))
|
||||||
|
|
||||||
|
if project, ok := opts["project"]; ok {
|
||||||
|
opts["project"] = strings.ToUpper(project)
|
||||||
|
}
|
||||||
|
|
||||||
cli := &Cli{
|
cli := &Cli{
|
||||||
endpoint: url,
|
endpoint: url,
|
||||||
opts: opts,
|
opts: opts,
|
||||||
@@ -240,11 +245,13 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m
|
|||||||
if val, ok := c.opts["edit"]; ok && val == "false" {
|
if val, ok := c.opts["edit"]; ok && val == "false" {
|
||||||
editing = false
|
editing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
for true {
|
for true {
|
||||||
if editing {
|
if editing {
|
||||||
log.Debug("Running: %s %s", editor, tmpFileName)
|
shell, _ := shellquote.Split(editor)
|
||||||
cmd := exec.Command(editor, tmpFileName)
|
shell = append(shell, tmpFileName)
|
||||||
|
log.Debug("Running: %#v", shell)
|
||||||
|
cmd := exec.Command(shell[0], shell[1:]...)
|
||||||
cmd.Stdout, cmd.Stderr, cmd.Stdin = os.Stdout, os.Stderr, os.Stdin
|
cmd.Stdout, cmd.Stderr, cmd.Stdin = os.Stdout, os.Stderr, os.Stdin
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
log.Error("Failed to edit template with %s: %s", editor, err)
|
log.Error("Failed to edit template with %s: %s", editor, err)
|
||||||
@@ -281,7 +288,7 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m
|
|||||||
if _, ok := templateData["meta"]; ok {
|
if _, ok := templateData["meta"]; ok {
|
||||||
mf := templateData["meta"].(map[string]interface{})["fields"]
|
mf := templateData["meta"].(map[string]interface{})["fields"]
|
||||||
if f, ok := edited["fields"].(map[string]interface{}); ok {
|
if f, ok := edited["fields"].(map[string]interface{}); ok {
|
||||||
for k, _ := range f {
|
for k := range f {
|
||||||
if _, ok := mf.(map[string]interface{})[k]; !ok {
|
if _, ok := mf.(map[string]interface{})[k]; !ok {
|
||||||
err := fmt.Errorf("Field %s is not editable", k)
|
err := fmt.Errorf("Field %s is not editable", k)
|
||||||
log.Error("%s", err)
|
log.Error("%s", err)
|
||||||
|
|||||||
+40
-14
@@ -27,14 +27,22 @@ func (c *Cli) CmdLogin() error {
|
|||||||
// probably got this, need to redirect the user to login manually
|
// probably got this, need to redirect the user to login manually
|
||||||
// X-Authentication-Denied-Reason: CAPTCHA_CHALLENGE; login-url=https://jira/login.jsp
|
// X-Authentication-Denied-Reason: CAPTCHA_CHALLENGE; login-url=https://jira/login.jsp
|
||||||
if reason := resp.Header.Get("X-Authentication-Denied-Reason"); reason != "" {
|
if reason := resp.Header.Get("X-Authentication-Denied-Reason"); reason != "" {
|
||||||
log.Error("Authentication Failed: %s", reason)
|
err := fmt.Errorf("Authenticaion Failed: %s", reason)
|
||||||
return fmt.Errorf("Authenticaion Failed: %s", reason)
|
log.Error("%s", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
log.Error("Authentication Failead: Unknown")
|
err := fmt.Errorf("Authentication Failed: Unknown Reason")
|
||||||
return fmt.Errorf("Authentication Failead")
|
log.Error("%s", err)
|
||||||
|
return err
|
||||||
|
|
||||||
}
|
} else if resp.StatusCode == 200 {
|
||||||
if resp.StatusCode != 200 {
|
// https://confluence.atlassian.com/display/JIRA043/JIRA+REST+API+%28Alpha%29+Tutorial#JIRARESTAPI%28Alpha%29Tutorial-CAPTCHAs
|
||||||
|
// probably bad password, try again
|
||||||
|
if reason := resp.Header.Get("X-Seraph-Loginreason"); reason == "AUTHENTICATION_DENIED" {
|
||||||
|
log.Warning("Authentication Failed: %s", reason)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
log.Warning("Login failed")
|
log.Warning("Login failed")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -90,12 +98,17 @@ func (c *Cli) CmdList() error {
|
|||||||
if reporter, ok := c.opts["reporter"]; ok {
|
if reporter, ok := c.opts["reporter"]; ok {
|
||||||
qbuff.WriteString(fmt.Sprintf(" AND reporter = '%s'", reporter))
|
qbuff.WriteString(fmt.Sprintf(" AND reporter = '%s'", reporter))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sort, ok := c.opts["sort"]; ok && sort != "" {
|
||||||
|
qbuff.WriteString(fmt.Sprintf(" ORDER BY %s", sort ))
|
||||||
|
}
|
||||||
|
|
||||||
query = qbuff.String()
|
query = qbuff.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := make([]string,0)
|
fields := make([]string, 0)
|
||||||
if qf, ok := c.opts["queryfields"]; ok {
|
if qf, ok := c.opts["queryfields"]; ok {
|
||||||
fields = strings.Split(qf,",")
|
fields = strings.Split(qf, ",")
|
||||||
} else {
|
} else {
|
||||||
fields = append(fields, "summary")
|
fields = append(fields, "summary")
|
||||||
}
|
}
|
||||||
@@ -104,7 +117,7 @@ func (c *Cli) CmdList() error {
|
|||||||
"jql": query,
|
"jql": query,
|
||||||
"startAt": "0",
|
"startAt": "0",
|
||||||
"maxResults": "500",
|
"maxResults": "500",
|
||||||
"fields": fields,
|
"fields": fields,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -220,6 +233,11 @@ func (c *Cli) CmdCreateMeta(project string, issuetype string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if val, ok := data.(map[string]interface{})["projects"]; ok {
|
if val, ok := data.(map[string]interface{})["projects"]; ok {
|
||||||
|
if len(val.([]interface{})) == 0 {
|
||||||
|
err = fmt.Errorf("Project '%s' or issuetype '%s' unknown. Unable to createmeta.", project, issuetype)
|
||||||
|
log.Error("%s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
if val, ok = val.([]interface{})[0].(map[string]interface{})["issuetypes"]; ok {
|
if val, ok = val.([]interface{})[0].(map[string]interface{})["issuetypes"]; ok {
|
||||||
data = val.([]interface{})[0]
|
data = val.([]interface{})[0]
|
||||||
}
|
}
|
||||||
@@ -253,6 +271,11 @@ func (c *Cli) CmdCreate(project string, issuetype string) error {
|
|||||||
issueData["overrides"].(map[string]string)["issuetype"] = issuetype
|
issueData["overrides"].(map[string]string)["issuetype"] = issuetype
|
||||||
|
|
||||||
if val, ok := data.(map[string]interface{})["projects"]; ok {
|
if val, ok := data.(map[string]interface{})["projects"]; ok {
|
||||||
|
if len(val.([]interface{})) == 0 {
|
||||||
|
err = fmt.Errorf("Project '%s' or issuetype '%s' unknown. Unable to create issue.", project, issuetype)
|
||||||
|
log.Error("%s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
if val, ok = val.([]interface{})[0].(map[string]interface{})["issuetypes"]; ok {
|
if val, ok = val.([]interface{})[0].(map[string]interface{})["issuetypes"]; ok {
|
||||||
issueData["meta"] = val.([]interface{})[0]
|
issueData["meta"] = val.([]interface{})[0]
|
||||||
}
|
}
|
||||||
@@ -279,7 +302,7 @@ func (c *Cli) CmdCreate(project string, issuetype string) error {
|
|||||||
key := json.(map[string]interface{})["key"]
|
key := json.(map[string]interface{})["key"]
|
||||||
c.Browse(key.(string))
|
c.Browse(key.(string))
|
||||||
fmt.Printf("OK %s %s/browse/%s\n", key, c.endpoint, key)
|
fmt.Printf("OK %s %s/browse/%s\n", key, c.endpoint, key)
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
@@ -450,7 +473,7 @@ func (c *Cli) CmdTransition(issue string, trans string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
uri = fmt.Sprintf("%s/rest/api/2/issue/%s", c.endpoint, issue)
|
uri = fmt.Sprintf("%s/rest/api/2/issue/%s", c.endpoint, issue)
|
||||||
var issueData map[string]interface{}
|
var issueData map[string]interface{}
|
||||||
if data, err := responseToJson(c.get(uri)); err != nil {
|
if data, err := responseToJson(c.get(uri)); err != nil {
|
||||||
@@ -462,9 +485,9 @@ func (c *Cli) CmdTransition(issue string, trans string) error {
|
|||||||
issueData["overrides"] = c.opts
|
issueData["overrides"] = c.opts
|
||||||
issueData["transition"] = map[string]interface{}{
|
issueData["transition"] = map[string]interface{}{
|
||||||
"name": transName,
|
"name": transName,
|
||||||
"id": transId,
|
"id": transId,
|
||||||
};
|
}
|
||||||
|
|
||||||
return c.editTemplate(
|
return c.editTemplate(
|
||||||
c.getTemplate("transition"),
|
c.getTemplate("transition"),
|
||||||
fmt.Sprintf("%s-trans-%s-", issue, trans),
|
fmt.Sprintf("%s-trans-%s-", issue, trans),
|
||||||
@@ -551,6 +574,9 @@ func (c *Cli) CmdExportTemplates() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for name, template := range all_templates {
|
for name, template := range all_templates {
|
||||||
|
if wanted, ok := c.opts["template"]; ok && wanted != name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
templateFile := fmt.Sprintf("%s/%s", dir, name)
|
templateFile := fmt.Sprintf("%s/%s", dir, name)
|
||||||
if _, err := os.Stat(templateFile); err == nil {
|
if _, err := os.Stat(templateFile); err == nil {
|
||||||
log.Warning("Skipping %s, already exists", templateFile)
|
log.Warning("Skipping %s, already exists", templateFile)
|
||||||
|
|||||||
+12
-3
@@ -8,6 +8,7 @@ var all_templates = map[string]string{
|
|||||||
"createmeta": default_debug_template,
|
"createmeta": default_debug_template,
|
||||||
"issuelinktypes": default_debug_template,
|
"issuelinktypes": default_debug_template,
|
||||||
"list": default_list_template,
|
"list": default_list_template,
|
||||||
|
"table": default_table_template,
|
||||||
"view": default_view_template,
|
"view": default_view_template,
|
||||||
"edit": default_edit_template,
|
"edit": default_edit_template,
|
||||||
"transitions": default_transitions_template,
|
"transitions": default_transitions_template,
|
||||||
@@ -21,6 +22,13 @@ const default_debug_template = "{{ . | toJson}}\n"
|
|||||||
|
|
||||||
const default_list_template = "{{ range .issues }}{{ .key | append \":\" | printf \"%-12s\"}} {{ .fields.summary }}\n{{ end }}"
|
const default_list_template = "{{ range .issues }}{{ .key | append \":\" | printf \"%-12s\"}} {{ .fields.summary }}\n{{ end }}"
|
||||||
|
|
||||||
|
const default_table_template = `+{{ "-" | rep 16 }}+{{ "-" | rep 57 }}+{{ "-" | rep 14 }}+{{ "-" | rep 14 }}+{{ "-" | rep 12 }}+{{ "-" | rep 14 }}+{{ "-" | rep 14 }}+
|
||||||
|
| {{ "Issue" | printf "%-14s" }} | {{ "Summary" | printf "%-55s" }} | {{ "Priority" | printf "%-12s" }} | {{ "Status" | printf "%-12s" }} | {{ "Age" | printf "%-10s" }} | {{ "Reporter" | printf "%-12s" }} | {{ "Assignee" | printf "%-12s" }} |
|
||||||
|
+{{ "-" | rep 16 }}+{{ "-" | rep 57 }}+{{ "-" | rep 14 }}+{{ "-" | rep 14 }}+{{ "-" | rep 12 }}+{{ "-" | rep 14 }}+{{ "-" | rep 14 }}+
|
||||||
|
{{ range .issues }}| {{ .key | printf "%-14s"}} | {{ .fields.summary | abbrev 55 | printf "%-55s" }} | {{.fields.priority.name | printf "%-12s" }} | {{.fields.status.name | printf "%-12s" }} | {{.fields.created | age | printf "%-10s" }} | {{if .fields.reporter}}{{ .fields.reporter.name | printf "%-12s"}}{{else}}<unassigned>{{end}} | {{if .fields.assignee }}{{.fields.assignee.name | printf "%-12s" }}{{else}}<unassigned>{{end}} |
|
||||||
|
{{ end }}+{{ "-" | rep 16 }}+{{ "-" | rep 57 }}+{{ "-" | rep 14 }}+{{ "-" | rep 14 }}+{{ "-" | rep 12 }}+{{ "-" | rep 14 }}+{{ "-" | rep 14 }}+
|
||||||
|
`
|
||||||
|
|
||||||
const default_view_template = `issue: {{ .key }}
|
const default_view_template = `issue: {{ .key }}
|
||||||
created: {{ .fields.created }}
|
created: {{ .fields.created }}
|
||||||
status: {{ .fields.status.name }}
|
status: {{ .fields.status.name }}
|
||||||
@@ -29,7 +37,7 @@ project: {{ .fields.project.key }}
|
|||||||
components: {{ range .fields.components }}{{ .name }} {{end}}
|
components: {{ range .fields.components }}{{ .name }} {{end}}
|
||||||
issuetype: {{ .fields.issuetype.name }}
|
issuetype: {{ .fields.issuetype.name }}
|
||||||
assignee: {{ if .fields.assignee }}{{ .fields.assignee.name }}{{end}}
|
assignee: {{ if .fields.assignee }}{{ .fields.assignee.name }}{{end}}
|
||||||
reporter: {{ .fields.reporter.name }}
|
reporter: {{ if .fields.reporter }}{{ .fields.reporter.name }}{{end}}
|
||||||
watchers: {{ range .fields.customfield_10110 }}{{ .name }} {{end}}
|
watchers: {{ range .fields.customfield_10110 }}{{ .name }} {{end}}
|
||||||
blockers: {{ range .fields.issuelinks }}{{if .outwardIssue}}{{ .outwardIssue.key }}[{{.outwardIssue.fields.status.name}}]{{end}}{{end}}
|
blockers: {{ range .fields.issuelinks }}{{if .outwardIssue}}{{ .outwardIssue.key }}[{{.outwardIssue.fields.status.name}}]{{end}}{{end}}
|
||||||
depends: {{ range .fields.issuelinks }}{{if .inwardIssue}}{{ .inwardIssue.key }}[{{.inwardIssue.fields.status.name}}]{{end}}{{end}}
|
depends: {{ range .fields.issuelinks }}{{if .inwardIssue}}{{ .inwardIssue.key }}[{{.inwardIssue.fields.status.name}}]{{end}}{{end}}
|
||||||
@@ -55,7 +63,7 @@ fields:
|
|||||||
assignee:
|
assignee:
|
||||||
name: {{ if .overrides.assignee }}{{.overrides.assignee}}{{else}}{{if .fields.assignee }}{{ .fields.assignee.name }}{{end}}{{end}}
|
name: {{ if .overrides.assignee }}{{.overrides.assignee}}{{else}}{{if .fields.assignee }}{{ .fields.assignee.name }}{{end}}{{end}}
|
||||||
reporter:
|
reporter:
|
||||||
name: {{ or .overrides.reporter .fields.reporter.name }}
|
name: {{ if .overrides.reporter }}{{ .overrides.reporter }}{{else if .fields.reporter}}{{ .fields.reporter.name }}{{end}}
|
||||||
# watchers
|
# watchers
|
||||||
customfield_10110: {{ range .fields.customfield_10110 }}
|
customfield_10110: {{ range .fields.customfield_10110 }}
|
||||||
- name: {{ .name }}{{end}}{{if .overrides.watcher}}
|
- name: {{ .name }}{{end}}{{if .overrides.watcher}}
|
||||||
@@ -88,7 +96,8 @@ const default_create_template = `fields:
|
|||||||
reporter:
|
reporter:
|
||||||
name: {{ or .overrides.reporter .overrides.user }}
|
name: {{ or .overrides.reporter .overrides.user }}
|
||||||
# watchers
|
# watchers
|
||||||
customfield_10110:
|
customfield_10110: {{ range split "," (or .overrides.watchers "")}}
|
||||||
|
- name: {{.}}{{end}}
|
||||||
- name:
|
- name:
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
+53
-2
@@ -13,6 +13,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func FindParentPaths(fileName string) []string {
|
func FindParentPaths(fileName string) []string {
|
||||||
@@ -62,6 +63,28 @@ func readFile(file string) string {
|
|||||||
return string(bytes)
|
return string(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fuzzyAge(start string) (string, error) {
|
||||||
|
if t, err := time.Parse("2006-01-02T15:04:05.000-0700", start); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else {
|
||||||
|
delta := time.Now().Sub(t)
|
||||||
|
if delta.Minutes() < 2 {
|
||||||
|
return "a minute", nil
|
||||||
|
} else if dm := delta.Minutes(); dm < 45 {
|
||||||
|
return fmt.Sprintf("%d minutes", int(dm)), nil
|
||||||
|
} else if dm := delta.Minutes(); dm < 90 {
|
||||||
|
return "an hour", nil
|
||||||
|
} else if dh := delta.Hours(); dh < 24 {
|
||||||
|
return fmt.Sprintf("%d hours", int(dh)), nil
|
||||||
|
} else if dh := delta.Hours(); dh < 48 {
|
||||||
|
return "a day", nil
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("%d days", int(delta.Hours()/24)), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "unknown", nil
|
||||||
|
}
|
||||||
|
|
||||||
func runTemplate(templateContent string, data interface{}, out io.Writer) error {
|
func runTemplate(templateContent string, data interface{}, out io.Writer) error {
|
||||||
|
|
||||||
if out == nil {
|
if out == nil {
|
||||||
@@ -100,6 +123,25 @@ func runTemplate(templateContent string, data interface{}, out io.Writer) error
|
|||||||
"split": func(sep string, content string) []string {
|
"split": func(sep string, content string) []string {
|
||||||
return strings.Split(content, sep)
|
return strings.Split(content, sep)
|
||||||
},
|
},
|
||||||
|
"abbrev": func(max int, content string) string {
|
||||||
|
if len(content) > max {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
buffer.WriteString(content[:max-3])
|
||||||
|
buffer.WriteString("...")
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
},
|
||||||
|
"rep": func(count int, content string) string {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
for i := 0; i < count; i += 1 {
|
||||||
|
buffer.WriteString(content)
|
||||||
|
}
|
||||||
|
return buffer.String()
|
||||||
|
},
|
||||||
|
"age": func(content string) (string, error) {
|
||||||
|
return fuzzyAge(content)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if tmpl, err := template.New("template").Funcs(funcs).Parse(templateContent); err != nil {
|
if tmpl, err := template.New("template").Funcs(funcs).Parse(templateContent); err != nil {
|
||||||
log.Error("Failed to parse template: %s", err)
|
log.Error("Failed to parse template: %s", err)
|
||||||
@@ -116,9 +158,18 @@ func runTemplate(templateContent string, data interface{}, out io.Writer) error
|
|||||||
func responseToJson(resp *http.Response, err error) (interface{}, error) {
|
func responseToJson(resp *http.Response, err error) (interface{}, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
|
||||||
return jsonDecode(resp.Body), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data := jsonDecode(resp.Body)
|
||||||
|
if resp.StatusCode == 400 {
|
||||||
|
if val, ok := data.(map[string]interface{})["errorMessages"]; ok {
|
||||||
|
for _, errMsg := range val.([]interface{}) {
|
||||||
|
log.Error("%s", errMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsonDecode(io io.Reader) interface{} {
|
func jsonDecode(io io.Reader) interface{} {
|
||||||
|
|||||||
+101
-13
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Netflix-Skunkworks/go-jira/jira/cli"
|
"github.com/Netflix-Skunkworks/go-jira/jira/cli"
|
||||||
"github.com/docopt/docopt-go"
|
"github.com/docopt/docopt-go"
|
||||||
@@ -8,6 +9,7 @@ import (
|
|||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,7 +21,7 @@ func main() {
|
|||||||
home := os.Getenv("HOME")
|
home := os.Getenv("HOME")
|
||||||
usage := fmt.Sprintf(`
|
usage := fmt.Sprintf(`
|
||||||
Usage:
|
Usage:
|
||||||
jira [-v ...] [-u USER] [-e URI] [-t FILE] (ls|list) ( [-q JQL] | [-p PROJECT] [-c COMPONENT] [-a ASSIGNEE] [-i ISSUETYPE] [-w WATCHER] [-r REPORTER])
|
jira [-v ...] [-u USER] [-e URI] [-t FILE] (ls|list) ( [-q JQL] | [-p PROJECT] [-c COMPONENT] [-a ASSIGNEE] [-i ISSUETYPE] [-w WATCHER] [-r REPORTER]) [-f FIELDS] [-s ORDER]
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] view ISSUE
|
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] view ISSUE
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] edit ISSUE [--noedit] [-m COMMENT] [-o KEY=VAL]...
|
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] edit ISSUE [--noedit] [-m COMMENT] [-o KEY=VAL]...
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] create [--noedit] [-p PROJECT] [-i ISSUETYPE] [-o KEY=VAL]...
|
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] create [--noedit] [-p PROJECT] [-i ISSUETYPE] [-o KEY=VAL]...
|
||||||
@@ -43,7 +45,7 @@ Usage:
|
|||||||
jira [-v ...] [-u USER] [-e URI] [-t FILE] issuetypes [-p PROJECT]
|
jira [-v ...] [-u USER] [-e URI] [-t FILE] issuetypes [-p PROJECT]
|
||||||
jira [-v ...] [-u USER] [-e URI] [-t FILE] createmeta [-p PROJECT] [-i ISSUETYPE]
|
jira [-v ...] [-u USER] [-e URI] [-t FILE] createmeta [-p PROJECT] [-i ISSUETYPE]
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] transitions ISSUE
|
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] transitions ISSUE
|
||||||
jira [-v ...] export-templates [-d DIR]
|
jira [-v ...] export-templates [-d DIR] [-t template]
|
||||||
jira [-v ...] [-u USER] [-e URI] (b|browse) ISSUE
|
jira [-v ...] [-u USER] [-e URI] (b|browse) ISSUE
|
||||||
jira [-v ...] [-u USER] [-e URI] [-t FILE] login
|
jira [-v ...] [-u USER] [-e URI] [-t FILE] login
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] ISSUE
|
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] ISSUE
|
||||||
@@ -61,18 +63,19 @@ Command Options:
|
|||||||
-b --browse Open your browser to the Jira issue
|
-b --browse Open your browser to the Jira issue
|
||||||
-c --component=COMPONENT Component to Search for
|
-c --component=COMPONENT Component to Search for
|
||||||
-d --directory=DIR Directory to export templates to (default: %s)
|
-d --directory=DIR Directory to export templates to (default: %s)
|
||||||
-f --queryfields Fields that are used in "list" template: (default: summary)
|
-f --queryfields=FIELDS Fields that are used in "list" template: (default: summary,created,priority,status,reporter,assignee)
|
||||||
-i --issuetype=ISSUETYPE Jira Issue Type (default: Bug)
|
-i --issuetype=ISSUETYPE Jira Issue Type (default: Bug)
|
||||||
-m --comment=COMMENT Comment message for transition
|
-m --comment=COMMENT Comment message for transition
|
||||||
-o --override=KEY:VAL Set custom key/value pairs
|
-o --override=KEY:VAL Set custom key/value pairs
|
||||||
-p --project=PROJECT Project to Search for
|
-p --project=PROJECT Project to Search for
|
||||||
-q --query=JQL Jira Query Language expression for the search
|
-q --query=JQL Jira Query Language expression for the search
|
||||||
-r --reporter=USER Reporter to search for
|
-r --reporter=USER Reporter to search for
|
||||||
|
-s --sort=ORDER For list operations, sort issues (default: priority asc, created)
|
||||||
-w --watcher=USER Watcher to add to issue (default: %s)
|
-w --watcher=USER Watcher to add to issue (default: %s)
|
||||||
or Watcher to search for
|
or Watcher to search for
|
||||||
`, user, fmt.Sprintf("%s/.jira.d/templates", home), user)
|
`, user, fmt.Sprintf("%s/.jira.d/templates", home), user)
|
||||||
|
|
||||||
args, err := docopt.Parse(usage, nil, true, "0.0.2", false, false)
|
args, err := docopt.Parse(usage, nil, true, "0.0.7", false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to parse options: %s", err)
|
log.Error("Failed to parse options: %s", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -95,6 +98,8 @@ Command Options:
|
|||||||
|
|
||||||
log.Info("Args: %v", args)
|
log.Info("Args: %v", args)
|
||||||
|
|
||||||
|
populateEnv(args)
|
||||||
|
|
||||||
opts := make(map[string]string)
|
opts := make(map[string]string)
|
||||||
loadConfigs(opts)
|
loadConfigs(opts)
|
||||||
|
|
||||||
@@ -133,17 +138,20 @@ Command Options:
|
|||||||
opts["user"] = user
|
opts["user"] = user
|
||||||
}
|
}
|
||||||
if _, ok := opts["queryfields"]; !ok {
|
if _, ok := opts["queryfields"]; !ok {
|
||||||
opts["queryfields"] = "summary"
|
opts["queryfields"] = "summary,created,priority,status,reporter,assignee"
|
||||||
}
|
}
|
||||||
if _, ok := opts["directory"]; !ok {
|
if _, ok := opts["directory"]; !ok {
|
||||||
opts["directory"] = fmt.Sprintf("%s/.jira.d/templates", home)
|
opts["directory"] = fmt.Sprintf("%s/.jira.d/templates", home)
|
||||||
}
|
}
|
||||||
|
if _, ok := opts["sort"]; !ok {
|
||||||
|
opts["sort"] = "priority asc, created"
|
||||||
|
}
|
||||||
|
|
||||||
if _, ok := opts["endpoint"]; !ok {
|
if _, ok := opts["endpoint"]; !ok {
|
||||||
log.Error("endpoint option required. Either use --endpoint or set a enpoint option in your ~/.jira.d/config.yml file")
|
log.Error("endpoint option required. Either use --endpoint or set a enpoint option in your ~/.jira.d/config.yml file")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
c := cli.New(opts)
|
c := cli.New(opts)
|
||||||
|
|
||||||
log.Debug("opts: %s", opts)
|
log.Debug("opts: %s", opts)
|
||||||
@@ -174,9 +182,7 @@ Command Options:
|
|||||||
opts["edit"] = "true"
|
opts["edit"] = "true"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if val, ok := opts["edit"]; ok && val == "true" {
|
if val, ok := opts["edit"]; ok && val != "true" {
|
||||||
opts["edit"] = "true"
|
|
||||||
} else {
|
|
||||||
opts["edit"] = "false"
|
opts["edit"] = "false"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,10 +224,16 @@ Command Options:
|
|||||||
args["ISSUE"].(string),
|
args["ISSUE"].(string),
|
||||||
)
|
)
|
||||||
} else if validCommand("dups") {
|
} else if validCommand("dups") {
|
||||||
err = c.CmdDups(
|
if err = c.CmdDups(
|
||||||
args["DUPLICATE"].(string),
|
args["DUPLICATE"].(string),
|
||||||
args["ISSUE"].(string),
|
args["ISSUE"].(string),
|
||||||
)
|
); err == nil {
|
||||||
|
opts["resolution"] = "Duplicate"
|
||||||
|
err = c.CmdTransition(
|
||||||
|
args["DUPLICATE"].(string),
|
||||||
|
"close",
|
||||||
|
)
|
||||||
|
}
|
||||||
} else if validCommand("watch") {
|
} else if validCommand("watch") {
|
||||||
err = c.CmdWatch(
|
err = c.CmdWatch(
|
||||||
args["ISSUE"].(string),
|
args["ISSUE"].(string),
|
||||||
@@ -255,7 +267,7 @@ Command Options:
|
|||||||
setEditing(true)
|
setEditing(true)
|
||||||
err = c.CmdComment(args["ISSUE"].(string))
|
err = c.CmdComment(args["ISSUE"].(string))
|
||||||
} else if validCommand("take") {
|
} else if validCommand("take") {
|
||||||
err = c.CmdAssign(args["ISSUE"].(string), user)
|
err = c.CmdAssign(args["ISSUE"].(string), opts["user"])
|
||||||
} else if validCommand("browse") || validCommand("b") {
|
} else if validCommand("browse") || validCommand("b") {
|
||||||
opts["browse"] = "true"
|
opts["browse"] = "true"
|
||||||
err = c.Browse(args["ISSUE"].(string))
|
err = c.Browse(args["ISSUE"].(string))
|
||||||
@@ -283,12 +295,88 @@ func parseYaml(file string, opts map[string]string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func populateEnv(args map[string]interface{}) {
|
||||||
|
foundOp := false
|
||||||
|
for key, val := range args {
|
||||||
|
if val != nil && strings.HasPrefix(key, "--") {
|
||||||
|
if key == "--override" {
|
||||||
|
for _, v := range val.([]string) {
|
||||||
|
if strings.Contains(v, "=") {
|
||||||
|
kv := strings.SplitN(v, "=", 2)
|
||||||
|
envName := fmt.Sprintf("JIRA_%s", strings.ToUpper(kv[0]))
|
||||||
|
os.Setenv(envName, kv[1])
|
||||||
|
} else {
|
||||||
|
log.Error("Malformed override, expected KEY=VALUE, got %s", v)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
envName := fmt.Sprintf("JIRA_%s", strings.ToUpper(key[2:]))
|
||||||
|
switch v := val.(type) {
|
||||||
|
case []string:
|
||||||
|
os.Setenv(envName, strings.Join(v, ","))
|
||||||
|
case string:
|
||||||
|
os.Setenv(envName, v)
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
os.Setenv(envName, "1")
|
||||||
|
} else {
|
||||||
|
os.Setenv(envName, "0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if val != nil {
|
||||||
|
// lower case strings are operations
|
||||||
|
if strings.ToLower(key) == key {
|
||||||
|
if key == "ls" && val.(bool) {
|
||||||
|
foundOp = true
|
||||||
|
os.Setenv("JIRA_OPERATION", "list")
|
||||||
|
} else if key == "b" && val.(bool) {
|
||||||
|
foundOp = true
|
||||||
|
os.Setenv("JIRA_OPERATION", "browse")
|
||||||
|
} else if key == "trans" && val.(bool) {
|
||||||
|
foundOp = true
|
||||||
|
os.Setenv("JIRA_OPERATION", "transition")
|
||||||
|
} else if key == "give" && val.(bool) {
|
||||||
|
foundOp = true
|
||||||
|
os.Setenv("JIRA_OPERATION", "assign")
|
||||||
|
} else if val.(bool) {
|
||||||
|
foundOp = true
|
||||||
|
os.Setenv("JIRA_OPERATION", key)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
os.Setenv(fmt.Sprintf("JIRA_%s", key), val.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundOp {
|
||||||
|
os.Setenv("JIRA_OPERATION", "view")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func loadConfigs(opts map[string]string) {
|
func loadConfigs(opts map[string]string) {
|
||||||
paths := cli.FindParentPaths(".jira.d/config.yml")
|
paths := cli.FindParentPaths(".jira.d/config.yml")
|
||||||
// prepend
|
// prepend
|
||||||
paths = append([]string{"/etc/jira-cli.yml"}, paths...)
|
paths = append([]string{"/etc/jira-cli.yml"}, paths...)
|
||||||
|
|
||||||
for _, file := range paths {
|
for _, file := range paths {
|
||||||
parseYaml(file, opts)
|
if stat, err := os.Stat(file); err == nil {
|
||||||
|
// check to see if config file is exectuable
|
||||||
|
if stat.Mode()&0111 == 0 {
|
||||||
|
parseYaml(file, opts)
|
||||||
|
} else {
|
||||||
|
log.Debug("Found Executable Config file: %s", file)
|
||||||
|
// it is executable, so run it and try to parse the output
|
||||||
|
cmd := exec.Command(file)
|
||||||
|
stdout := bytes.NewBufferString("")
|
||||||
|
cmd.Stdout = stdout
|
||||||
|
cmd.Stderr = bytes.NewBufferString("")
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
log.Error("%s is exectuable, but it failed to execute: %s\n%s", file, err, cmd.Stderr)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
yaml.Unmarshal(stdout.Bytes(), &opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user