mirror of
https://github.com/Threnklyn/jira.git
synced 2026-05-28 08:48:29 +02:00
Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b5417ef585 | |||
| c5af781c41 | |||
| f2c4df9b3e | |||
| 1dde7e06e6 | |||
| 7bc1897792 | |||
| 37aab3580b | |||
| 42990d8ca0 | |||
| 8e662462da | |||
| ad7bb2b724 | |||
| a8cce44178 | |||
| 35955a7a93 | |||
| f349e25bb9 | |||
| a92a93b282 | |||
| 8645ef11f1 | |||
| e042a3e62a | |||
| a738d1515e | |||
| d4f15ae5c6 | |||
| bc70b43868 | |||
| e24b431b7a | |||
| 101bc1da68 | |||
| 63e035c5c1 | |||
| 40bafc9b66 | |||
| 5d863ffed4 | |||
| 577394b0bd | |||
| c1a7e1bbdb | |||
| f904f3c089 | |||
| e35e518368 | |||
| 159d142f37 | |||
| df84d47552 | |||
| fa4ce5647d | |||
| 713d300a57 | |||
| c8ae7fc685 | |||
| 80322b648e | |||
| f2076a0977 | |||
| 886adb5db2 | |||
| 3bdbdbdaff | |||
| 544b923fab | |||
| 13a69e6f44 | |||
| fae9f94817 | |||
| 7d90672736 | |||
| 20faa959aa | |||
| aaff47d606 | |||
| 7bfb2946d4 | |||
| 9884281079 | |||
| 03fce96eb5 | |||
| 8f9e6f7d85 | |||
| 65c0240d34 | |||
| ccec749a0b | |||
| 6a9901f171 | |||
| 868764ac86 | |||
| f1e7514a00 | |||
| 92d10c3498 | |||
| a9c6b865b6 | |||
| 9cc55a13c1 | |||
| b116764d3e | |||
| a4aa52fb58 | |||
| 582f37866f | |||
| ab7f194647 | |||
| e8ba1d6053 | |||
| 4dc23487ba | |||
| 8f44cc5de7 | |||
| a989198630 | |||
| 7f85842df3 | |||
| f1921454d6 | |||
| d7f6638f2a | |||
| ae285b5a55 | |||
| 5d8dc9f9d8 | |||
| ade9f5f1f5 | |||
| a990fdf7bb | |||
| 0f217cefb5 | |||
| 38b3e90198 | |||
| 80b6f5a198 | |||
| 794654dcd7 | |||
| a36bf387fa | |||
| b134e7c6f7 | |||
| b8889c656d | |||
| 8949c12354 | |||
| 880b45c90b | |||
| 6c508f9ce1 | |||
| cf14b5af97 | |||
| 43a2753451 | |||
| 66596f3aa5 | |||
| 3a68ccdd3f | |||
| 91c57d496c | |||
| 87ab7291e1 | |||
| b72040bfd4 | |||
| 2dcc840a06 | |||
| 10c4aef671 | |||
| b5643e545b | |||
| fc00743095 | |||
| 6a3e2aa4d4 | |||
| abc3953448 | |||
| 52eb7f4ed7 | |||
| a5c7a133c0 | |||
| f42d0b6366 | |||
| 8040746bcf | |||
| 90a8ee7c33 |
@@ -1,5 +1,101 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.0.20 - 2016-01-21
|
||||||
|
|
||||||
|
* [issue [#28](https://github.com/Netflix-Skunkworks/go-jira/issues/28)] check to make sure we got back issuetypes for create metadata [Cory Bennett] [[ee0e780](https://github.com/Netflix-Skunkworks/go-jira/commit/ee0e780)]
|
||||||
|
* Add insecure option for TLS endpoints [Brian Lalor] [[6a88bb9](https://github.com/Netflix-Skunkworks/go-jira/commit/6a88bb9)]
|
||||||
|
* Correct naming of parameter: set/add/remove are actions. [Mike Pountney] [[303784f](https://github.com/Netflix-Skunkworks/go-jira/commit/303784f)]
|
||||||
|
* Tweak CmdLabels args so that magic happens with CLI [Mike Pountney] [[40a7c65](https://github.com/Netflix-Skunkworks/go-jira/commit/40a7c65)]
|
||||||
|
* Expose ViewTicket as per FindIssues [Mike Pountney] [[8977f3d](https://github.com/Netflix-Skunkworks/go-jira/commit/8977f3d)]
|
||||||
|
* Add exposed versions of getTemplate and runTemplate [Mike Pountney] [[da6cbd5](https://github.com/Netflix-Skunkworks/go-jira/commit/da6cbd5)]
|
||||||
|
* Add 'labels' command to set/add/remove labels [Mike Pountney] [[230b52d](https://github.com/Netflix-Skunkworks/go-jira/commit/230b52d)]
|
||||||
|
* Add a 'join' func to the template engine [Mike Pountney] [[a7820fe](https://github.com/Netflix-Skunkworks/go-jira/commit/a7820fe)]
|
||||||
|
* make "jira" golang package, move code from jira/cli to root, move jira/main.go to main/main.go [Cory Bennett] [[7268b9e](https://github.com/Netflix-Skunkworks/go-jira/commit/7268b9e)]
|
||||||
|
|
||||||
|
## 0.0.19 - 2015-12-09
|
||||||
|
|
||||||
|
* fix jira trans TRANS ISSUE (case sensitivity issue), also go fmt [Cory Bennett] [[3c30f3b](https://github.com/Netflix-Skunkworks/go-jira/commit/3c30f3b)]
|
||||||
|
|
||||||
|
## 0.0.18 - 2015-12-03
|
||||||
|
|
||||||
|
* need to default "quiet" to false [Cory Bennett] [[4f4a89b](https://github.com/Netflix-Skunkworks/go-jira/commit/4f4a89b)]
|
||||||
|
|
||||||
|
## 0.0.17 - 2015-12-03
|
||||||
|
|
||||||
|
* add --quiet command to not print the OK .. add --saveFile option to print the issue/link to a file on create command [Cory Bennett] [[c9ac162](https://github.com/Netflix-Skunkworks/go-jira/commit/c9ac162)]
|
||||||
|
* fix overrides [Cory Bennett] [[eaddfe6](https://github.com/Netflix-Skunkworks/go-jira/commit/eaddfe6)]
|
||||||
|
* add abstract request wrapper to allow you to access/process random apis supported by Jira but not yet supported by go-jira [Cory Bennett] [[90ef56a](https://github.com/Netflix-Skunkworks/go-jira/commit/90ef56a)]
|
||||||
|
|
||||||
|
## 0.0.16 - 2015-11-23
|
||||||
|
|
||||||
|
* jira edit should not require one arguemnt (allow for --query) [Cory Bennett] [[a1eb4a1](https://github.com/Netflix-Skunkworks/go-jira/commit/a1eb4a1)]
|
||||||
|
|
||||||
|
## 0.0.15 - 2015-11-23
|
||||||
|
|
||||||
|
* [[#17](https://github.com/Netflix-Skunkworks/go-jira/issues/17)] print usage on missing arguments [Cory Bennett] [[fd2a2fe](https://github.com/Netflix-Skunkworks/go-jira/commit/fd2a2fe)]
|
||||||
|
|
||||||
|
## 0.0.14 - 2015-11-17
|
||||||
|
|
||||||
|
* s/enpoint/endpoint/g [Oliver Schrenk] [[c5d251d](https://github.com/Netflix-Skunkworks/go-jira/commit/c5d251d)]
|
||||||
|
* Implement dateFormat template command [Mike Pountney] [[68d3bae](https://github.com/Netflix-Skunkworks/go-jira/commit/68d3bae)]
|
||||||
|
* Add 'updated' field to default queryfields. [Mike Pountney] [[91e2475](https://github.com/Netflix-Skunkworks/go-jira/commit/91e2475)]
|
||||||
|
* Fix export-templates option (typo) [Mike Pountney] [[4d7fdb8](https://github.com/Netflix-Skunkworks/go-jira/commit/4d7fdb8)]
|
||||||
|
* when yaml element resolves to "\n" strip it out so we dont post it to jira [Cory Bennett] [[47ced2f](https://github.com/Netflix-Skunkworks/go-jira/commit/47ced2f)]
|
||||||
|
* print PUT/POST data when using --dryrun to help debug [Cory Bennett] [[618f245](https://github.com/Netflix-Skunkworks/go-jira/commit/618f245)]
|
||||||
|
|
||||||
|
## 0.0.13 - 2015-09-19
|
||||||
|
|
||||||
|
* replace dead/deprecated code.google.com/p/gopass with golang.org/x/crypto/ssh/terminal for reading password from stdin [Cory Bennett] [[909eb06](https://github.com/Netflix-Skunkworks/go-jira/commit/909eb06)]
|
||||||
|
|
||||||
|
## 0.0.12 - 2015-09-18
|
||||||
|
|
||||||
|
* fix exception from "jira create" [Cory Bennett] [[9348a4b](https://github.com/Netflix-Skunkworks/go-jira/commit/9348a4b)]
|
||||||
|
* add some debug messages to help diagnose login failures [Cory Bennett] [[1c08a7d](https://github.com/Netflix-Skunkworks/go-jira/commit/1c08a7d)]
|
||||||
|
|
||||||
|
## 0.0.11 - 2015-09-16
|
||||||
|
|
||||||
|
* add --version [Cory Bennett] [[8385ee2](https://github.com/Netflix-Skunkworks/go-jira/commit/8385ee2)]
|
||||||
|
* fix command line parser broken in 0.0.10 [Cory Bennett] [[15ae929](https://github.com/Netflix-Skunkworks/go-jira/commit/15ae929)]
|
||||||
|
|
||||||
|
## 0.0.10 - 2015-09-15
|
||||||
|
|
||||||
|
* allow for command aliasing in conjunction with executable config files. Issue #5 [Cory Bennett] [[23590d4](https://github.com/Netflix-Skunkworks/go-jira/commit/23590d4)]
|
||||||
|
* update usage [Cory Bennett] [[ef7a57e](https://github.com/Netflix-Skunkworks/go-jira/commit/ef7a57e)]
|
||||||
|
|
||||||
|
## 0.0.9 - 2015-09-15
|
||||||
|
|
||||||
|
* use forked yaml.v2 so as to not lose line terminations present in jira fields [Cory Bennett] [[f84e77f](https://github.com/Netflix-Skunkworks/go-jira/commit/f84e77f)]
|
||||||
|
* adding a |~ literal yaml syntax to just chomp a single newline (again to preserve existing formatting in jira fields) [Cory Bennett] [[f84e77f](https://github.com/Netflix-Skunkworks/go-jira/commit/f84e77f)]
|
||||||
|
* for indent/comment allow for unicode line termination characters that yaml will use for parsing [Cory Bennett] [[f84e77f](https://github.com/Netflix-Skunkworks/go-jira/commit/f84e77f)]
|
||||||
|
* fix "edit" default option, change how defaults are dealt with for filters [Cory Bennett] [[4265913](https://github.com/Netflix-Skunkworks/go-jira/commit/4265913)]
|
||||||
|
* for edit template add issue id as comment, also add "comments" as comment so you can review the comment details while editing [Cory Bennett] [[968a9df](https://github.com/Netflix-Skunkworks/go-jira/commit/968a9df)]
|
||||||
|
* add "comment" template filter to comment out multiline statements [Cory Bennett] [[d664868](https://github.com/Netflix-Skunkworks/go-jira/commit/d664868)]
|
||||||
|
* add getOpt wrappers to get options with defaults [Cory Bennett] [[c0070cf](https://github.com/Netflix-Skunkworks/go-jira/commit/c0070cf)]
|
||||||
|
* make --dryrun work [Cory Bennett] [[d229ac1](https://github.com/Netflix-Skunkworks/go-jira/commit/d229ac1)]
|
||||||
|
* refactor config/option loading so command options override settings in config files [Cory Bennett] [[d229ac1](https://github.com/Netflix-Skunkworks/go-jira/commit/d229ac1)]
|
||||||
|
* allow query options to be used on the "edit" command to iterate editing [Cory Bennett] [[d229ac1](https://github.com/Netflix-Skunkworks/go-jira/commit/d229ac1)]
|
||||||
|
* remove duplication for defaults [Cory Bennett] [[f8c8ddf](https://github.com/Netflix-Skunkworks/go-jira/commit/f8c8ddf)]
|
||||||
|
* use optigo for option parsing, drop docopt [Cory Bennett] [[7bbd571](https://github.com/Netflix-Skunkworks/go-jira/commit/7bbd571)]
|
||||||
|
* allow "abort: true" to be set while editing to cancel the edit operation [Cory Bennett] [[ea67a77](https://github.com/Netflix-Skunkworks/go-jira/commit/ea67a77)]
|
||||||
|
* if no changes are made on edit templates then abort edit [Cory Bennett] [[e69b65c](https://github.com/Netflix-Skunkworks/go-jira/commit/e69b65c)]
|
||||||
|
|
||||||
|
## 0.0.8 - 2015-07-31
|
||||||
|
|
||||||
|
* Add --max_results option for 'ls' [Mike Pountney] [[e06ff0c](https://github.com/Netflix-Skunkworks/go-jira/commit/e06ff0c)]
|
||||||
|
|
||||||
|
## 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
|
## 0.0.5 - 2015-02-21
|
||||||
|
|
||||||
* handle editor having arguments [Cory Bennett] [[7186fb3](https://github.com/Netflix-Skunkworks/go-jira/commit/7186fb3)]
|
* handle editor having arguments [Cory Bennett] [[7186fb3](https://github.com/Netflix-Skunkworks/go-jira/commit/7186fb3)]
|
||||||
|
|||||||
@@ -14,41 +14,74 @@ PLATFORMS= \
|
|||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
DIST=$(shell pwd)/dist
|
DIST=$(shell pwd)/dist
|
||||||
|
|
||||||
export GOPATH=$(shell pwd)
|
export GOPATH=$(shell pwd)
|
||||||
|
|
||||||
build:
|
GOBIN ?= $(shell pwd)
|
||||||
cd src/github.com/Netflix-Skunkworks/go-jira/jira; \
|
NAME=jira
|
||||||
go get -v
|
|
||||||
|
|
||||||
all:
|
BIN ?= $(GOBIN)/$(NAME)
|
||||||
mkdir -p $(DIST); \
|
|
||||||
cd src/github.com/Netflix-Skunkworks/go-jira/jira; \
|
CURVER ?= $(shell [ -d .git ] && git describe --abbrev=0 --tags || grep ^\#\# CHANGELOG.md | awk '{print $$2; exit}')
|
||||||
go get -d; \
|
LDFLAGS:=-X jira.VERSION=$(patsubst v%,%,$(CURVER)) -w
|
||||||
|
|
||||||
|
# use make DEBUG=1 and you can get a debuggable golang binary
|
||||||
|
# see https://github.com/mailgun/godebug
|
||||||
|
ifneq ($(DEBUG),)
|
||||||
|
GOBUILD=go get -v github.com/mailgun/godebug && ./bin/godebug build
|
||||||
|
else
|
||||||
|
GOBUILD=go build -v -ldflags "$(LDFLAGS) -s"
|
||||||
|
endif
|
||||||
|
|
||||||
|
build: src/github.com/Netflix-Skunkworks/go-jira
|
||||||
|
$(GOBUILD) -o $(BIN) main/main.go
|
||||||
|
|
||||||
|
src/%:
|
||||||
|
mkdir -p $(@D)
|
||||||
|
test -L $@ || ln -sf ../../.. $@
|
||||||
|
go get -v $* $*/main
|
||||||
|
|
||||||
|
cross-setup:
|
||||||
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; \
|
cd $(GOROOT)/src && sudo GOROOT_BOOTSTRAP=$(GOROOT) GOOS=$${p/-*/} GOARCH=$${p/*-/} bash ./make.bash --no-clean; \
|
||||||
done
|
done
|
||||||
|
|
||||||
fmt:
|
all:
|
||||||
gofmt -s -w jira
|
rm -rf $(DIST); \
|
||||||
|
mkdir -p $(DIST); \
|
||||||
|
for p in $(PLATFORMS); do \
|
||||||
|
echo "Building for $$p"; \
|
||||||
|
${MAKE} build GOOS=$${p/-*/} GOARCH=$${p/*-/} BIN=$(DIST)/$(NAME)-$$p; \
|
||||||
|
done
|
||||||
|
|
||||||
CURVER := $(shell grep '\#\#' CHANGELOG.md | awk '{print $$2; exit}')
|
fmt:
|
||||||
NEWVER := $(shell awk -F'"' '/docopt.Parse/{print $$2}' jira/main.go)
|
gofmt -s -w main/*.go *.go
|
||||||
|
|
||||||
|
install:
|
||||||
|
${MAKE} GOBIN=~/bin build
|
||||||
|
|
||||||
|
NEWVER ?= $(shell echo $(CURVER) | awk -F. '{print $$1"."$$2"."$$3+1}')
|
||||||
TODAY := $(shell date +%Y-%m-%d)
|
TODAY := $(shell date +%Y-%m-%d)
|
||||||
|
|
||||||
changes:
|
changes:
|
||||||
@git log --pretty=format:"* %s [%cn] [%h]" --no-merges ^$(CURVER) HEAD jira | grep -v gofmt | grep -v "bump version"
|
@git log --pretty=format:"* %s [%cn] [%h]" --no-merges ^$(CURVER) HEAD main/*.go *.go | grep -vE 'gofmt|go fmt'
|
||||||
|
|
||||||
update-changelog:
|
update-changelog:
|
||||||
@echo "# Changelog" > CHANGELOG.md.new; \
|
@echo "# Changelog" > CHANGELOG.md.new; \
|
||||||
echo >> CHANGELOG.md.new; \
|
echo >> CHANGELOG.md.new; \
|
||||||
echo "## $(NEWVER) - $(TODAY)" >> CHANGELOG.md.new; \
|
echo "## $(NEWVER) - $(TODAY)" >> CHANGELOG.md.new; \
|
||||||
echo >> CHANGELOG.md.new; \
|
echo >> CHANGELOG.md.new; \
|
||||||
$(MAKE) changes | \
|
$(MAKE) --no-print-directory --silent changes | \
|
||||||
perl -pe 's{\[([a-f0-9]+)\]}{[[$$1](https://github.com/Netflix-Skunkworks/go-jira/commit/$$1)]}g' | \
|
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; \
|
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; \
|
tail -n +2 CHANGELOG.md >> CHANGELOG.md.new; \
|
||||||
mv CHANGELOG.md.new CHANGELOG.md
|
mv CHANGELOG.md.new CHANGELOG.md; \
|
||||||
|
git commit -m "Updated Changelog" CHANGELOG.md; \
|
||||||
|
git tag v$(NEWVER)
|
||||||
|
|
||||||
# https://github.com/Netflix-Skunkworks/go-jira/commit/d5330fd
|
version:
|
||||||
# [#1349](https://github.com/bower/bower/issues/1349)
|
@echo $(patsubst v%,%,$(CURVER))
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf pkg dist bin src ./$(NAME)
|
||||||
|
|||||||
@@ -171,55 +171,65 @@ hard-coded templates with `jira export-templates` which will write them to **~/.
|
|||||||
|
|
||||||
```
|
```
|
||||||
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 (ls|list) <Query Options>
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] view ISSUE
|
jira view ISSUE
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] edit ISSUE [--noedit] [-m COMMENT] [-o KEY=VAL]...
|
jira edit [--noedit] <Edit Options> [ISSUE | <Query Options>]
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] create [--noedit] [-p PROJECT] [-i ISSUETYPE] [-o KEY=VAL]...
|
jira create [--noedit] [-p PROJECT] <Create Options>
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] DUPLICATE dups ISSUE
|
jira DUPLICATE dups ISSUE
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] BLOCKER blocks ISSUE
|
jira BLOCKER blocks ISSUE
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] watch ISSUE [-w WATCHER]
|
jira watch ISSUE [-w WATCHER]
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] (trans|transition) TRANSITION ISSUE [-m COMMENT] [-o KEY=VAL] [--noedit]
|
jira (trans|transition) TRANSITION ISSUE [--noedit] <Edit Options>
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] ack ISSUE [-m COMMENT] [-o KEY=VAL] [--edit]
|
jira ack ISSUE [--edit] <Edit Options>
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] close ISSUE [-m COMMENT] [-o KEY=VAL] [--edit]
|
jira close ISSUE [--edit] <Edit Options>
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] resolve ISSUE [-m COMMENT] [-o KEY=VAL] [--edit]
|
jira resolve ISSUE [--edit] <Edit Options>
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] reopen ISSUE [-m COMMENT] [-o KEY=VAL] [--edit]
|
jira reopen ISSUE [--edit] <Edit Options>
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] start ISSUE [-m COMMENT] [-o KEY=VAL] [--edit]
|
jira start ISSUE [--edit] <Edit Options>
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] stop ISSUE [-m COMMENT] [-o KEY=VAL] [--edit]
|
jira stop ISSUE [--edit] <Edit Options>
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] comment ISSUE [-m COMMENT]
|
jira comment ISSUE [--noedit] <Edit Options>
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] take ISSUE
|
jira take ISSUE
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] (assign|give) ISSUE ASSIGNEE
|
jira (assign|give) ISSUE ASSIGNEE
|
||||||
jira [-v ...] [-u USER] [-e URI] [-t FILE] fields
|
jira fields
|
||||||
jira [-v ...] [-u USER] [-e URI] [-t FILE] issuelinktypes
|
jira issuelinktypes
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b][-t FILE] transmeta ISSUE
|
jira transmeta ISSUE
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] editmeta ISSUE
|
jira editmeta ISSUE
|
||||||
jira [-v ...] [-u USER] [-e URI] [-t FILE] issuetypes [-p PROJECT]
|
jira issuetypes [-p PROJECT]
|
||||||
jira [-v ...] [-u USER] [-e URI] [-t FILE] createmeta [-p PROJECT] [-i ISSUETYPE]
|
jira createmeta [-p PROJECT] [-i ISSUETYPE]
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] transitions ISSUE
|
jira transitions ISSUE
|
||||||
jira [-v ...] export-templates [-d DIR] [-t template]
|
jira export-templates [-d DIR] [-t template]
|
||||||
jira [-v ...] [-u USER] [-e URI] (b|browse) ISSUE
|
jira (b|browse) ISSUE
|
||||||
jira [-v ...] [-u USER] [-e URI] [-t FILE] login
|
jira login
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] ISSUE
|
jira ISSUE
|
||||||
|
|
||||||
General Options:
|
General Options:
|
||||||
|
-b --browse Open your browser to the Jira issue
|
||||||
-e --endpoint=URI URI to use for jira
|
-e --endpoint=URI URI to use for jira
|
||||||
-h --help Show this usage
|
-h --help Show this usage
|
||||||
-t --template=FILE Template file to use for output/editing
|
-t --template=FILE Template file to use for output/editing
|
||||||
-u --user=USER Username to use for authenticaion (default: cbennett)
|
-u --user=USER Username to use for authenticaion (default: $USER)
|
||||||
-v --verbose Increase output logging
|
-v --verbose Increase output logging
|
||||||
--version Show this version
|
|
||||||
|
|
||||||
Command Options:
|
Query Options:
|
||||||
-a --assignee=USER Username assigned the issue
|
-a --assignee=USER Username assigned the 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)
|
-f --queryfields=FIELDS Fields that are used in "list" template: (default: summary,created,updated,priority,status,reporter,assignee)
|
||||||
-f --queryfields=FIELDS Fields that are used in "list" template: (default: summary,created,priority,status,reporter,assignee)
|
-i --issuetype=ISSUETYPE The Issue Type
|
||||||
-i --issuetype=ISSUETYPE Jira Issue Type (default: Bug)
|
-l --limit=VAL Maximum number of results to return in query (default: 500)
|
||||||
-m --comment=COMMENT Comment message for transition
|
|
||||||
-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
|
||||||
-w --watcher=USER Watcher to add to issue (default: cbennett)
|
-s --sort=ORDER For list operations, sort issues (default: priority asc, created)
|
||||||
|
-w --watcher=USER Watcher to add to issue (default: $USER)
|
||||||
or Watcher to search for
|
or Watcher to search for
|
||||||
|
|
||||||
|
Edit Options:
|
||||||
|
-m --comment=COMMENT Comment message for transition
|
||||||
|
-o --override=KEY=VAL Set custom key/value pairs
|
||||||
|
|
||||||
|
Create Options:
|
||||||
|
-i --issuetype=ISSUETYPE Jira Issue Type (default: Bug)
|
||||||
|
-m --comment=COMMENT Comment message for transition
|
||||||
|
-o --override=KEY=VAL Set custom key/value pairs
|
||||||
|
|
||||||
|
Command Options:
|
||||||
|
-d --directory=DIR Directory to export templates to (default: $HOME/.jira.d/templates)
|
||||||
```
|
```
|
||||||
|
|||||||
+159
-15
@@ -1,12 +1,13 @@
|
|||||||
package cli
|
package jira
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/kballard/go-shellquote"
|
"github.com/kballard/go-shellquote"
|
||||||
"github.com/op/go-logging"
|
"github.com/op/go-logging"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/coryb/yaml.v2"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
@@ -18,30 +19,44 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var log = logging.MustGetLogger("jira.cli")
|
var (
|
||||||
|
log = logging.MustGetLogger("jira")
|
||||||
|
VERSION string
|
||||||
|
)
|
||||||
|
|
||||||
type Cli struct {
|
type Cli struct {
|
||||||
endpoint *url.URL
|
endpoint *url.URL
|
||||||
opts map[string]string
|
opts map[string]interface{}
|
||||||
cookieFile string
|
cookieFile string
|
||||||
ua *http.Client
|
ua *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(opts map[string]string) *Cli {
|
func New(opts map[string]interface{}) *Cli {
|
||||||
homedir := os.Getenv("HOME")
|
homedir := os.Getenv("HOME")
|
||||||
cookieJar, _ := cookiejar.New(nil)
|
cookieJar, _ := cookiejar.New(nil)
|
||||||
endpoint, _ := opts["endpoint"]
|
endpoint, _ := opts["endpoint"].(string)
|
||||||
url, _ := url.Parse(strings.TrimRight(endpoint, "/"))
|
url, _ := url.Parse(strings.TrimRight(endpoint, "/"))
|
||||||
|
|
||||||
if project, ok := opts["project"]; ok {
|
transport := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if project, ok := opts["project"].(string); ok {
|
||||||
opts["project"] = strings.ToUpper(project)
|
opts["project"] = strings.ToUpper(project)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if insecureSkipVerify, ok := opts["insecure"].(bool); ok {
|
||||||
|
transport.TLSClientConfig.InsecureSkipVerify = insecureSkipVerify
|
||||||
|
}
|
||||||
|
|
||||||
cli := &Cli{
|
cli := &Cli{
|
||||||
endpoint: url,
|
endpoint: url,
|
||||||
opts: opts,
|
opts: opts,
|
||||||
cookieFile: fmt.Sprintf("%s/.jira.d/cookies.js", homedir),
|
cookieFile: fmt.Sprintf("%s/.jira.d/cookies.js", homedir),
|
||||||
ua: &http.Client{Jar: cookieJar},
|
ua: &http.Client{
|
||||||
|
Jar: cookieJar,
|
||||||
|
Transport: transport,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.ua.Jar.SetCookies(url, cli.loadCookies())
|
cli.ua.Jar.SetCookies(url, cli.loadCookies())
|
||||||
@@ -174,8 +189,12 @@ func (c *Cli) makeRequest(req *http.Request) (resp *http.Response, err error) {
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cli) GetTemplate(name string) string {
|
||||||
|
return c.getTemplate(name)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Cli) getTemplate(name string) string {
|
func (c *Cli) getTemplate(name string) string {
|
||||||
if override, ok := c.opts["template"]; ok {
|
if override, ok := c.opts["template"].(string); ok {
|
||||||
if _, err := os.Stat(override); err == nil {
|
if _, err := os.Stat(override); err == nil {
|
||||||
return readFile(override)
|
return readFile(override)
|
||||||
} else {
|
} else {
|
||||||
@@ -203,6 +222,12 @@ func (c *Cli) getTemplate(name string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NoChangesFound struct{}
|
||||||
|
|
||||||
|
func (f NoChangesFound) Error() string {
|
||||||
|
return "No changes found, aborting"
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData map[string]interface{}, templateProcessor func(string) error) error {
|
func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData map[string]interface{}, templateProcessor func(string) error) error {
|
||||||
|
|
||||||
tmpdir := fmt.Sprintf("%s/.jira.d/tmp", os.Getenv("HOME"))
|
tmpdir := fmt.Sprintf("%s/.jira.d/tmp", os.Getenv("HOME"))
|
||||||
@@ -222,6 +247,9 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m
|
|||||||
log.Error("Failed to rename %s to %s: %s", fh.Name(), fmt.Sprintf("%s.yml", fh.Name()), err)
|
log.Error("Failed to rename %s to %s: %s", fh.Name(), fmt.Sprintf("%s.yml", fh.Name()), err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
|
os.Remove(tmpFileName)
|
||||||
|
}()
|
||||||
|
|
||||||
err = runTemplate(template, templateData, fh)
|
err = runTemplate(template, templateData, fh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -230,7 +258,7 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m
|
|||||||
|
|
||||||
fh.Close()
|
fh.Close()
|
||||||
|
|
||||||
editor, ok := c.opts["editor"]
|
editor, ok := c.opts["editor"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
editor = os.Getenv("JIRA_EDITOR")
|
editor = os.Getenv("JIRA_EDITOR")
|
||||||
if editor == "" {
|
if editor == "" {
|
||||||
@@ -241,10 +269,13 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
editing := true
|
editing := c.getOptBool("edit", true)
|
||||||
if val, ok := c.opts["edit"]; ok && val == "false" {
|
|
||||||
editing = false
|
tmpFileNameOrig := fmt.Sprintf("%s.orig", tmpFileName)
|
||||||
}
|
copyFile(tmpFileName, tmpFileNameOrig)
|
||||||
|
defer func() {
|
||||||
|
os.Remove(tmpFileNameOrig)
|
||||||
|
}()
|
||||||
|
|
||||||
for true {
|
for true {
|
||||||
if editing {
|
if editing {
|
||||||
@@ -260,6 +291,12 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diff := exec.Command("diff", "-q", tmpFileNameOrig, tmpFileName)
|
||||||
|
// if err == nil then diff found no changes
|
||||||
|
if err := diff.Run(); err == nil {
|
||||||
|
return NoChangesFound{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
edited := make(map[string]interface{})
|
edited := make(map[string]interface{})
|
||||||
@@ -285,6 +322,14 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m
|
|||||||
edited = fixed.(map[string]interface{})
|
edited = fixed.(map[string]interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if you want to abort editing a jira issue then
|
||||||
|
// you can add the "abort: true" flag to the document
|
||||||
|
// and we will abort now
|
||||||
|
if val, ok := edited["abort"].(bool); ok && val {
|
||||||
|
log.Info("abort flag found in template, quiting")
|
||||||
|
return fmt.Errorf("abort flag found in template, quiting")
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -318,7 +363,7 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) Browse(issue string) error {
|
func (c *Cli) Browse(issue string) error {
|
||||||
if val, ok := c.opts["browse"]; ok && val == "true" {
|
if val, ok := c.opts["browse"].(bool); ok && val {
|
||||||
if runtime.GOOS == "darwin" {
|
if runtime.GOOS == "darwin" {
|
||||||
return exec.Command("open", fmt.Sprintf("%s/browse/%s", c.endpoint, issue)).Run()
|
return exec.Command("open", fmt.Sprintf("%s/browse/%s", c.endpoint, issue)).Run()
|
||||||
} else if runtime.GOOS == "linux" {
|
} else if runtime.GOOS == "linux" {
|
||||||
@@ -327,3 +372,102 @@ func (c *Cli) Browse(issue string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cli) SaveData(data interface{}) error {
|
||||||
|
if val, ok := c.opts["saveFile"].(string); ok && val != "" {
|
||||||
|
yamlWrite(val, data)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cli) ViewIssue(issue string) (interface{}, error) {
|
||||||
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s", c.endpoint, issue)
|
||||||
|
data, err := responseToJson(c.get(uri))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cli) FindIssues() (interface{}, error) {
|
||||||
|
var query string
|
||||||
|
var ok bool
|
||||||
|
// project = BAKERY and status not in (Resolved, Closed)
|
||||||
|
if query, ok = c.opts["query"].(string); !ok {
|
||||||
|
qbuff := bytes.NewBufferString("resolution = unresolved")
|
||||||
|
if project, ok := c.opts["project"]; !ok {
|
||||||
|
err := fmt.Errorf("Missing required arguments, either 'query' or 'project' are required")
|
||||||
|
log.Error("%s", err)
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
qbuff.WriteString(fmt.Sprintf(" AND project = '%s'", project))
|
||||||
|
}
|
||||||
|
|
||||||
|
if component, ok := c.opts["component"]; ok {
|
||||||
|
qbuff.WriteString(fmt.Sprintf(" AND component = '%s'", component))
|
||||||
|
}
|
||||||
|
|
||||||
|
if assignee, ok := c.opts["assignee"]; ok {
|
||||||
|
qbuff.WriteString(fmt.Sprintf(" AND assignee = '%s'", assignee))
|
||||||
|
}
|
||||||
|
|
||||||
|
if issuetype, ok := c.opts["issuetype"]; ok {
|
||||||
|
qbuff.WriteString(fmt.Sprintf(" AND issuetype = '%s'", issuetype))
|
||||||
|
}
|
||||||
|
|
||||||
|
if watcher, ok := c.opts["watcher"]; ok {
|
||||||
|
qbuff.WriteString(fmt.Sprintf(" AND watcher = '%s'", watcher))
|
||||||
|
}
|
||||||
|
|
||||||
|
if reporter, ok := c.opts["reporter"]; ok {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := make([]string, 0)
|
||||||
|
if qf, ok := c.opts["queryfields"].(string); ok {
|
||||||
|
fields = strings.Split(qf, ",")
|
||||||
|
} else {
|
||||||
|
fields = append(fields, "summary")
|
||||||
|
}
|
||||||
|
|
||||||
|
json, err := jsonEncode(map[string]interface{}{
|
||||||
|
"jql": query,
|
||||||
|
"startAt": "0",
|
||||||
|
"maxResults": c.opts["max_results"],
|
||||||
|
"fields": fields,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("%s/rest/api/2/search", c.endpoint)
|
||||||
|
if data, err := responseToJson(c.post(uri, json)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cli) getOptString(optName string, dflt string) string {
|
||||||
|
if val, ok := c.opts[optName].(string); ok {
|
||||||
|
return val
|
||||||
|
} else {
|
||||||
|
return dflt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cli) getOptBool(optName string, dflt bool) bool {
|
||||||
|
if val, ok := c.opts[optName].(bool); ok {
|
||||||
|
return val
|
||||||
|
} else {
|
||||||
|
return dflt
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
package cli
|
package jira
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"code.google.com/p/gopass"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
// "github.com/kr/pretty"
|
// "github.com/kr/pretty"
|
||||||
@@ -14,15 +15,19 @@ func (c *Cli) CmdLogin() error {
|
|||||||
uri := fmt.Sprintf("%s/rest/auth/1/session", c.endpoint)
|
uri := fmt.Sprintf("%s/rest/auth/1/session", c.endpoint)
|
||||||
for true {
|
for true {
|
||||||
req, _ := http.NewRequest("GET", uri, nil)
|
req, _ := http.NewRequest("GET", uri, nil)
|
||||||
user, _ := c.opts["user"]
|
user, _ := c.opts["user"].(string)
|
||||||
|
|
||||||
prompt := fmt.Sprintf("Enter Password for %s: ", user)
|
fmt.Printf("Enter Password for %s: ", user)
|
||||||
passwd, _ := gopass.GetPass(prompt)
|
pwbytes, _ := terminal.ReadPassword(int(os.Stdin.Fd()))
|
||||||
|
passwd := string(pwbytes)
|
||||||
|
|
||||||
req.SetBasicAuth(user, passwd)
|
req.SetBasicAuth(user, passwd)
|
||||||
|
log.Info("%s %s", req.Method, req.URL.String())
|
||||||
if resp, err := c.makeRequest(req); err != nil {
|
if resp, err := c.makeRequest(req); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
|
out, _ := httputil.DumpResponse(resp, true)
|
||||||
|
log.Debug("%s", out)
|
||||||
if resp.StatusCode == 403 {
|
if resp.StatusCode == 403 {
|
||||||
// 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
|
||||||
@@ -65,77 +70,20 @@ func (c *Cli) CmdFields() error {
|
|||||||
|
|
||||||
func (c *Cli) CmdList() error {
|
func (c *Cli) CmdList() error {
|
||||||
log.Debug("list called")
|
log.Debug("list called")
|
||||||
|
if data, err := c.FindIssues(); err != nil {
|
||||||
var query string
|
return err
|
||||||
var ok bool
|
|
||||||
// project = BAKERY and status not in (Resolved, Closed)
|
|
||||||
if query, ok = c.opts["query"]; !ok {
|
|
||||||
qbuff := bytes.NewBufferString("resolution = unresolved")
|
|
||||||
if project, ok := c.opts["project"]; !ok {
|
|
||||||
err := fmt.Errorf("Missing required arguments, either 'query' or 'project' are required")
|
|
||||||
log.Error("%s", err)
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
qbuff.WriteString(fmt.Sprintf(" AND project = '%s'", project))
|
|
||||||
}
|
|
||||||
|
|
||||||
if component, ok := c.opts["component"]; ok {
|
|
||||||
qbuff.WriteString(fmt.Sprintf(" AND component = '%s'", component))
|
|
||||||
}
|
|
||||||
|
|
||||||
if assignee, ok := c.opts["assignee"]; ok {
|
|
||||||
qbuff.WriteString(fmt.Sprintf(" AND assignee = '%s'", assignee))
|
|
||||||
}
|
|
||||||
|
|
||||||
if issuetype, ok := c.opts["issuetype"]; ok {
|
|
||||||
qbuff.WriteString(fmt.Sprintf(" AND issuetype = '%s'", issuetype))
|
|
||||||
}
|
|
||||||
|
|
||||||
if watcher, ok := c.opts["watcher"]; ok {
|
|
||||||
qbuff.WriteString(fmt.Sprintf(" AND watcher = '%s'", watcher))
|
|
||||||
}
|
|
||||||
|
|
||||||
if reporter, ok := c.opts["reporter"]; ok {
|
|
||||||
qbuff.WriteString(fmt.Sprintf(" AND reporter = '%s'", reporter))
|
|
||||||
}
|
|
||||||
query = qbuff.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := make([]string, 0)
|
|
||||||
if qf, ok := c.opts["queryfields"]; ok {
|
|
||||||
fields = strings.Split(qf, ",")
|
|
||||||
} else {
|
} else {
|
||||||
fields = append(fields, "summary")
|
return runTemplate(c.getTemplate("list"), data, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
json, err := jsonEncode(map[string]interface{}{
|
|
||||||
"jql": query,
|
|
||||||
"startAt": "0",
|
|
||||||
"maxResults": "500",
|
|
||||||
"fields": fields,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/search", c.endpoint)
|
|
||||||
data, err := responseToJson(c.post(uri, json))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return runTemplate(c.getTemplate("list"), data, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdView(issue string) error {
|
func (c *Cli) CmdView(issue string) error {
|
||||||
log.Debug("view called")
|
log.Debug("view called")
|
||||||
c.Browse(issue)
|
c.Browse(issue)
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s", c.endpoint, issue)
|
data, err := c.ViewIssue(issue)
|
||||||
data, err := responseToJson(c.get(uri))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return runTemplate(c.getTemplate("view"), data, nil)
|
return runTemplate(c.getTemplate("view"), data, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +112,11 @@ func (c *Cli) CmdEdit(issue string) error {
|
|||||||
fmt.Sprintf("%s-edit-", issue),
|
fmt.Sprintf("%s-edit-", issue),
|
||||||
issueData,
|
issueData,
|
||||||
func(json string) error {
|
func(json string) error {
|
||||||
|
if c.getOptBool("dryrun", false) {
|
||||||
|
log.Debug("PUT: %s", json)
|
||||||
|
log.Debug("Dryrun mode, skipping PUT")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
resp, err := c.put(uri, json)
|
resp, err := c.put(uri, json)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -171,7 +124,9 @@ func (c *Cli) CmdEdit(issue string) error {
|
|||||||
|
|
||||||
if resp.StatusCode == 204 {
|
if resp.StatusCode == 204 {
|
||||||
c.Browse(issueData["key"].(string))
|
c.Browse(issueData["key"].(string))
|
||||||
fmt.Printf("OK %s %s/browse/%s\n", issueData["key"], c.endpoint, issueData["key"])
|
if !c.opts["quiet"].(bool) {
|
||||||
|
fmt.Printf("OK %s %s/browse/%s\n", issueData["key"], c.endpoint, issueData["key"])
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
@@ -208,7 +163,8 @@ func (c *Cli) CmdTransitionMeta(issue string) error {
|
|||||||
return runTemplate(c.getTemplate("transmeta"), data, nil)
|
return runTemplate(c.getTemplate("transmeta"), data, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdIssueTypes(project string) error {
|
func (c *Cli) CmdIssueTypes() error {
|
||||||
|
project := c.opts["project"].(string)
|
||||||
log.Debug("issueTypes called")
|
log.Debug("issueTypes called")
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/createmeta?projectKeys=%s", c.endpoint, project)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/createmeta?projectKeys=%s", c.endpoint, project)
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
@@ -219,7 +175,10 @@ func (c *Cli) CmdIssueTypes(project string) error {
|
|||||||
return runTemplate(c.getTemplate("issuetypes"), data, nil)
|
return runTemplate(c.getTemplate("issuetypes"), data, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdCreateMeta(project string, issuetype string) error {
|
func (c *Cli) CmdCreateMeta() error {
|
||||||
|
project := c.opts["project"].(string)
|
||||||
|
issuetype := c.getOptString("issuetype", "Bug")
|
||||||
|
|
||||||
log.Debug("createMeta called")
|
log.Debug("createMeta called")
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/createmeta?projectKeys=%s&issuetypeNames=%s&expand=projects.issuetypes.fields", c.endpoint, project, issuetype)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/createmeta?projectKeys=%s&issuetypeNames=%s&expand=projects.issuetypes.fields", c.endpoint, project, issuetype)
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
@@ -252,7 +211,9 @@ func (c *Cli) CmdTransitions(issue string) error {
|
|||||||
return runTemplate(c.getTemplate("transitions"), data, nil)
|
return runTemplate(c.getTemplate("transitions"), data, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdCreate(project string, issuetype string) error {
|
func (c *Cli) CmdCreate() error {
|
||||||
|
project := c.opts["project"].(string)
|
||||||
|
issuetype := c.getOptString("issuetype", "Bug")
|
||||||
log.Debug("create called")
|
log.Debug("create called")
|
||||||
|
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/createmeta?projectKeys=%s&issuetypeNames=%s&expand=projects.issuetypes.fields", c.endpoint, project, issuetype)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/createmeta?projectKeys=%s&issuetypeNames=%s&expand=projects.issuetypes.fields", c.endpoint, project, issuetype)
|
||||||
@@ -263,7 +224,7 @@ func (c *Cli) CmdCreate(project string, issuetype string) error {
|
|||||||
|
|
||||||
issueData := make(map[string]interface{})
|
issueData := make(map[string]interface{})
|
||||||
issueData["overrides"] = c.opts
|
issueData["overrides"] = c.opts
|
||||||
issueData["overrides"].(map[string]string)["issuetype"] = issuetype
|
issueData["overrides"].(map[string]interface{})["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 {
|
if len(val.([]interface{})) == 0 {
|
||||||
@@ -272,6 +233,11 @@ func (c *Cli) CmdCreate(project string, issuetype string) error {
|
|||||||
return 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 {
|
||||||
|
if len(val.([]interface{})) == 0 {
|
||||||
|
err = fmt.Errorf("Project '%s' does not support issuetype '%s'. Unable to create issue.", project, issuetype)
|
||||||
|
log.Error("%s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
issueData["meta"] = val.([]interface{})[0]
|
issueData["meta"] = val.([]interface{})[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,6 +250,11 @@ func (c *Cli) CmdCreate(project string, issuetype string) error {
|
|||||||
func(json string) error {
|
func(json string) error {
|
||||||
log.Debug("JSON: %s", json)
|
log.Debug("JSON: %s", json)
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue", c.endpoint)
|
uri := fmt.Sprintf("%s/rest/api/2/issue", c.endpoint)
|
||||||
|
if c.getOptBool("dryrun", false) {
|
||||||
|
log.Debug("POST: %s", json)
|
||||||
|
log.Debug("Dryrun mode, skipping POST")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
resp, err := c.post(uri, json)
|
resp, err := c.post(uri, json)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -294,10 +265,16 @@ func (c *Cli) CmdCreate(project string, issuetype string) error {
|
|||||||
if json, err := responseToJson(resp, nil); err != nil {
|
if json, err := responseToJson(resp, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
key := json.(map[string]interface{})["key"]
|
key := json.(map[string]interface{})["key"].(string)
|
||||||
c.Browse(key.(string))
|
link := fmt.Sprintf("%s/browse/%s", c.endpoint, key)
|
||||||
fmt.Printf("OK %s %s/browse/%s\n", key, c.endpoint, key)
|
c.Browse(key)
|
||||||
|
c.SaveData(map[string]string{
|
||||||
|
"issue": key,
|
||||||
|
"link": link,
|
||||||
|
})
|
||||||
|
if !c.opts["quiet"].(bool) {
|
||||||
|
fmt.Printf("OK %s %s\n", key, link)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
@@ -341,13 +318,20 @@ func (c *Cli) CmdBlocks(blocker string, issue string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issueLink", c.endpoint)
|
uri := fmt.Sprintf("%s/rest/api/2/issueLink", c.endpoint)
|
||||||
|
if c.getOptBool("dryrun", false) {
|
||||||
|
log.Debug("POST: %s", json)
|
||||||
|
log.Debug("Dryrun mode, skipping POST")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
resp, err := c.post(uri, json)
|
resp, err := c.post(uri, json)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if resp.StatusCode == 201 {
|
if resp.StatusCode == 201 {
|
||||||
c.Browse(issue)
|
c.Browse(issue)
|
||||||
fmt.Printf("OK %s %s/browse/%s\n", issue, c.endpoint, issue)
|
if !c.opts["quiet"].(bool) {
|
||||||
|
fmt.Printf("OK %s %s/browse/%s\n", issue, c.endpoint, issue)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
resp.Write(logBuffer)
|
resp.Write(logBuffer)
|
||||||
@@ -377,13 +361,20 @@ func (c *Cli) CmdDups(duplicate string, issue string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issueLink", c.endpoint)
|
uri := fmt.Sprintf("%s/rest/api/2/issueLink", c.endpoint)
|
||||||
|
if c.getOptBool("dryrun", false) {
|
||||||
|
log.Debug("POST: %s", json)
|
||||||
|
log.Debug("Dryrun mode, skipping POST")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
resp, err := c.post(uri, json)
|
resp, err := c.post(uri, json)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if resp.StatusCode == 201 {
|
if resp.StatusCode == 201 {
|
||||||
c.Browse(issue)
|
c.Browse(issue)
|
||||||
fmt.Printf("OK %s %s/browse/%s\n", issue, c.endpoint, issue)
|
if !c.opts["quiet"].(bool) {
|
||||||
|
fmt.Printf("OK %s %s/browse/%s\n", issue, c.endpoint, issue)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
resp.Write(logBuffer)
|
resp.Write(logBuffer)
|
||||||
@@ -394,7 +385,8 @@ func (c *Cli) CmdDups(duplicate string, issue string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdWatch(issue string, watcher string) error {
|
func (c *Cli) CmdWatch(issue string) error {
|
||||||
|
watcher := c.getOptString("watcher", c.opts["user"].(string))
|
||||||
log.Debug("watch called")
|
log.Debug("watch called")
|
||||||
|
|
||||||
json, err := jsonEncode(watcher)
|
json, err := jsonEncode(watcher)
|
||||||
@@ -403,13 +395,20 @@ func (c *Cli) CmdWatch(issue string, watcher string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/watchers", c.endpoint, issue)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/watchers", c.endpoint, issue)
|
||||||
|
if c.getOptBool("dryrun", false) {
|
||||||
|
log.Debug("POST: %s", json)
|
||||||
|
log.Debug("Dryrun mode, skipping POST")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
resp, err := c.post(uri, json)
|
resp, err := c.post(uri, json)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if resp.StatusCode == 204 {
|
if resp.StatusCode == 204 {
|
||||||
c.Browse(issue)
|
c.Browse(issue)
|
||||||
fmt.Printf("OK %s %s/browse/%s\n", issue, c.endpoint, issue)
|
if !c.opts["quiet"].(bool) {
|
||||||
|
fmt.Printf("OK %s %s/browse/%s\n", issue, c.endpoint, issue)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
resp.Write(logBuffer)
|
resp.Write(logBuffer)
|
||||||
@@ -436,7 +435,7 @@ func (c *Cli) CmdTransition(issue string, trans string) error {
|
|||||||
name := transition.(map[string]interface{})["name"].(string)
|
name := transition.(map[string]interface{})["name"].(string)
|
||||||
id := transition.(map[string]interface{})["id"].(string)
|
id := transition.(map[string]interface{})["id"].(string)
|
||||||
found = append(found, name)
|
found = append(found, name)
|
||||||
if strings.Contains(strings.ToLower(name), trans) {
|
if strings.Contains(strings.ToLower(name), strings.ToLower(trans)) {
|
||||||
transName = name
|
transName = name
|
||||||
transId = id
|
transId = id
|
||||||
transMeta = transition.(map[string]interface{})
|
transMeta = transition.(map[string]interface{})
|
||||||
@@ -452,13 +451,20 @@ func (c *Cli) CmdTransition(issue string, trans string) error {
|
|||||||
log.Debug("POST: %s", json)
|
log.Debug("POST: %s", json)
|
||||||
// os.Exit(0)
|
// os.Exit(0)
|
||||||
uri = fmt.Sprintf("%s/rest/api/2/issue/%s/transitions", c.endpoint, issue)
|
uri = fmt.Sprintf("%s/rest/api/2/issue/%s/transitions", c.endpoint, issue)
|
||||||
|
if c.getOptBool("dryrun", false) {
|
||||||
|
log.Debug("POST: %s", json)
|
||||||
|
log.Debug("Dryrun mode, skipping POST")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
resp, err := c.post(uri, json)
|
resp, err := c.post(uri, json)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if resp.StatusCode == 204 {
|
if resp.StatusCode == 204 {
|
||||||
c.Browse(issue)
|
c.Browse(issue)
|
||||||
fmt.Printf("OK %s %s/browse/%s\n", issue, c.endpoint, issue)
|
if !c.opts["quiet"].(bool) {
|
||||||
|
fmt.Printf("OK %s %s/browse/%s\n", issue, c.endpoint, issue)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
resp.Write(logBuffer)
|
resp.Write(logBuffer)
|
||||||
@@ -497,6 +503,11 @@ func (c *Cli) CmdComment(issue string) error {
|
|||||||
handlePost := func(json string) error {
|
handlePost := func(json string) error {
|
||||||
log.Debug("JSON: %s", json)
|
log.Debug("JSON: %s", json)
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/comment", c.endpoint, issue)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/comment", c.endpoint, issue)
|
||||||
|
if c.getOptBool("dryrun", false) {
|
||||||
|
log.Debug("POST: %s", json)
|
||||||
|
log.Debug("Dryrun mode, skipping POST")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
resp, err := c.post(uri, json)
|
resp, err := c.post(uri, json)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -504,7 +515,9 @@ func (c *Cli) CmdComment(issue string) error {
|
|||||||
|
|
||||||
if resp.StatusCode == 201 {
|
if resp.StatusCode == 201 {
|
||||||
c.Browse(issue)
|
c.Browse(issue)
|
||||||
fmt.Printf("OK %s %s/browse/%s\n", issue, c.endpoint, issue)
|
if !c.opts["quiet"].(bool) {
|
||||||
|
fmt.Printf("OK %s %s/browse/%s\n", issue, c.endpoint, issue)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
@@ -534,6 +547,71 @@ func (c *Cli) CmdComment(issue string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cli) CmdLabels(action string, issue string, labels []string) error {
|
||||||
|
log.Debug("label called")
|
||||||
|
|
||||||
|
if action != "add" && action != "remove" && action != "set" {
|
||||||
|
return fmt.Errorf("action must be 'add', 'set' or 'remove': %q is invalid", action)
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePut := func(json string) error {
|
||||||
|
log.Debug("JSON: %s", json)
|
||||||
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s", c.endpoint, issue)
|
||||||
|
if c.getOptBool("dryrun", false) {
|
||||||
|
log.Debug("PUT: %s", json)
|
||||||
|
log.Debug("Dryrun mode, skipping POST")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
resp, err := c.put(uri, json)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode == 204 {
|
||||||
|
c.Browse(issue)
|
||||||
|
if !c.opts["quiet"].(bool) {
|
||||||
|
fmt.Printf("OK %s %s/browse/%s\n", issue, c.endpoint, issue)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
|
resp.Write(logBuffer)
|
||||||
|
err := fmt.Errorf("Unexpected Response From PUT")
|
||||||
|
log.Error("%s:\n%s", err, logBuffer)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var labels_json string
|
||||||
|
var err error
|
||||||
|
if action == "set" {
|
||||||
|
labelsActions := make([]map[string][]string, 1)
|
||||||
|
labelsActions[0] = map[string][]string{
|
||||||
|
"set": labels,
|
||||||
|
}
|
||||||
|
labels_json, err = jsonEncode(map[string]interface{}{
|
||||||
|
"labels": labelsActions,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
labelsActions := make([]map[string]string, len(labels))
|
||||||
|
for i, label := range labels {
|
||||||
|
labelActionMap := map[string]string{
|
||||||
|
action: label,
|
||||||
|
}
|
||||||
|
labelsActions[i] = labelActionMap
|
||||||
|
}
|
||||||
|
labels_json, err = jsonEncode(map[string]interface{}{
|
||||||
|
"labels": labelsActions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
json := fmt.Sprintf("{ \"update\": %s }", labels_json)
|
||||||
|
return handlePut(json)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdAssign(issue string, user string) error {
|
func (c *Cli) CmdAssign(issue string, user string) error {
|
||||||
log.Debug("assign called")
|
log.Debug("assign called")
|
||||||
|
|
||||||
@@ -545,13 +623,20 @@ func (c *Cli) CmdAssign(issue string, user string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/assignee", c.endpoint, issue)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/assignee", c.endpoint, issue)
|
||||||
|
if c.getOptBool("dryrun", false) {
|
||||||
|
log.Debug("PUT: %s", json)
|
||||||
|
log.Debug("Dryrun mode, skipping PUT")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
resp, err := c.put(uri, json)
|
resp, err := c.put(uri, json)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if resp.StatusCode == 204 {
|
if resp.StatusCode == 204 {
|
||||||
c.Browse(issue)
|
c.Browse(issue)
|
||||||
fmt.Printf("OK %s %s/browse/%s\n", issue, c.endpoint, issue)
|
if !c.opts["quiet"].(bool) {
|
||||||
|
fmt.Printf("OK %s %s/browse/%s\n", issue, c.endpoint, issue)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
resp.Write(logBuffer)
|
resp.Write(logBuffer)
|
||||||
@@ -563,7 +648,7 @@ func (c *Cli) CmdAssign(issue string, user string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdExportTemplates() error {
|
func (c *Cli) CmdExportTemplates() error {
|
||||||
dir := c.opts["directory"]
|
dir := c.opts["directory"].(string)
|
||||||
if err := mkdir(dir); err != nil {
|
if err := mkdir(dir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -588,3 +673,25 @@ func (c *Cli) CmdExportTemplates() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cli) CmdRequest(uri, content string) (err error) {
|
||||||
|
log.Debug("request called")
|
||||||
|
|
||||||
|
if !strings.HasPrefix(uri, "http") {
|
||||||
|
uri = fmt.Sprintf("%s%s", c.endpoint, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
method := strings.ToUpper(c.opts["method"].(string))
|
||||||
|
var data interface{}
|
||||||
|
if method == "GET" {
|
||||||
|
data, err = responseToJson(c.get(uri))
|
||||||
|
} else if method == "POST" {
|
||||||
|
data, err = responseToJson(c.post(uri, content))
|
||||||
|
} else if method == "PUT" {
|
||||||
|
data, err = responseToJson(c.put(uri, content))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return runTemplate(c.getTemplate("request"), data, nil)
|
||||||
|
}
|
||||||
-365
@@ -1,365 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"github.com/Netflix-Skunkworks/go-jira/jira/cli"
|
|
||||||
"github.com/docopt/docopt-go"
|
|
||||||
"github.com/op/go-logging"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log = logging.MustGetLogger("jira")
|
|
||||||
var format = "%{color}%{time:2006-01-02T15:04:05.000Z07:00} %{level:-5s} [%{shortfile}]%{color:reset} %{message}"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
user := os.Getenv("USER")
|
|
||||||
home := os.Getenv("HOME")
|
|
||||||
usage := fmt.Sprintf(`
|
|
||||||
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] [-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] 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] BLOCKER blocks ISSUE
|
|
||||||
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] [-o KEY=VAL] [--noedit]
|
|
||||||
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] [-o KEY=VAL] [--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] [-o KEY=VAL] [--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] [-o KEY=VAL] [--edit]
|
|
||||||
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] (assign|give) ISSUE ASSIGNEE
|
|
||||||
jira [-v ...] [-u USER] [-e URI] [-t FILE] fields
|
|
||||||
jira [-v ...] [-u USER] [-e URI] [-t FILE] issuelinktypes
|
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b][-t FILE] transmeta ISSUE
|
|
||||||
jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] editmeta ISSUE
|
|
||||||
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] [-b] [-t FILE] transitions ISSUE
|
|
||||||
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] [-b] [-t FILE] ISSUE
|
|
||||||
|
|
||||||
General Options:
|
|
||||||
-e --endpoint=URI URI to use for jira
|
|
||||||
-h --help Show this usage
|
|
||||||
-t --template=FILE Template file to use for output/editing
|
|
||||||
-u --user=USER Username to use for authenticaion (default: %s)
|
|
||||||
-v --verbose Increase output logging
|
|
||||||
--version Show this version
|
|
||||||
|
|
||||||
Command Options:
|
|
||||||
-a --assignee=USER Username assigned the issue
|
|
||||||
-b --browse Open your browser to the Jira issue
|
|
||||||
-c --component=COMPONENT Component to Search for
|
|
||||||
-d --directory=DIR Directory to export templates to (default: %s)
|
|
||||||
-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)
|
|
||||||
-m --comment=COMMENT Comment message for transition
|
|
||||||
-o --override=KEY:VAL Set custom key/value pairs
|
|
||||||
-p --project=PROJECT Project to Search for
|
|
||||||
-q --query=JQL Jira Query Language expression for the search
|
|
||||||
-r --reporter=USER Reporter to search for
|
|
||||||
-w --watcher=USER Watcher to add to issue (default: %s)
|
|
||||||
or Watcher to search for
|
|
||||||
`, user, fmt.Sprintf("%s/.jira.d/templates", home), user)
|
|
||||||
|
|
||||||
args, err := docopt.Parse(usage, nil, true, "0.0.5", false, false)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to parse options: %s", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
logBackend := logging.NewLogBackend(os.Stderr, "", 0)
|
|
||||||
logging.SetBackend(
|
|
||||||
logging.NewBackendFormatter(
|
|
||||||
logBackend,
|
|
||||||
logging.MustStringFormatter(format),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
logging.SetLevel(logging.NOTICE, "")
|
|
||||||
if verbose, ok := args["--verbose"]; ok {
|
|
||||||
if verbose.(int) > 1 {
|
|
||||||
logging.SetLevel(logging.DEBUG, "")
|
|
||||||
} else if verbose.(int) > 0 {
|
|
||||||
logging.SetLevel(logging.INFO, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Args: %v", args)
|
|
||||||
|
|
||||||
populateEnv(args)
|
|
||||||
|
|
||||||
opts := make(map[string]string)
|
|
||||||
loadConfigs(opts)
|
|
||||||
|
|
||||||
// strip the "--" off the command line options
|
|
||||||
// and populate the opts that we pass to the cli ctor
|
|
||||||
for key, val := range args {
|
|
||||||
if val != nil && strings.HasPrefix(key, "--") {
|
|
||||||
opt := key[2:]
|
|
||||||
if opt == "override" {
|
|
||||||
for _, v := range val.([]string) {
|
|
||||||
if strings.Contains(v, "=") {
|
|
||||||
kv := strings.SplitN(v, "=", 2)
|
|
||||||
opts[kv[0]] = kv[1]
|
|
||||||
} else {
|
|
||||||
log.Error("Malformed override, expected KEY=VALUE, got %s", v)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch v := val.(type) {
|
|
||||||
case string:
|
|
||||||
opts[opt] = v
|
|
||||||
case int:
|
|
||||||
opts[opt] = fmt.Sprintf("%d", v)
|
|
||||||
case bool:
|
|
||||||
opts[opt] = fmt.Sprintf("%t", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cant use proper [default:x] syntax in docopt
|
|
||||||
// because only want to default if the option is not
|
|
||||||
// already specified in some .jira.d/config.yml file
|
|
||||||
if _, ok := opts["user"]; !ok {
|
|
||||||
opts["user"] = user
|
|
||||||
}
|
|
||||||
if _, ok := opts["queryfields"]; !ok {
|
|
||||||
opts["queryfields"] = "summary,created,priority,status,reporter,assignee"
|
|
||||||
}
|
|
||||||
if _, ok := opts["directory"]; !ok {
|
|
||||||
opts["directory"] = fmt.Sprintf("%s/.jira.d/templates", home)
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
c := cli.New(opts)
|
|
||||||
|
|
||||||
log.Debug("opts: %s", opts)
|
|
||||||
|
|
||||||
validCommand := func(cmd string) bool {
|
|
||||||
if val, ok := args[cmd]; ok && val.(bool) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
validOpt := func(opt string, dflt interface{}) interface{} {
|
|
||||||
if val, ok := opts[opt]; ok {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
if dflt == nil {
|
|
||||||
log.Error("Missing required option --%s or \"%s\" property override in the config file", opt, opt)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return dflt
|
|
||||||
}
|
|
||||||
|
|
||||||
setEditing := func(dflt bool) {
|
|
||||||
if dflt {
|
|
||||||
if val, ok := opts["noedit"]; ok && val == "true" {
|
|
||||||
opts["edit"] = "false"
|
|
||||||
} else {
|
|
||||||
opts["edit"] = "true"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if val, ok := opts["edit"]; ok && val == "true" {
|
|
||||||
opts["edit"] = "true"
|
|
||||||
} else {
|
|
||||||
opts["edit"] = "false"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if validCommand("login") {
|
|
||||||
err = c.CmdLogin()
|
|
||||||
} else if validCommand("fields") {
|
|
||||||
err = c.CmdFields()
|
|
||||||
} else if validCommand("ls") || validCommand("list") {
|
|
||||||
err = c.CmdList()
|
|
||||||
} else if validCommand("edit") {
|
|
||||||
setEditing(true)
|
|
||||||
err = c.CmdEdit(args["ISSUE"].(string))
|
|
||||||
} else if validCommand("editmeta") {
|
|
||||||
err = c.CmdEditMeta(args["ISSUE"].(string))
|
|
||||||
} else if validCommand("transmeta") {
|
|
||||||
err = c.CmdTransitionMeta(args["ISSUE"].(string))
|
|
||||||
} else if validCommand("issuelinktypes") {
|
|
||||||
err = c.CmdIssueLinkTypes()
|
|
||||||
} else if validCommand("issuetypes") {
|
|
||||||
err = c.CmdIssueTypes(validOpt("project", nil).(string))
|
|
||||||
} else if validCommand("createmeta") {
|
|
||||||
err = c.CmdCreateMeta(
|
|
||||||
validOpt("project", nil).(string),
|
|
||||||
validOpt("issuetype", "Bug").(string),
|
|
||||||
)
|
|
||||||
} else if validCommand("create") {
|
|
||||||
setEditing(true)
|
|
||||||
err = c.CmdCreate(
|
|
||||||
validOpt("project", nil).(string),
|
|
||||||
validOpt("issuetype", "Bug").(string),
|
|
||||||
)
|
|
||||||
} else if validCommand("transitions") {
|
|
||||||
err = c.CmdTransitions(args["ISSUE"].(string))
|
|
||||||
} else if validCommand("blocks") {
|
|
||||||
err = c.CmdBlocks(
|
|
||||||
args["BLOCKER"].(string),
|
|
||||||
args["ISSUE"].(string),
|
|
||||||
)
|
|
||||||
} else if validCommand("dups") {
|
|
||||||
err = c.CmdDups(
|
|
||||||
args["DUPLICATE"].(string),
|
|
||||||
args["ISSUE"].(string),
|
|
||||||
)
|
|
||||||
} else if validCommand("watch") {
|
|
||||||
err = c.CmdWatch(
|
|
||||||
args["ISSUE"].(string),
|
|
||||||
validOpt("watcher", user).(string),
|
|
||||||
)
|
|
||||||
} else if validCommand("trans") || validCommand("transition") {
|
|
||||||
setEditing(true)
|
|
||||||
err = c.CmdTransition(
|
|
||||||
args["ISSUE"].(string),
|
|
||||||
args["TRANSITION"].(string),
|
|
||||||
)
|
|
||||||
} else if validCommand("close") {
|
|
||||||
setEditing(false)
|
|
||||||
err = c.CmdTransition(args["ISSUE"].(string), "close")
|
|
||||||
} else if validCommand("ack") {
|
|
||||||
setEditing(false)
|
|
||||||
err = c.CmdTransition(args["ISSUE"].(string), "acknowledge")
|
|
||||||
} else if validCommand("reopen") {
|
|
||||||
setEditing(false)
|
|
||||||
err = c.CmdTransition(args["ISSUE"].(string), "reopen")
|
|
||||||
} else if validCommand("resolve") {
|
|
||||||
setEditing(false)
|
|
||||||
err = c.CmdTransition(args["ISSUE"].(string), "resolve")
|
|
||||||
} else if validCommand("start") {
|
|
||||||
setEditing(false)
|
|
||||||
err = c.CmdTransition(args["ISSUE"].(string), "start")
|
|
||||||
} else if validCommand("stop") {
|
|
||||||
setEditing(false)
|
|
||||||
err = c.CmdTransition(args["ISSUE"].(string), "stop")
|
|
||||||
} else if validCommand("comment") {
|
|
||||||
setEditing(true)
|
|
||||||
err = c.CmdComment(args["ISSUE"].(string))
|
|
||||||
} else if validCommand("take") {
|
|
||||||
err = c.CmdAssign(args["ISSUE"].(string), user)
|
|
||||||
} else if validCommand("browse") || validCommand("b") {
|
|
||||||
opts["browse"] = "true"
|
|
||||||
err = c.Browse(args["ISSUE"].(string))
|
|
||||||
} else if validCommand("export-templates") {
|
|
||||||
err = c.CmdExportTemplates()
|
|
||||||
} else if validCommand("assign") || validCommand("give") {
|
|
||||||
err = c.CmdAssign(
|
|
||||||
args["ISSUE"].(string),
|
|
||||||
args["ASSIGNEE"].(string),
|
|
||||||
)
|
|
||||||
} else if val, ok := args["ISSUE"]; ok {
|
|
||||||
err = c.CmdView(val.(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseYaml(file string, opts map[string]string) {
|
|
||||||
if fh, err := ioutil.ReadFile(file); err == nil {
|
|
||||||
log.Debug("Found Config file: %s", file)
|
|
||||||
yaml.Unmarshal(fh, &opts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func populateEnv(args map[string]interface{}) {
|
|
||||||
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) {
|
|
||||||
os.Setenv("JIRA_OPERATION", "list")
|
|
||||||
} else if key == "b" && val.(bool) {
|
|
||||||
os.Setenv("JIRA_OPERATION", "browse")
|
|
||||||
} else if key == "trans" && val.(bool) {
|
|
||||||
os.Setenv("JIRA_OPERATION", "transition")
|
|
||||||
} else if key == "give" && val.(bool) {
|
|
||||||
os.Setenv("JIRA_OPERATION", "assign")
|
|
||||||
} else if val.(bool) {
|
|
||||||
os.Setenv("JIRA_OPERATION", key)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
os.Setenv(fmt.Sprintf("JIRA_%s", key), val.(string))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadConfigs(opts map[string]string) {
|
|
||||||
paths := cli.FindParentPaths(".jira.d/config.yml")
|
|
||||||
// prepend
|
|
||||||
paths = append([]string{"/etc/jira-cli.yml"}, paths...)
|
|
||||||
|
|
||||||
for _, file := range paths {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+489
@@ -0,0 +1,489 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/Netflix-Skunkworks/go-jira"
|
||||||
|
"github.com/coryb/optigo"
|
||||||
|
"github.com/op/go-logging"
|
||||||
|
"gopkg.in/coryb/yaml.v2"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
log = logging.MustGetLogger("jira")
|
||||||
|
format = "%{color}%{time:2006-01-02T15:04:05.000Z07:00} %{level:-5s} [%{shortfile}]%{color:reset} %{message}"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
logBackend := logging.NewLogBackend(os.Stderr, "", 0)
|
||||||
|
logging.SetBackend(
|
||||||
|
logging.NewBackendFormatter(
|
||||||
|
logBackend,
|
||||||
|
logging.MustStringFormatter(format),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
logging.SetLevel(logging.NOTICE, "")
|
||||||
|
|
||||||
|
user := os.Getenv("USER")
|
||||||
|
home := os.Getenv("HOME")
|
||||||
|
defaultQueryFields := "summary,created,updated,priority,status,reporter,assignee"
|
||||||
|
defaultSort := "priority asc, created"
|
||||||
|
defaultMaxResults := 500
|
||||||
|
|
||||||
|
usage := func(ok bool) {
|
||||||
|
printer := fmt.Printf
|
||||||
|
if !ok {
|
||||||
|
printer = func(format string, args ...interface{}) (int, error) {
|
||||||
|
return fmt.Fprintf(os.Stderr, format, args...)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
os.Exit(1)
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
defer func() {
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
output := fmt.Sprintf(`
|
||||||
|
Usage:
|
||||||
|
jira (ls|list) <Query Options>
|
||||||
|
jira view ISSUE
|
||||||
|
jira edit [--noedit] <Edit Options> [ISSUE | <Query Options>]
|
||||||
|
jira create [--noedit] [-p PROJECT] <Create Options>
|
||||||
|
jira DUPLICATE dups ISSUE
|
||||||
|
jira BLOCKER blocks ISSUE
|
||||||
|
jira watch ISSUE [-w WATCHER]
|
||||||
|
jira (trans|transition) TRANSITION ISSUE [--noedit] <Edit Options>
|
||||||
|
jira ack ISSUE [--edit] <Edit Options>
|
||||||
|
jira close ISSUE [--edit] <Edit Options>
|
||||||
|
jira resolve ISSUE [--edit] <Edit Options>
|
||||||
|
jira reopen ISSUE [--edit] <Edit Options>
|
||||||
|
jira start ISSUE [--edit] <Edit Options>
|
||||||
|
jira stop ISSUE [--edit] <Edit Options>
|
||||||
|
jira comment ISSUE [--noedit] <Edit Options>
|
||||||
|
jira (set,add,remove) labels ISSUE [LABEL] ...
|
||||||
|
jira take ISSUE
|
||||||
|
jira (assign|give) ISSUE ASSIGNEE
|
||||||
|
jira fields
|
||||||
|
jira issuelinktypes
|
||||||
|
jira transmeta ISSUE
|
||||||
|
jira editmeta ISSUE
|
||||||
|
jira issuetypes [-p PROJECT]
|
||||||
|
jira createmeta [-p PROJECT] [-i ISSUETYPE]
|
||||||
|
jira transitions ISSUE
|
||||||
|
jira export-templates [-d DIR] [-t template]
|
||||||
|
jira (b|browse) ISSUE
|
||||||
|
jira login
|
||||||
|
jira request [-M METHOD] URI [DATA]
|
||||||
|
jira ISSUE
|
||||||
|
|
||||||
|
General Options:
|
||||||
|
-b --browse Open your browser to the Jira issue
|
||||||
|
-e --endpoint=URI URI to use for jira
|
||||||
|
-k --insecure disable TLS certificate verification
|
||||||
|
-h --help Show this usage
|
||||||
|
-t --template=FILE Template file to use for output/editing
|
||||||
|
-u --user=USER Username to use for authenticaion (default: %s)
|
||||||
|
-v --verbose Increase output logging
|
||||||
|
--version Print version
|
||||||
|
|
||||||
|
Query Options:
|
||||||
|
-a --assignee=USER Username assigned the issue
|
||||||
|
-c --component=COMPONENT Component to Search for
|
||||||
|
-f --queryfields=FIELDS Fields that are used in "list" template: (default: %s)
|
||||||
|
-i --issuetype=ISSUETYPE The Issue Type
|
||||||
|
-l --limit=VAL Maximum number of results to return in query (default: %d)
|
||||||
|
-p --project=PROJECT Project to Search for
|
||||||
|
-q --query=JQL Jira Query Language expression for the search
|
||||||
|
-r --reporter=USER Reporter to search for
|
||||||
|
-s --sort=ORDER For list operations, sort issues (default: %s)
|
||||||
|
-w --watcher=USER Watcher to add to issue (default: %s)
|
||||||
|
or Watcher to search for
|
||||||
|
|
||||||
|
Edit Options:
|
||||||
|
-m --comment=COMMENT Comment message for transition
|
||||||
|
-o --override=KEY=VAL Set custom key/value pairs
|
||||||
|
|
||||||
|
Create Options:
|
||||||
|
-i --issuetype=ISSUETYPE Jira Issue Type (default: Bug)
|
||||||
|
-m --comment=COMMENT Comment message for transition
|
||||||
|
-o --override=KEY=VAL Set custom key/value pairs
|
||||||
|
|
||||||
|
Command Options:
|
||||||
|
-d --directory=DIR Directory to export templates to (default: %s)
|
||||||
|
`, user, defaultQueryFields, defaultMaxResults, defaultSort, user, fmt.Sprintf("%s/.jira.d/templates", home))
|
||||||
|
printer(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
jiraCommands := map[string]string{
|
||||||
|
"list": "list",
|
||||||
|
"ls": "list",
|
||||||
|
"view": "view",
|
||||||
|
"edit": "edit",
|
||||||
|
"create": "create",
|
||||||
|
"dups": "dups",
|
||||||
|
"blocks": "blocks",
|
||||||
|
"watch": "watch",
|
||||||
|
"trans": "transition",
|
||||||
|
"transition": "transition",
|
||||||
|
"ack": "acknowledge",
|
||||||
|
"acknowledge": "acknowledge",
|
||||||
|
"close": "close",
|
||||||
|
"resolve": "resolve",
|
||||||
|
"reopen": "reopen",
|
||||||
|
"start": "start",
|
||||||
|
"stop": "stop",
|
||||||
|
"comment": "comment",
|
||||||
|
"label": "labels",
|
||||||
|
"labels": "labels",
|
||||||
|
"take": "take",
|
||||||
|
"assign": "assign",
|
||||||
|
"give": "assign",
|
||||||
|
"fields": "fields",
|
||||||
|
"issuelinktypes": "issuelinktypes",
|
||||||
|
"transmeta": "transmeta",
|
||||||
|
"editmeta": "editmeta",
|
||||||
|
"issuetypes": "issuetypes",
|
||||||
|
"createmeta": "createmeta",
|
||||||
|
"transitions": "transitions",
|
||||||
|
"export-templates": "export-templates",
|
||||||
|
"browse": "browse",
|
||||||
|
"login": "login",
|
||||||
|
"req": "request",
|
||||||
|
"request": "request",
|
||||||
|
}
|
||||||
|
|
||||||
|
defaults := map[string]interface{}{
|
||||||
|
"user": user,
|
||||||
|
"queryfields": defaultQueryFields,
|
||||||
|
"directory": fmt.Sprintf("%s/.jira.d/templates", home),
|
||||||
|
"sort": defaultSort,
|
||||||
|
"max_results": defaultMaxResults,
|
||||||
|
"method": "GET",
|
||||||
|
"quiet": false,
|
||||||
|
}
|
||||||
|
opts := make(map[string]interface{})
|
||||||
|
|
||||||
|
setopt := func(name string, value interface{}) {
|
||||||
|
opts[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
op := optigo.NewDirectAssignParser(map[string]interface{}{
|
||||||
|
"h|help": usage,
|
||||||
|
"version": func() {
|
||||||
|
fmt.Println(fmt.Sprintf("version: %s", jira.VERSION))
|
||||||
|
os.Exit(0)
|
||||||
|
},
|
||||||
|
"v|verbose+": func() {
|
||||||
|
logging.SetLevel(logging.GetLevel("")+1, "")
|
||||||
|
},
|
||||||
|
"dryrun": setopt,
|
||||||
|
"b|browse": setopt,
|
||||||
|
"editor=s": setopt,
|
||||||
|
"u|user=s": setopt,
|
||||||
|
"endpoint=s": setopt,
|
||||||
|
"k|insecure": setopt,
|
||||||
|
"t|template=s": setopt,
|
||||||
|
"q|query=s": setopt,
|
||||||
|
"p|project=s": setopt,
|
||||||
|
"c|component=s": setopt,
|
||||||
|
"a|assignee=s": setopt,
|
||||||
|
"i|issuetype=s": setopt,
|
||||||
|
"w|watcher=s": setopt,
|
||||||
|
"r|reporter=s": setopt,
|
||||||
|
"f|queryfields=s": setopt,
|
||||||
|
"s|sort=s": setopt,
|
||||||
|
"l|limit|max_results=i": setopt,
|
||||||
|
"o|override=s%": &opts,
|
||||||
|
"noedit": setopt,
|
||||||
|
"edit": setopt,
|
||||||
|
"m|comment=s": setopt,
|
||||||
|
"d|dir|directory=s": setopt,
|
||||||
|
"M|method=s": setopt,
|
||||||
|
"S|saveFile=s": setopt,
|
||||||
|
"Q|quiet": setopt,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := op.ProcessAll(os.Args[1:]); err != nil {
|
||||||
|
log.Error("%s", err)
|
||||||
|
usage(false)
|
||||||
|
}
|
||||||
|
args := op.Args
|
||||||
|
|
||||||
|
var command string
|
||||||
|
if len(args) > 0 {
|
||||||
|
if alias, ok := jiraCommands[args[0]]; ok {
|
||||||
|
command = alias
|
||||||
|
args = args[1:]
|
||||||
|
} else if len(args) > 1 {
|
||||||
|
// look at second arg for "dups" and "blocks" commands
|
||||||
|
// also for 'set/add/remove' actions like 'labels'
|
||||||
|
if alias, ok := jiraCommands[args[1]]; ok {
|
||||||
|
command = alias
|
||||||
|
args = append(args[:1], args[2:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if command == "" && len(args) > 0 {
|
||||||
|
command = args[0]
|
||||||
|
args = args[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Setenv("JIRA_OPERATION", command)
|
||||||
|
loadConfigs(opts)
|
||||||
|
|
||||||
|
// check to see if it was set in the configs:
|
||||||
|
if value, ok := opts["command"].(string); ok {
|
||||||
|
command = value
|
||||||
|
} else if _, ok := jiraCommands[command]; !ok || command == "" {
|
||||||
|
if command != "" {
|
||||||
|
args = append([]string{command}, args...)
|
||||||
|
}
|
||||||
|
command = "view"
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply defaults
|
||||||
|
for k, v := range defaults {
|
||||||
|
if _, ok := opts[k]; !ok {
|
||||||
|
log.Debug("Setting %q to %#v from defaults", k, v)
|
||||||
|
opts[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("opts: %v", opts)
|
||||||
|
log.Debug("args: %v", args)
|
||||||
|
|
||||||
|
if _, ok := opts["endpoint"]; !ok {
|
||||||
|
log.Error("endpoint option required. Either use --endpoint or set a endpoint option in your ~/.jira.d/config.yml file")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := jira.New(opts)
|
||||||
|
|
||||||
|
log.Debug("opts: %s", opts)
|
||||||
|
|
||||||
|
setEditing := func(dflt bool) {
|
||||||
|
log.Debug("Default Editing: %t", dflt)
|
||||||
|
if dflt {
|
||||||
|
if val, ok := opts["noedit"].(bool); ok && val {
|
||||||
|
log.Debug("Setting edit = false")
|
||||||
|
opts["edit"] = false
|
||||||
|
} else {
|
||||||
|
log.Debug("Setting edit = true")
|
||||||
|
opts["edit"] = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, ok := opts["edit"].(bool); !ok {
|
||||||
|
log.Debug("Setting edit = %t", dflt)
|
||||||
|
opts["edit"] = dflt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requireArgs := func(count int) {
|
||||||
|
if len(args) < count {
|
||||||
|
log.Error("Not enough arguments. %d required, %d provided", count, len(args))
|
||||||
|
usage(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
switch command {
|
||||||
|
case "login":
|
||||||
|
err = c.CmdLogin()
|
||||||
|
case "fields":
|
||||||
|
err = c.CmdFields()
|
||||||
|
case "list":
|
||||||
|
err = c.CmdList()
|
||||||
|
case "edit":
|
||||||
|
setEditing(true)
|
||||||
|
if len(args) > 0 {
|
||||||
|
err = c.CmdEdit(args[0])
|
||||||
|
} else {
|
||||||
|
var data interface{}
|
||||||
|
if data, err = c.FindIssues(); err == nil {
|
||||||
|
issues := data.(map[string]interface{})["issues"].([]interface{})
|
||||||
|
for _, issue := range issues {
|
||||||
|
if err = c.CmdEdit(issue.(map[string]interface{})["key"].(string)); err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case jira.NoChangesFound:
|
||||||
|
log.Warning("No Changes found: %s", err)
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "editmeta":
|
||||||
|
requireArgs(1)
|
||||||
|
err = c.CmdEditMeta(args[0])
|
||||||
|
case "transmeta":
|
||||||
|
requireArgs(1)
|
||||||
|
err = c.CmdTransitionMeta(args[0])
|
||||||
|
case "issuelinktypes":
|
||||||
|
err = c.CmdIssueLinkTypes()
|
||||||
|
case "issuetypes":
|
||||||
|
err = c.CmdIssueTypes()
|
||||||
|
case "createmeta":
|
||||||
|
err = c.CmdCreateMeta()
|
||||||
|
case "create":
|
||||||
|
setEditing(true)
|
||||||
|
err = c.CmdCreate()
|
||||||
|
case "transitions":
|
||||||
|
requireArgs(1)
|
||||||
|
err = c.CmdTransitions(args[0])
|
||||||
|
case "blocks":
|
||||||
|
requireArgs(2)
|
||||||
|
err = c.CmdBlocks(args[0], args[1])
|
||||||
|
case "dups":
|
||||||
|
requireArgs(2)
|
||||||
|
if err = c.CmdDups(args[0], args[1]); err == nil {
|
||||||
|
opts["resolution"] = "Duplicate"
|
||||||
|
err = c.CmdTransition(args[0], "close")
|
||||||
|
}
|
||||||
|
case "watch":
|
||||||
|
requireArgs(1)
|
||||||
|
err = c.CmdWatch(args[0])
|
||||||
|
case "transition":
|
||||||
|
requireArgs(2)
|
||||||
|
setEditing(true)
|
||||||
|
err = c.CmdTransition(args[1], args[0])
|
||||||
|
case "close":
|
||||||
|
requireArgs(1)
|
||||||
|
setEditing(false)
|
||||||
|
err = c.CmdTransition(args[0], "close")
|
||||||
|
case "acknowledge":
|
||||||
|
requireArgs(1)
|
||||||
|
setEditing(false)
|
||||||
|
err = c.CmdTransition(args[0], "acknowledge")
|
||||||
|
case "reopen":
|
||||||
|
requireArgs(1)
|
||||||
|
setEditing(false)
|
||||||
|
err = c.CmdTransition(args[0], "reopen")
|
||||||
|
case "resolve":
|
||||||
|
requireArgs(1)
|
||||||
|
setEditing(false)
|
||||||
|
err = c.CmdTransition(args[0], "resolve")
|
||||||
|
case "start":
|
||||||
|
requireArgs(1)
|
||||||
|
setEditing(false)
|
||||||
|
err = c.CmdTransition(args[0], "start")
|
||||||
|
case "stop":
|
||||||
|
requireArgs(1)
|
||||||
|
setEditing(false)
|
||||||
|
err = c.CmdTransition(args[0], "stop")
|
||||||
|
case "comment":
|
||||||
|
requireArgs(1)
|
||||||
|
setEditing(true)
|
||||||
|
err = c.CmdComment(args[0])
|
||||||
|
case "labels":
|
||||||
|
requireArgs(2)
|
||||||
|
action := args[0]
|
||||||
|
issue := args[1]
|
||||||
|
labels := args[2:]
|
||||||
|
err = c.CmdLabels(action, issue, labels)
|
||||||
|
case "take":
|
||||||
|
requireArgs(1)
|
||||||
|
err = c.CmdAssign(args[0], opts["user"].(string))
|
||||||
|
case "browse":
|
||||||
|
requireArgs(1)
|
||||||
|
opts["browse"] = true
|
||||||
|
err = c.Browse(args[0])
|
||||||
|
case "export-templates":
|
||||||
|
err = c.CmdExportTemplates()
|
||||||
|
case "assign":
|
||||||
|
requireArgs(2)
|
||||||
|
err = c.CmdAssign(args[0], args[1])
|
||||||
|
case "view":
|
||||||
|
requireArgs(1)
|
||||||
|
err = c.CmdView(args[0])
|
||||||
|
case "request":
|
||||||
|
requireArgs(1)
|
||||||
|
data := ""
|
||||||
|
if len(args) > 1 {
|
||||||
|
data = args[1]
|
||||||
|
}
|
||||||
|
err = c.CmdRequest(args[0], data)
|
||||||
|
default:
|
||||||
|
log.Error("Unknown command %s", command)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error("%s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseYaml(file string, opts map[string]interface{}) {
|
||||||
|
if fh, err := ioutil.ReadFile(file); err == nil {
|
||||||
|
log.Debug("Found Config file: %s", file)
|
||||||
|
yaml.Unmarshal(fh, &opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func populateEnv(opts map[string]interface{}) {
|
||||||
|
for k, v := range opts {
|
||||||
|
envName := fmt.Sprintf("JIRA_%s", strings.ToUpper(k))
|
||||||
|
var val string
|
||||||
|
switch t := v.(type) {
|
||||||
|
case string:
|
||||||
|
val = t
|
||||||
|
case int, int8, int16, int32, int64:
|
||||||
|
val = fmt.Sprintf("%d", t)
|
||||||
|
case float32, float64:
|
||||||
|
val = fmt.Sprintf("%f", t)
|
||||||
|
case bool:
|
||||||
|
val = fmt.Sprintf("%t", t)
|
||||||
|
default:
|
||||||
|
val = fmt.Sprintf("%v", t)
|
||||||
|
}
|
||||||
|
os.Setenv(envName, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConfigs(opts map[string]interface{}) {
|
||||||
|
populateEnv(opts)
|
||||||
|
paths := jira.FindParentPaths(".jira.d/config.yml")
|
||||||
|
// prepend
|
||||||
|
paths = append([]string{"/etc/go-jira.yml"}, paths...)
|
||||||
|
|
||||||
|
// iterate paths in reverse
|
||||||
|
for i := len(paths) - 1; i >= 0; i-- {
|
||||||
|
file := paths[i]
|
||||||
|
if stat, err := os.Stat(file); err == nil {
|
||||||
|
tmp := make(map[string]interface{})
|
||||||
|
// check to see if config file is exectuable
|
||||||
|
if stat.Mode()&0111 == 0 {
|
||||||
|
parseYaml(file, tmp)
|
||||||
|
} 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(), &tmp)
|
||||||
|
}
|
||||||
|
for k, v := range tmp {
|
||||||
|
if _, ok := opts[k]; !ok {
|
||||||
|
log.Debug("Setting %q to %#v from %s", k, v, file)
|
||||||
|
opts[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
populateEnv(opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package cli
|
package jira
|
||||||
|
|
||||||
var all_templates = map[string]string{
|
var all_templates = map[string]string{
|
||||||
"debug": default_debug_template,
|
"debug": default_debug_template,
|
||||||
@@ -16,6 +16,7 @@ var all_templates = map[string]string{
|
|||||||
"create": default_create_template,
|
"create": default_create_template,
|
||||||
"comment": default_comment_template,
|
"comment": default_comment_template,
|
||||||
"transition": default_transition_template,
|
"transition": default_transition_template,
|
||||||
|
"request": default_debug_template,
|
||||||
}
|
}
|
||||||
|
|
||||||
const default_debug_template = "{{ . | toJson}}\n"
|
const default_debug_template = "{{ . | toJson}}\n"
|
||||||
@@ -40,7 +41,7 @@ assignee: {{ if .fields.assignee }}{{ .fields.assignee.name }}{{end}}
|
|||||||
reporter: {{ if .fields.reporter }}{{ .fields.reporter.name }}{{end}}
|
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}}
|
||||||
epends: {{ 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}}
|
||||||
priority: {{ .fields.priority.name }}
|
priority: {{ .fields.priority.name }}
|
||||||
description: |
|
description: |
|
||||||
{{ or .fields.description "" | indent 2 }}
|
{{ or .fields.description "" | indent 2 }}
|
||||||
@@ -50,10 +51,11 @@ comments:
|
|||||||
{{ or .body "" | indent 4}}
|
{{ or .body "" | indent 4}}
|
||||||
{{end}}
|
{{end}}
|
||||||
`
|
`
|
||||||
const default_edit_template = `update:
|
const default_edit_template = `# issue: {{ .key }}
|
||||||
|
update:
|
||||||
comment:
|
comment:
|
||||||
- add:
|
- add:
|
||||||
body: |
|
body: |~
|
||||||
{{ or .overrides.comment "" | indent 10 }}
|
{{ or .overrides.comment "" | indent 10 }}
|
||||||
fields:
|
fields:
|
||||||
summary: {{ or .overrides.summary .fields.summary }}
|
summary: {{ or .overrides.summary .fields.summary }}
|
||||||
@@ -70,8 +72,12 @@ fields:
|
|||||||
- name: {{ .overrides.watcher}}{{end}}
|
- name: {{ .overrides.watcher}}{{end}}
|
||||||
priority: # Values: {{ range .meta.fields.priority.allowedValues }}{{.name}}, {{end}}
|
priority: # Values: {{ range .meta.fields.priority.allowedValues }}{{.name}}, {{end}}
|
||||||
name: {{ or .overrides.priority .fields.priority.name }}
|
name: {{ or .overrides.priority .fields.priority.name }}
|
||||||
description: |
|
description: |~
|
||||||
{{ or .overrides.description (or .fields.description "") | indent 4 }}
|
{{ or .overrides.description (or .fields.description "") | indent 4 }}
|
||||||
|
# comments:
|
||||||
|
# {{ range .fields.comment.comments }} - | # {{.author.name}} at {{.created}}
|
||||||
|
# {{ or .body "" | indent 4 | comment}}
|
||||||
|
# {{end}}
|
||||||
`
|
`
|
||||||
const default_transitions_template = `{{ range .transitions }}{{.id }}: {{.name}}
|
const default_transitions_template = `{{ range .transitions }}{{.id }}: {{.name}}
|
||||||
{{end}}`
|
{{end}}`
|
||||||
@@ -89,7 +95,7 @@ const default_create_template = `fields:
|
|||||||
name: {{ or .overrides.priority "unassigned" }}
|
name: {{ or .overrides.priority "unassigned" }}
|
||||||
components: # Values: {{ range .meta.fields.components.allowedValues }}{{.name}}, {{end}}{{ range split "," (or .overrides.components "")}}
|
components: # Values: {{ range .meta.fields.components.allowedValues }}{{.name}}, {{end}}{{ range split "," (or .overrides.components "")}}
|
||||||
- name: {{ . }}{{end}}
|
- name: {{ . }}{{end}}
|
||||||
description: |
|
description: |~
|
||||||
{{ or .overrides.description "" | indent 4 }}
|
{{ or .overrides.description "" | indent 4 }}
|
||||||
assignee:
|
assignee:
|
||||||
name: {{ or .overrides.assignee "" }}
|
name: {{ or .overrides.assignee "" }}
|
||||||
@@ -101,14 +107,14 @@ const default_create_template = `fields:
|
|||||||
- name:
|
- name:
|
||||||
`
|
`
|
||||||
|
|
||||||
const default_comment_template = `body: |
|
const default_comment_template = `body: |~
|
||||||
{{ or .overrides.comment "" | indent 2 }}
|
{{ or .overrides.comment "" | indent 2 }}
|
||||||
`
|
`
|
||||||
|
|
||||||
const default_transition_template = `update:
|
const default_transition_template = `update:
|
||||||
comment:
|
comment:
|
||||||
- add:
|
- add:
|
||||||
body: |
|
body: |~
|
||||||
{{ or .overrides.comment "" | indent 10 }}
|
{{ or .overrides.comment "" | indent 10 }}
|
||||||
fields:{{if .meta.fields.assignee}}
|
fields:{{if .meta.fields.assignee}}
|
||||||
assignee:
|
assignee:
|
||||||
+78
-10
@@ -1,4 +1,4 @@
|
|||||||
package cli
|
package jira
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mgutz/ansi"
|
"github.com/mgutz/ansi"
|
||||||
|
"gopkg.in/coryb/yaml.v2"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -63,6 +64,21 @@ func readFile(file string) string {
|
|||||||
return string(bytes)
|
return string(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func copyFile(src, dst string) (err error) {
|
||||||
|
var s, d *os.File
|
||||||
|
if s, err = os.Open(src); err == nil {
|
||||||
|
defer s.Close()
|
||||||
|
if d, err = os.Create(dst); err == nil {
|
||||||
|
if _, err = io.Copy(d, s); err != nil {
|
||||||
|
d.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return d.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func fuzzyAge(start string) (string, error) {
|
func fuzzyAge(start string) (string, error) {
|
||||||
if t, err := time.Parse("2006-01-02T15:04:05.000-0700", start); err != nil {
|
if t, err := time.Parse("2006-01-02T15:04:05.000-0700", start); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -85,8 +101,19 @@ func fuzzyAge(start string) (string, error) {
|
|||||||
return "unknown", nil
|
return "unknown", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTemplate(templateContent string, data interface{}, out io.Writer) error {
|
func dateFormat(format string, content string) (string, error) {
|
||||||
|
if t, err := time.Parse("2006-01-02T15:04:05.000-0700", content); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else {
|
||||||
|
return t.Format(format), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunTemplate(templateContent string, data interface{}, out io.Writer) error {
|
||||||
|
return runTemplate(templateContent, data, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTemplate(templateContent string, data interface{}, out io.Writer) error {
|
||||||
if out == nil {
|
if out == nil {
|
||||||
out = os.Stdout
|
out = os.Stdout
|
||||||
}
|
}
|
||||||
@@ -110,12 +137,26 @@ func runTemplate(templateContent string, data interface{}, out io.Writer) error
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indent": func(spaces int, content string) string {
|
"indent": func(spaces int, content string) string {
|
||||||
indent := make([]byte, spaces+1, spaces+1)
|
indent := make([]rune, spaces+1, spaces+1)
|
||||||
indent[0] = '\n'
|
indent[0] = '\n'
|
||||||
for i := 1; i < spaces+1; i += 1 {
|
for i := 1; i < spaces+1; i += 1 {
|
||||||
indent[i] = ' '
|
indent[i] = ' '
|
||||||
}
|
}
|
||||||
return strings.Replace(content, "\n", string(indent), -1)
|
|
||||||
|
lineSeps := []rune{'\n', '\u0085', '\u2028', '\u2029'}
|
||||||
|
for _, sep := range lineSeps {
|
||||||
|
indent[0] = sep
|
||||||
|
content = strings.Replace(content, string(sep), string(indent), -1)
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
|
||||||
|
},
|
||||||
|
"comment": func(content string) string {
|
||||||
|
lineSeps := []rune{'\n', '\u0085', '\u2028', '\u2029'}
|
||||||
|
for _, sep := range lineSeps {
|
||||||
|
content = strings.Replace(content, string(sep), string([]rune{sep, '#', ' '}), -1)
|
||||||
|
}
|
||||||
|
return content
|
||||||
},
|
},
|
||||||
"color": func(color string) string {
|
"color": func(color string) string {
|
||||||
return ansi.ColorCode(color)
|
return ansi.ColorCode(color)
|
||||||
@@ -123,6 +164,13 @@ 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)
|
||||||
},
|
},
|
||||||
|
"join": func(sep string, content []interface{}) string {
|
||||||
|
vals := make([]string, len(content))
|
||||||
|
for i, v := range content {
|
||||||
|
vals[i] = v.(string)
|
||||||
|
}
|
||||||
|
return strings.Join(vals, sep)
|
||||||
|
},
|
||||||
"abbrev": func(max int, content string) string {
|
"abbrev": func(max int, content string) string {
|
||||||
if len(content) > max {
|
if len(content) > max {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
@@ -142,6 +190,9 @@ func runTemplate(templateContent string, data interface{}, out io.Writer) error
|
|||||||
"age": func(content string) (string, error) {
|
"age": func(content string) (string, error) {
|
||||||
return fuzzyAge(content)
|
return fuzzyAge(content)
|
||||||
},
|
},
|
||||||
|
"dateFormat": func(format string, content string) (string, error) {
|
||||||
|
return dateFormat(format, 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)
|
||||||
@@ -205,6 +256,21 @@ func jsonWrite(file string, data interface{}) {
|
|||||||
enc.Encode(data)
|
enc.Encode(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func yamlWrite(file string, data interface{}) {
|
||||||
|
fh, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
defer fh.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to open %s: %s", file, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if out, err := yaml.Marshal(data); err != nil {
|
||||||
|
log.Error("Failed to marshal yaml %v: %s", data, err)
|
||||||
|
os.Exit(1)
|
||||||
|
} else {
|
||||||
|
fh.Write(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func promptYN(prompt string, yes bool) bool {
|
func promptYN(prompt string, yes bool) bool {
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
if !yes {
|
if !yes {
|
||||||
@@ -246,25 +312,27 @@ func yamlFixup(data interface{}) (interface{}, error) {
|
|||||||
}
|
}
|
||||||
return copy, nil
|
return copy, nil
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
|
copy := make(map[string]interface{})
|
||||||
for k, v := range d {
|
for k, v := range d {
|
||||||
if fixed, err := yamlFixup(v); err != nil {
|
if fixed, err := yamlFixup(v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if fixed != nil {
|
} else if fixed != nil {
|
||||||
d[k] = fixed
|
copy[k] = fixed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return d, nil
|
return copy, nil
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
for i, val := range d {
|
copy := make([]interface{}, 0, len(d))
|
||||||
|
for _, val := range d {
|
||||||
if fixed, err := yamlFixup(val); err != nil {
|
if fixed, err := yamlFixup(val); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if fixed != nil {
|
} else if fixed != nil {
|
||||||
d[i] = fixed
|
copy = append(copy, fixed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return data, nil
|
return copy, nil
|
||||||
case string:
|
case string:
|
||||||
if d == "" {
|
if d == "" || d == "\n" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return d, nil
|
return d, nil
|
||||||
Reference in New Issue
Block a user