mirror of
https://github.com/Threnklyn/jira.git
synced 2026-05-28 16:58:28 +02:00
Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| de4fe76fec | |||
| 5b870cb7a2 | |||
| 89bb82b3f2 | |||
| dd0f5efd32 | |||
| 68b5e60dd9 | |||
| 71acc5d7fc | |||
| 4f91cecf25 | |||
| 688b987895 | |||
| 71bb04fabb | |||
| 3a9f763f9d | |||
| d86d85f7b2 | |||
| 4b798cbfb4 | |||
| 598924b51d | |||
| 674957af5d | |||
| c568d7e921 | |||
| 6eb3567ca5 | |||
| 87ec73c5c3 | |||
| 23551abb11 | |||
| 693e1441f7 | |||
| 6e5cc9821e | |||
| 9e90376816 | |||
| 20b32c2ed6 | |||
| ac170e9ab1 | |||
| d8bce08d3a | |||
| 382bf4faeb | |||
| 595a5212b4 | |||
| f595801202 | |||
| 404caf6400 | |||
| f7eb04e36d | |||
| b0d4f7273d | |||
| a927181db1 | |||
| b5417ef585 | |||
| c5af781c41 | |||
| f2c4df9b3e | |||
| 1dde7e06e6 | |||
| 7bc1897792 | |||
| 37aab3580b | |||
| ff56136937 | |||
| 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 |
@@ -5,3 +5,5 @@ src/github.com/docopt/
|
|||||||
src/github.com/mgutz/
|
src/github.com/mgutz/
|
||||||
src/github.com/op/
|
src/github.com/op/
|
||||||
src/gopkg.in/
|
src/gopkg.in/
|
||||||
|
jira
|
||||||
|
jira.exe
|
||||||
@@ -1,5 +1,68 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.1.1 - 2016-06-28
|
||||||
|
|
||||||
|
* use USERPROFILE instead of HOME for windows, rework paths to use filepath.Join for better cross platform support [Cory Bennett] [[adcedc4](https://github.com/Netflix-Skunkworks/go-jira/commit/adcedc4)]
|
||||||
|
* Include templates from a system path [Mike Pountney] [[cf10f53](https://github.com/Netflix-Skunkworks/go-jira/commit/cf10f53)]
|
||||||
|
* Added support for the ```expand``` option for Issues [tobyjoe] [[fb4afc9](https://github.com/Netflix-Skunkworks/go-jira/commit/fb4afc9)]
|
||||||
|
* change for api changes to go-logging [Cory Bennett] [[7bfc6e8](https://github.com/Netflix-Skunkworks/go-jira/commit/7bfc6e8)]
|
||||||
|
* Fix issuetype calls adding URL escaping [Jonathan Wright] [[e4a25e2](https://github.com/Netflix-Skunkworks/go-jira/commit/e4a25e2)]
|
||||||
|
|
||||||
|
## 0.1.0 - 2016-01-29
|
||||||
|
|
||||||
|
* Fixes [#32](https://github.com/Netflix-Skunkworks/go-jira/issues/32) - make path to cookieFile if it's not present [Mike Pountney] [[6644579](https://github.com/Netflix-Skunkworks/go-jira/commit/6644579)]
|
||||||
|
* Add component/components support: add and list for now. [Mike Pountney] [[d7b3226](https://github.com/Netflix-Skunkworks/go-jira/commit/d7b3226)]
|
||||||
|
* Tweak the CmdWatch contract and add watcher remove support [Mike Pountney] [[383847a](https://github.com/Netflix-Skunkworks/go-jira/commit/383847a)]
|
||||||
|
* Amend vote/unvote to be vote/vote --down [Mike Pountney] [[797edef](https://github.com/Netflix-Skunkworks/go-jira/commit/797edef)]
|
||||||
|
* Add 'vote' and 'unvote' [Mike Pountney] [[c95e66e](https://github.com/Netflix-Skunkworks/go-jira/commit/c95e66e)]
|
||||||
|
|
||||||
|
## 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
|
## 0.0.12 - 2015-09-18
|
||||||
|
|
||||||
* fix exception from "jira create" [Cory Bennett] [[9348a4b](https://github.com/Netflix-Skunkworks/go-jira/commit/9348a4b)]
|
* fix exception from "jira create" [Cory Bennett] [[9348a4b](https://github.com/Netflix-Skunkworks/go-jira/commit/9348a4b)]
|
||||||
|
|||||||
@@ -13,61 +13,86 @@ PLATFORMS= \
|
|||||||
darwin-amd64 \
|
darwin-amd64 \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
DIST=$(shell pwd)/dist
|
NAME=jira
|
||||||
export GOPATH=$(shell pwd)
|
|
||||||
|
|
||||||
build:
|
OS=$(shell uname -s)
|
||||||
cd src/github.com/Netflix-Skunkworks/go-jira/jira; \
|
ifeq ($(filter CYGWIN%,$(OS)),$(OS))
|
||||||
go get -v
|
export CWD=$(shell cygpath -wa .)
|
||||||
|
export SEP=\\
|
||||||
|
export CYGWIN=winsymlinks:native
|
||||||
|
BIN ?= $(GOBIN)$(SEP)$(NAME).exe
|
||||||
|
else
|
||||||
|
export CWD=$(shell pwd)
|
||||||
|
export SEP=/
|
||||||
|
BIN ?= $(GOBIN)$(SEP)$(NAME)
|
||||||
|
endif
|
||||||
|
|
||||||
|
export GOPATH=$(CWD)
|
||||||
|
|
||||||
|
DIST=$(CWD)$(SEP)dist
|
||||||
|
|
||||||
|
GOBIN ?= $(CWD)
|
||||||
|
|
||||||
|
CURVER ?= $(shell [ -d .git ] && git describe --abbrev=0 --tags || grep ^\#\# CHANGELOG.md | awk '{print $$2; exit}')
|
||||||
|
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 '$(GOPATH)' $@
|
||||||
|
go get -v $* $*/main
|
||||||
|
|
||||||
cross-setup:
|
cross-setup:
|
||||||
for p in $(PLATFORMS); do \
|
for p in $(PLATFORMS); do \
|
||||||
echo "Building for $$p"; \
|
echo Building for $$p"; \
|
||||||
cd $(GOROOT)/src && sudo GOOS=$${p/-*/} GOARCH=$${p/*-/} bash ./make.bash --no-clean; \
|
cd $(GOROOT)/src && sudo GOROOT_BOOTSTRAP=$(GOROOT) GOOS=$${p/-*/} GOARCH=$${p/*-/} bash ./make.bash --no-clean; \
|
||||||
done
|
done
|
||||||
|
|
||||||
all:
|
all:
|
||||||
rm -rf $(DIST); \
|
rm -rf $(DIST); \
|
||||||
mkdir -p $(DIST); \
|
mkdir -p $(DIST); \
|
||||||
cd src/github.com/Netflix-Skunkworks/go-jira/jira; \
|
|
||||||
go get -d; \
|
|
||||||
for p in $(PLATFORMS); do \
|
for p in $(PLATFORMS); do \
|
||||||
echo "Building for $$p"; \
|
echo "Building for $$p"; \
|
||||||
GOOS=$${p/-*/} GOARCH=$${p/*-/} go build -v -ldflags -s -o $(DIST)/jira-$$p; \
|
${MAKE} build GOOS=$${p/-*/} GOARCH=$${p/*-/} BIN=$(DIST)/$(NAME)-$$p; \
|
||||||
done
|
done
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
gofmt -s -w jira
|
gofmt -s -w main/*.go *.go
|
||||||
|
|
||||||
install:
|
install:
|
||||||
export GOBIN=~/bin && ${MAKE} build
|
${MAKE} GOBIN=~/bin build
|
||||||
|
|
||||||
# need gsort on OSX (brew install coreutils) or newer sort on linux
|
|
||||||
# that supports the -V option for version sorting
|
|
||||||
SORT=gsort
|
|
||||||
|
|
||||||
CURVER ?= $(shell git fetch --tags && git tag | $(SORT) -V | tail -1)
|
|
||||||
NEWVER ?= $(shell echo $(CURVER) | awk -F. '{print $$1"."$$2"."$$3+1}')
|
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 commit -m "Updated Changelog" CHANGELOG.md; \
|
||||||
perl -pi -e "s/version: $(CURVER)/version: $(NEWVER)/" jira/main.go; \
|
git tag v$(NEWVER)
|
||||||
git commit -m "bump version" jira/main.go; \
|
|
||||||
git tag $(NEWVER)
|
version:
|
||||||
|
@echo $(patsubst v%,%,$(CURVER))
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf pkg dist bin && find src \! -path \*/go-jira\* -delete
|
rm -rf pkg dist bin src ./$(NAME)
|
||||||
|
|||||||
@@ -69,25 +69,13 @@ You can download one of the pre-built binaries for **go-jira** [here](https://gi
|
|||||||
|
|
||||||
* **NOTE** You will need **`go-1.4.1`** minimum
|
* **NOTE** You will need **`go-1.4.1`** minimum
|
||||||
|
|
||||||
* If you do not have a **GOPATH** setup, these are simple build steps:
|
* To build the `jira` binary the current directory just run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone git@github.com:Netflix-Skunkworks/go-jira.git
|
make
|
||||||
cd go-jira
|
|
||||||
export GOPATH=$(pwd)
|
|
||||||
export GOBIN=$GOPATH/bin
|
|
||||||
export PATH=$GOBIN:$PATH
|
|
||||||
cd src/github.com/Netflix-Skunkworks/go-jira/jira
|
|
||||||
go get -v
|
|
||||||
```
|
```
|
||||||
|
* To install the binary to you ~/bin directory you can run:
|
||||||
* If you do have a **GOPATH** setup, these are the standard steps to build:
|
```bash
|
||||||
|
make install
|
||||||
```
|
|
||||||
cd $GOPATH
|
|
||||||
git clone git@github.com:Netflix-Skunkworks/go-jira.git src/github.com/Netflix-Skunkworks/go-jira
|
|
||||||
cd src/github.com/Netflix-Skunkworks/go-jira/jira
|
|
||||||
go get -v
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
@@ -117,6 +105,8 @@ endpoint: https://jira.mycompany.com
|
|||||||
EOM
|
EOM
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Then use `jira login` to authenticate yourself.
|
||||||
|
|
||||||
### Dynamic Configuration
|
### Dynamic Configuration
|
||||||
|
|
||||||
If the **.jira.d/config.yml** file is executable, then **go-jira** will attempt to execute the file and use the stdout for configuration. You can use this to customize templates or other overrides depending on what type of operation you are running. For example if you would like to use the "table" template when ever you run `jira ls`, then you can create a template like this:
|
If the **.jira.d/config.yml** file is executable, then **go-jira** will attempt to execute the file and use the stdout for configuration. You can use this to customize templates or other overrides depending on what type of operation you are running. For example if you would like to use the "table" template when ever you run `jira ls`, then you can create a template like this:
|
||||||
@@ -205,13 +195,13 @@ General Options:
|
|||||||
-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: $USER)
|
-u --user=USER Username to use for authentication (default: $USER)
|
||||||
-v --verbose Increase output logging
|
-v --verbose Increase output logging
|
||||||
|
|
||||||
Query Options:
|
Query Options:
|
||||||
-a --assignee=USER Username assigned the issue
|
-a --assignee=USER Username assigned the issue
|
||||||
-c --component=COMPONENT Component to Search for
|
-c --component=COMPONENT Component to Search for
|
||||||
-f --queryfields=FIELDS Fields that are used in "list" template: (default: summary,created,priority,status,reporter,assignee)
|
-f --queryfields=FIELDS Fields that are used in "list" template: (default: summary,created,updated,priority,status,reporter,assignee)
|
||||||
-i --issuetype=ISSUETYPE The Issue Type
|
-i --issuetype=ISSUETYPE The Issue Type
|
||||||
-l --limit=VAL Maximum number of results to return in query (default: 500)
|
-l --limit=VAL Maximum number of results to return in query (default: 500)
|
||||||
-p --project=PROJECT Project to Search for
|
-p --project=PROJECT Project to Search for
|
||||||
|
|||||||
+123
-44
@@ -1,24 +1,30 @@
|
|||||||
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"
|
|
||||||
"gopkg.in/coryb/yaml.v2"
|
"gopkg.in/coryb/yaml.v2"
|
||||||
|
"gopkg.in/op/go-logging.v1"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"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
|
||||||
@@ -28,20 +34,31 @@ type Cli struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func New(opts map[string]interface{}) *Cli {
|
func New(opts map[string]interface{}) *Cli {
|
||||||
homedir := os.Getenv("HOME")
|
homedir := homedir()
|
||||||
cookieJar, _ := cookiejar.New(nil)
|
cookieJar, _ := cookiejar.New(nil)
|
||||||
endpoint, _ := opts["endpoint"].(string)
|
endpoint, _ := opts["endpoint"].(string)
|
||||||
url, _ := url.Parse(strings.TrimRight(endpoint, "/"))
|
url, _ := url.Parse(strings.TrimRight(endpoint, "/"))
|
||||||
|
|
||||||
|
transport := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{},
|
||||||
|
}
|
||||||
|
|
||||||
if project, ok := opts["project"].(string); ok {
|
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: filepath.Join(homedir, ".jira.d", "cookies.js"),
|
||||||
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())
|
||||||
@@ -72,6 +89,7 @@ func (c *Cli) saveCookies(cookies []*http.Cookie) {
|
|||||||
}
|
}
|
||||||
jsonWrite(c.cookieFile, mergedCookies)
|
jsonWrite(c.cookieFile, mergedCookies)
|
||||||
} else {
|
} else {
|
||||||
|
mkdir(path.Dir(c.cookieFile))
|
||||||
jsonWrite(c.cookieFile, cookies)
|
jsonWrite(c.cookieFile, cookies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,15 +101,15 @@ func (c *Cli) loadCookies() []*http.Cookie {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to open %s: %s", c.cookieFile, err)
|
log.Errorf("Failed to open %s: %s", c.cookieFile, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
cookies := make([]*http.Cookie, 0)
|
cookies := make([]*http.Cookie, 0)
|
||||||
err = json.Unmarshal(bytes, &cookies)
|
err = json.Unmarshal(bytes, &cookies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to parse json from file %s: %s", c.cookieFile, err)
|
log.Errorf("Failed to parse json from file %s: %s", c.cookieFile, err)
|
||||||
}
|
}
|
||||||
log.Debug("Loading Cookies: %s", cookies)
|
log.Debugf("Loading Cookies: %s", cookies)
|
||||||
return cookies
|
return cookies
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,15 +121,33 @@ func (c *Cli) put(uri string, content string) (*http.Response, error) {
|
|||||||
return c.makeRequestWithContent("PUT", uri, content)
|
return c.makeRequestWithContent("PUT", uri, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cli) delete(uri string) (*http.Response, error) {
|
||||||
|
method := "DELETE"
|
||||||
|
req, _ := http.NewRequest(method, uri, nil)
|
||||||
|
log.Infof("%s %s", req.Method, req.URL.String())
|
||||||
|
if resp, err := c.makeRequest(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
if resp.StatusCode == 401 {
|
||||||
|
if err := c.CmdLogin(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, _ = http.NewRequest(method, uri, nil)
|
||||||
|
return c.makeRequest(req)
|
||||||
|
}
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Cli) makeRequestWithContent(method string, uri string, content string) (*http.Response, error) {
|
func (c *Cli) makeRequestWithContent(method string, uri string, content string) (*http.Response, error) {
|
||||||
buffer := bytes.NewBufferString(content)
|
buffer := bytes.NewBufferString(content)
|
||||||
req, _ := http.NewRequest(method, uri, buffer)
|
req, _ := http.NewRequest(method, uri, buffer)
|
||||||
|
|
||||||
log.Info("%s %s", req.Method, req.URL.String())
|
log.Infof("%s %s", req.Method, req.URL.String())
|
||||||
if log.IsEnabledFor(logging.DEBUG) {
|
if log.IsEnabledFor(logging.DEBUG) {
|
||||||
logBuffer := bytes.NewBuffer(make([]byte, 0, len(content)))
|
logBuffer := bytes.NewBuffer(make([]byte, 0, len(content)))
|
||||||
req.Write(logBuffer)
|
req.Write(logBuffer)
|
||||||
log.Debug("%s", logBuffer)
|
log.Debugf("%s", logBuffer)
|
||||||
// need to recreate the buffer since the offset is now at the end
|
// need to recreate the buffer since the offset is now at the end
|
||||||
// need to be able to rewind the buffer offset, dont know how yet
|
// need to be able to rewind the buffer offset, dont know how yet
|
||||||
req, _ = http.NewRequest(method, uri, bytes.NewBufferString(content))
|
req, _ = http.NewRequest(method, uri, bytes.NewBufferString(content))
|
||||||
@@ -133,11 +169,11 @@ func (c *Cli) makeRequestWithContent(method string, uri string, content string)
|
|||||||
|
|
||||||
func (c *Cli) get(uri string) (*http.Response, error) {
|
func (c *Cli) get(uri string) (*http.Response, error) {
|
||||||
req, _ := http.NewRequest("GET", uri, nil)
|
req, _ := http.NewRequest("GET", uri, nil)
|
||||||
log.Info("%s %s", req.Method, req.URL.String())
|
log.Infof("%s %s", req.Method, req.URL.String())
|
||||||
if log.IsEnabledFor(logging.DEBUG) {
|
if log.IsEnabledFor(logging.DEBUG) {
|
||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
req.Write(logBuffer)
|
req.Write(logBuffer)
|
||||||
log.Debug("%s", logBuffer)
|
log.Debugf("%s", logBuffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp, err := c.makeRequest(req); err != nil {
|
if resp, err := c.makeRequest(req); err != nil {
|
||||||
@@ -156,11 +192,11 @@ func (c *Cli) get(uri string) (*http.Response, error) {
|
|||||||
func (c *Cli) makeRequest(req *http.Request) (resp *http.Response, err error) {
|
func (c *Cli) makeRequest(req *http.Request) (resp *http.Response, err error) {
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
if resp, err = c.ua.Do(req); err != nil {
|
if resp, err = c.ua.Do(req); err != nil {
|
||||||
log.Error("Failed to %s %s: %s", req.Method, req.URL.String(), err)
|
log.Errorf("Failed to %s %s: %s", req.Method, req.URL.String(), err)
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 && resp.StatusCode != 401 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 && resp.StatusCode != 401 {
|
||||||
log.Error("response status: %s", resp.Status)
|
log.Errorf("response status: %s", resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
runtime.SetFinalizer(resp, func(r *http.Response) {
|
runtime.SetFinalizer(resp, func(r *http.Response) {
|
||||||
@@ -174,33 +210,37 @@ 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 getLookedUpTemplate(name string, dflt string) string {
|
||||||
|
if file, err := FindClosestParentPath(filepath.Join(".jira.d", "templates", name)); err == nil {
|
||||||
|
return readFile(file)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(fmt.Sprintf("/etc/go-jira/templates/%s", name)); err == nil {
|
||||||
|
file := fmt.Sprintf("/etc/go-jira/templates/%s", name)
|
||||||
|
return readFile(file)
|
||||||
|
}
|
||||||
|
return dflt
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Cli) getTemplate(name string) string {
|
func (c *Cli) getTemplate(name string) string {
|
||||||
if override, ok := c.opts["template"].(string); 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 {
|
||||||
if file, err := FindClosestParentPath(fmt.Sprintf(".jira.d/templates/%s", override)); err == nil {
|
if t := getLookedUpTemplate(override, all_templates[override]); t != "" {
|
||||||
return readFile(file)
|
return t
|
||||||
}
|
|
||||||
if dflt, ok := all_templates[override]; ok {
|
|
||||||
return dflt
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if file, err := FindClosestParentPath(fmt.Sprintf(".jira.d/templates/%s", name)); err != nil {
|
// create-bug etc are special, if we dont find it in the path
|
||||||
// create-bug etc are special, if we dont find it in the path
|
// then just return the create template
|
||||||
// then just return a generic create template
|
if strings.HasPrefix(name, "create-") {
|
||||||
if strings.HasPrefix(name, "create-") {
|
return getLookedUpTemplate(name, c.getTemplate("create"))
|
||||||
if file, err := FindClosestParentPath(".jira.d/templates/create"); err != nil {
|
|
||||||
return all_templates["create"]
|
|
||||||
} else {
|
|
||||||
return readFile(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return all_templates[name]
|
|
||||||
} else {
|
|
||||||
return readFile(file)
|
|
||||||
}
|
}
|
||||||
|
return getLookedUpTemplate(name, all_templates[name])
|
||||||
}
|
}
|
||||||
|
|
||||||
type NoChangesFound struct{}
|
type NoChangesFound struct{}
|
||||||
@@ -211,21 +251,21 @@ func (f NoChangesFound) Error() string {
|
|||||||
|
|
||||||
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 := filepath.Join(homedir(), ".jira.d", "tmp")
|
||||||
if err := mkdir(tmpdir); err != nil {
|
if err := mkdir(tmpdir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fh, err := ioutil.TempFile(tmpdir, tmpFilePrefix)
|
fh, err := ioutil.TempFile(tmpdir, tmpFilePrefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to make temp file in %s: %s", tmpdir, err)
|
log.Errorf("Failed to make temp file in %s: %s", tmpdir, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer fh.Close()
|
defer fh.Close()
|
||||||
|
|
||||||
tmpFileName := fmt.Sprintf("%s.yml", fh.Name())
|
tmpFileName := fmt.Sprintf("%s.yml", fh.Name())
|
||||||
if err := os.Rename(fh.Name(), tmpFileName); err != nil {
|
if err := os.Rename(fh.Name(), tmpFileName); err != nil {
|
||||||
log.Error("Failed to rename %s to %s: %s", fh.Name(), fmt.Sprintf("%s.yml", fh.Name()), err)
|
log.Errorf("Failed to rename %s to %s: %s", fh.Name(), fmt.Sprintf("%s.yml", fh.Name()), err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -262,11 +302,11 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m
|
|||||||
if editing {
|
if editing {
|
||||||
shell, _ := shellquote.Split(editor)
|
shell, _ := shellquote.Split(editor)
|
||||||
shell = append(shell, tmpFileName)
|
shell = append(shell, tmpFileName)
|
||||||
log.Debug("Running: %#v", shell)
|
log.Debugf("Running: %#v", shell)
|
||||||
cmd := exec.Command(shell[0], shell[1:]...)
|
cmd := exec.Command(shell[0], shell[1:]...)
|
||||||
cmd.Stdout, cmd.Stderr, cmd.Stdin = os.Stdout, os.Stderr, os.Stdin
|
cmd.Stdout, cmd.Stderr, cmd.Stdin = os.Stdout, os.Stderr, os.Stdin
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
log.Error("Failed to edit template with %s: %s", editor, err)
|
log.Errorf("Failed to edit template with %s: %s", editor, err)
|
||||||
if promptYN("edit again?", true) {
|
if promptYN("edit again?", true) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -282,14 +322,14 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m
|
|||||||
|
|
||||||
edited := make(map[string]interface{})
|
edited := make(map[string]interface{})
|
||||||
if fh, err := ioutil.ReadFile(tmpFileName); err != nil {
|
if fh, err := ioutil.ReadFile(tmpFileName); err != nil {
|
||||||
log.Error("Failed to read tmpfile %s: %s", tmpFileName, err)
|
log.Errorf("Failed to read tmpfile %s: %s", tmpFileName, err)
|
||||||
if editing && promptYN("edit again?", true) {
|
if editing && promptYN("edit again?", true) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
if err := yaml.Unmarshal(fh, &edited); err != nil {
|
if err := yaml.Unmarshal(fh, &edited); err != nil {
|
||||||
log.Error("Failed to parse YAML: %s", err)
|
log.Errorf("Failed to parse YAML: %s", err)
|
||||||
if editing && promptYN("edit again?", true) {
|
if editing && promptYN("edit again?", true) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -307,7 +347,7 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m
|
|||||||
// you can add the "abort: true" flag to the document
|
// you can add the "abort: true" flag to the document
|
||||||
// and we will abort now
|
// and we will abort now
|
||||||
if val, ok := edited["abort"].(bool); ok && val {
|
if val, ok := edited["abort"].(bool); ok && val {
|
||||||
log.Info("abort flag found in template, quiting")
|
log.Infof("abort flag found in template, quiting")
|
||||||
return fmt.Errorf("abort flag found in template, quiting")
|
return fmt.Errorf("abort flag found in template, quiting")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,7 +357,7 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m
|
|||||||
for k := range f {
|
for k := range f {
|
||||||
if _, ok := mf.(map[string]interface{})[k]; !ok {
|
if _, ok := mf.(map[string]interface{})[k]; !ok {
|
||||||
err := fmt.Errorf("Field %s is not editable", k)
|
err := fmt.Errorf("Field %s is not editable", k)
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
if editing && promptYN("edit again?", true) {
|
if editing && promptYN("edit again?", true) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -333,7 +373,7 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := templateProcessor(json); err != nil {
|
if err := templateProcessor(json); err != nil {
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
if editing && promptYN("edit again?", true) {
|
if editing && promptYN("edit again?", true) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -354,6 +394,27 @@ 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)
|
||||||
|
if x := c.expansions(); len(x) > 0 {
|
||||||
|
uri = fmt.Sprintf("%s?expand=%s", uri, strings.Join(x, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := responseToJson(c.get(uri))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Cli) FindIssues() (interface{}, error) {
|
func (c *Cli) FindIssues() (interface{}, error) {
|
||||||
var query string
|
var query string
|
||||||
var ok bool
|
var ok bool
|
||||||
@@ -362,7 +423,7 @@ func (c *Cli) FindIssues() (interface{}, error) {
|
|||||||
qbuff := bytes.NewBufferString("resolution = unresolved")
|
qbuff := bytes.NewBufferString("resolution = unresolved")
|
||||||
if project, ok := c.opts["project"]; !ok {
|
if project, ok := c.opts["project"]; !ok {
|
||||||
err := fmt.Errorf("Missing required arguments, either 'query' or 'project' are required")
|
err := fmt.Errorf("Missing required arguments, either 'query' or 'project' are required")
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
qbuff.WriteString(fmt.Sprintf(" AND project = '%s'", project))
|
qbuff.WriteString(fmt.Sprintf(" AND project = '%s'", project))
|
||||||
@@ -407,6 +468,7 @@ func (c *Cli) FindIssues() (interface{}, error) {
|
|||||||
"startAt": "0",
|
"startAt": "0",
|
||||||
"maxResults": c.opts["max_results"],
|
"maxResults": c.opts["max_results"],
|
||||||
"fields": fields,
|
"fields": fields,
|
||||||
|
"expand": c.expansions(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -420,6 +482,10 @@ func (c *Cli) FindIssues() (interface{}, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cli) GetOptString(optName string, dflt string) string {
|
||||||
|
return c.getOptString(optName, dflt)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Cli) getOptString(optName string, dflt string) string {
|
func (c *Cli) getOptString(optName string, dflt string) string {
|
||||||
if val, ok := c.opts[optName].(string); ok {
|
if val, ok := c.opts[optName].(string); ok {
|
||||||
return val
|
return val
|
||||||
@@ -428,6 +494,10 @@ func (c *Cli) getOptString(optName string, dflt string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cli) GetOptBool(optName string, dflt bool) bool {
|
||||||
|
return c.getOptBool(optName, dflt)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Cli) getOptBool(optName string, dflt bool) bool {
|
func (c *Cli) getOptBool(optName string, dflt bool) bool {
|
||||||
if val, ok := c.opts[optName].(bool); ok {
|
if val, ok := c.opts[optName].(bool); ok {
|
||||||
return val
|
return val
|
||||||
@@ -435,3 +505,12 @@ func (c *Cli) getOptBool(optName string, dflt bool) bool {
|
|||||||
return dflt
|
return dflt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expansions returns a comma-separated list of values for field expansion
|
||||||
|
func (c *Cli) expansions() []string {
|
||||||
|
expansions := make([]string, 0)
|
||||||
|
if x, ok := c.opts["expand"].(string); ok {
|
||||||
|
expansions = strings.Split(x, ",")
|
||||||
|
}
|
||||||
|
return expansions
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package cli
|
package jira
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"code.google.com/p/gopass"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
// "github.com/kr/pretty"
|
// "github.com/kr/pretty"
|
||||||
@@ -17,26 +19,27 @@ func (c *Cli) CmdLogin() error {
|
|||||||
req, _ := http.NewRequest("GET", uri, nil)
|
req, _ := http.NewRequest("GET", uri, nil)
|
||||||
user, _ := c.opts["user"].(string)
|
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())
|
log.Infof("%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)
|
out, _ := httputil.DumpResponse(resp, true)
|
||||||
log.Debug("%s", out)
|
log.Debugf("%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
|
||||||
if reason := resp.Header.Get("X-Authentication-Denied-Reason"); reason != "" {
|
if reason := resp.Header.Get("X-Authentication-Denied-Reason"); reason != "" {
|
||||||
err := fmt.Errorf("Authenticaion Failed: %s", reason)
|
err := fmt.Errorf("Authenticaion Failed: %s", reason)
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err := fmt.Errorf("Authentication Failed: Unknown Reason")
|
err := fmt.Errorf("Authentication Failed: Unknown Reason")
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
return err
|
return err
|
||||||
|
|
||||||
} else if resp.StatusCode == 200 {
|
} else if resp.StatusCode == 200 {
|
||||||
@@ -57,7 +60,7 @@ func (c *Cli) CmdLogin() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdFields() error {
|
func (c *Cli) CmdFields() error {
|
||||||
log.Debug("fields called")
|
log.Debugf("fields called")
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/field", c.endpoint)
|
uri := fmt.Sprintf("%s/rest/api/2/field", c.endpoint)
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -68,7 +71,7 @@ func (c *Cli) CmdFields() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdList() error {
|
func (c *Cli) CmdList() error {
|
||||||
log.Debug("list called")
|
log.Debugf("list called")
|
||||||
if data, err := c.FindIssues(); err != nil {
|
if data, err := c.FindIssues(); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
@@ -77,19 +80,17 @@ func (c *Cli) CmdList() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdView(issue string) error {
|
func (c *Cli) CmdView(issue string) error {
|
||||||
log.Debug("view called")
|
log.Debugf("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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdEdit(issue string) error {
|
func (c *Cli) CmdEdit(issue string) error {
|
||||||
log.Debug("edit called")
|
log.Debugf("edit called")
|
||||||
|
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/editmeta", c.endpoint, issue)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/editmeta", c.endpoint, issue)
|
||||||
editmeta, err := responseToJson(c.get(uri))
|
editmeta, err := responseToJson(c.get(uri))
|
||||||
@@ -114,7 +115,8 @@ func (c *Cli) CmdEdit(issue string) error {
|
|||||||
issueData,
|
issueData,
|
||||||
func(json string) error {
|
func(json string) error {
|
||||||
if c.getOptBool("dryrun", false) {
|
if c.getOptBool("dryrun", false) {
|
||||||
log.Debug("Dryrun mode, skipping PUT")
|
log.Debugf("PUT: %s", json)
|
||||||
|
log.Debugf("Dryrun mode, skipping PUT")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
resp, err := c.put(uri, json)
|
resp, err := c.put(uri, json)
|
||||||
@@ -124,13 +126,15 @@ 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))
|
||||||
resp.Write(logBuffer)
|
resp.Write(logBuffer)
|
||||||
err := fmt.Errorf("Unexpected Response From PUT")
|
err := fmt.Errorf("Unexpected Response From PUT")
|
||||||
log.Error("%s:\n%s", err, logBuffer)
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -138,7 +142,7 @@ func (c *Cli) CmdEdit(issue string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdEditMeta(issue string) error {
|
func (c *Cli) CmdEditMeta(issue string) error {
|
||||||
log.Debug("editMeta called")
|
log.Debugf("editMeta called")
|
||||||
c.Browse(issue)
|
c.Browse(issue)
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/editmeta", c.endpoint, issue)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/editmeta", c.endpoint, issue)
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
@@ -150,7 +154,7 @@ func (c *Cli) CmdEditMeta(issue string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdTransitionMeta(issue string) error {
|
func (c *Cli) CmdTransitionMeta(issue string) error {
|
||||||
log.Debug("tranisionMeta called")
|
log.Debugf("tranisionMeta called")
|
||||||
c.Browse(issue)
|
c.Browse(issue)
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/transitions?expand=transitions.fields", c.endpoint, issue)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/transitions?expand=transitions.fields", c.endpoint, issue)
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
@@ -163,7 +167,7 @@ func (c *Cli) CmdTransitionMeta(issue string) error {
|
|||||||
|
|
||||||
func (c *Cli) CmdIssueTypes() error {
|
func (c *Cli) CmdIssueTypes() error {
|
||||||
project := c.opts["project"].(string)
|
project := c.opts["project"].(string)
|
||||||
log.Debug("issueTypes called")
|
log.Debugf("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))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -177,8 +181,8 @@ func (c *Cli) CmdCreateMeta() error {
|
|||||||
project := c.opts["project"].(string)
|
project := c.opts["project"].(string)
|
||||||
issuetype := c.getOptString("issuetype", "Bug")
|
issuetype := c.getOptString("issuetype", "Bug")
|
||||||
|
|
||||||
log.Debug("createMeta called")
|
log.Debugf("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, url.QueryEscape(issuetype))
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -187,7 +191,7 @@ func (c *Cli) CmdCreateMeta() error {
|
|||||||
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 {
|
||||||
err = fmt.Errorf("Project '%s' or issuetype '%s' unknown. Unable to createmeta.", project, issuetype)
|
err = fmt.Errorf("Project '%s' or issuetype '%s' unknown. Unable to createmeta.", project, issuetype)
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
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 {
|
||||||
@@ -198,8 +202,18 @@ func (c *Cli) CmdCreateMeta() error {
|
|||||||
return runTemplate(c.getTemplate("createmeta"), data, nil)
|
return runTemplate(c.getTemplate("createmeta"), data, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cli) CmdComponents(project string) error {
|
||||||
|
log.Debugf("Components called")
|
||||||
|
uri := fmt.Sprintf("%s/rest/api/2/project/%s/components", c.endpoint, project)
|
||||||
|
data, err := responseToJson(c.get(uri))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return runTemplate(c.getTemplate("components"), data, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdTransitions(issue string) error {
|
func (c *Cli) CmdTransitions(issue string) error {
|
||||||
log.Debug("Transitions called")
|
log.Debugf("Transitions called")
|
||||||
c.Browse(issue)
|
c.Browse(issue)
|
||||||
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)
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
@@ -212,9 +226,9 @@ func (c *Cli) CmdTransitions(issue string) error {
|
|||||||
func (c *Cli) CmdCreate() error {
|
func (c *Cli) CmdCreate() error {
|
||||||
project := c.opts["project"].(string)
|
project := c.opts["project"].(string)
|
||||||
issuetype := c.getOptString("issuetype", "Bug")
|
issuetype := c.getOptString("issuetype", "Bug")
|
||||||
log.Debug("create called")
|
log.Debugf("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, url.QueryEscape(issuetype))
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -227,10 +241,15 @@ func (c *Cli) CmdCreate() error {
|
|||||||
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 {
|
||||||
err = fmt.Errorf("Project '%s' or issuetype '%s' unknown. Unable to create issue.", project, issuetype)
|
err = fmt.Errorf("Project '%s' or issuetype '%s' unknown. Unable to create issue.", project, issuetype)
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
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.Errorf("%s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
issueData["meta"] = val.([]interface{})[0]
|
issueData["meta"] = val.([]interface{})[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,10 +260,11 @@ func (c *Cli) CmdCreate() error {
|
|||||||
fmt.Sprintf("create-%s-", sanitizedType),
|
fmt.Sprintf("create-%s-", sanitizedType),
|
||||||
issueData,
|
issueData,
|
||||||
func(json string) error {
|
func(json string) error {
|
||||||
log.Debug("JSON: %s", json)
|
log.Debugf("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) {
|
if c.getOptBool("dryrun", false) {
|
||||||
log.Debug("Dryrun mode, skipping POST")
|
log.Debugf("POST: %s", json)
|
||||||
|
log.Debugf("Dryrun mode, skipping POST")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
resp, err := c.post(uri, json)
|
resp, err := c.post(uri, json)
|
||||||
@@ -257,17 +277,23 @@ func (c *Cli) CmdCreate() 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 {
|
||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
resp.Write(logBuffer)
|
resp.Write(logBuffer)
|
||||||
err := fmt.Errorf("Unexpected Response From POST")
|
err := fmt.Errorf("Unexpected Response From POST")
|
||||||
log.Error("%s:\n%s", err, logBuffer)
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -276,7 +302,7 @@ func (c *Cli) CmdCreate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdIssueLinkTypes() error {
|
func (c *Cli) CmdIssueLinkTypes() error {
|
||||||
log.Debug("Transitions called")
|
log.Debugf("Transitions called")
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issueLinkType", c.endpoint)
|
uri := fmt.Sprintf("%s/rest/api/2/issueLinkType", c.endpoint)
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -286,7 +312,7 @@ func (c *Cli) CmdIssueLinkTypes() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdBlocks(blocker string, issue string) error {
|
func (c *Cli) CmdBlocks(blocker string, issue string) error {
|
||||||
log.Debug("blocks called")
|
log.Debugf("blocks called")
|
||||||
|
|
||||||
json, err := jsonEncode(map[string]interface{}{
|
json, err := jsonEncode(map[string]interface{}{
|
||||||
"type": map[string]string{
|
"type": map[string]string{
|
||||||
@@ -305,7 +331,8 @@ 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) {
|
if c.getOptBool("dryrun", false) {
|
||||||
log.Debug("Dryrun mode, skipping POST")
|
log.Debugf("POST: %s", json)
|
||||||
|
log.Debugf("Dryrun mode, skipping POST")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
resp, err := c.post(uri, json)
|
resp, err := c.post(uri, json)
|
||||||
@@ -314,19 +341,21 @@ func (c *Cli) CmdBlocks(blocker string, 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)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
resp.Write(logBuffer)
|
resp.Write(logBuffer)
|
||||||
err := fmt.Errorf("Unexpected Response From POST")
|
err := fmt.Errorf("Unexpected Response From POST")
|
||||||
log.Error("%s:\n%s", err, logBuffer)
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdDups(duplicate string, issue string) error {
|
func (c *Cli) CmdDups(duplicate string, issue string) error {
|
||||||
log.Debug("dups called")
|
log.Debugf("dups called")
|
||||||
|
|
||||||
json, err := jsonEncode(map[string]interface{}{
|
json, err := jsonEncode(map[string]interface{}{
|
||||||
"type": map[string]string{
|
"type": map[string]string{
|
||||||
@@ -345,7 +374,8 @@ 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) {
|
if c.getOptBool("dryrun", false) {
|
||||||
log.Debug("Dryrun mode, skipping POST")
|
log.Debugf("POST: %s", json)
|
||||||
|
log.Debugf("Dryrun mode, skipping POST")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
resp, err := c.post(uri, json)
|
resp, err := c.post(uri, json)
|
||||||
@@ -354,50 +384,114 @@ func (c *Cli) CmdDups(duplicate string, 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)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
resp.Write(logBuffer)
|
resp.Write(logBuffer)
|
||||||
err := fmt.Errorf("Unexpected Response From POST")
|
err := fmt.Errorf("Unexpected Response From POST")
|
||||||
log.Error("%s:\n%s", err, logBuffer)
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdWatch(issue string) error {
|
func (c *Cli) CmdWatch(issue string, watcher string, remove bool) error {
|
||||||
watcher := c.getOptString("watcher", c.opts["user"].(string))
|
log.Debugf("watch called: watcher: %q, remove: %n", watcher, remove)
|
||||||
log.Debug("watch called")
|
|
||||||
|
|
||||||
|
var uri string
|
||||||
json, err := jsonEncode(watcher)
|
json, err := jsonEncode(watcher)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/watchers", c.endpoint, issue)
|
|
||||||
if c.getOptBool("dryrun", false) {
|
if c.getOptBool("dryrun", false) {
|
||||||
log.Debug("Dryrun mode, skipping POST")
|
if !remove {
|
||||||
|
log.Debugf("POST: %s", json)
|
||||||
|
log.Debugf("Dryrun mode, skipping POST")
|
||||||
|
} else {
|
||||||
|
log.Debugf("DELETE: %s", watcher)
|
||||||
|
log.Debugf("Dryrun mode, skipping POST")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
resp, err := c.post(uri, json)
|
|
||||||
|
var resp *http.Response
|
||||||
|
if !remove {
|
||||||
|
uri = fmt.Sprintf("%s/rest/api/2/issue/%s/watchers", c.endpoint, issue)
|
||||||
|
resp, err = c.post(uri, json)
|
||||||
|
} else {
|
||||||
|
uri = fmt.Sprintf("%s/rest/api/2/issue/%s/watchers?username=%s", c.endpoint, issue, watcher)
|
||||||
|
resp, err = c.delete(uri)
|
||||||
|
}
|
||||||
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)
|
||||||
err := fmt.Errorf("Unexpected Response From POST")
|
if !remove {
|
||||||
log.Error("%s:\n%s", err, logBuffer)
|
err = fmt.Errorf("Unexpected Response From POST")
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("Unexpected Response From DELETE")
|
||||||
|
}
|
||||||
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cli) CmdVote(issue string, up bool) error {
|
||||||
|
log.Debugf("vote called, with up: %n", up)
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/votes", c.endpoint, issue)
|
||||||
|
if c.getOptBool("dryrun", false) {
|
||||||
|
if up {
|
||||||
|
log.Debugf("POST: %s", "")
|
||||||
|
log.Debugf("Dryrun mode, skipping POST")
|
||||||
|
} else {
|
||||||
|
log.Debugf("DELETE: %s", "")
|
||||||
|
log.Debugf("Dryrun mode, skipping DELETE")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var resp *http.Response
|
||||||
|
var err error
|
||||||
|
if up {
|
||||||
|
resp, err = c.post(uri, "")
|
||||||
|
} else {
|
||||||
|
resp, err = c.delete(uri)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
|
resp.Write(logBuffer)
|
||||||
|
if up {
|
||||||
|
err = fmt.Errorf("Unexpected Response From POST")
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("Unexpected Response From DELETE")
|
||||||
|
}
|
||||||
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdTransition(issue string, trans string) error {
|
func (c *Cli) CmdTransition(issue string, trans string) error {
|
||||||
log.Debug("transition called")
|
log.Debugf("transition called")
|
||||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/transitions?expand=transitions.fields", c.endpoint, issue)
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/transitions?expand=transitions.fields", c.endpoint, issue)
|
||||||
data, err := responseToJson(c.get(uri))
|
data, err := responseToJson(c.get(uri))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -412,7 +506,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{})
|
||||||
@@ -420,16 +514,17 @@ func (c *Cli) CmdTransition(issue string, trans string) error {
|
|||||||
}
|
}
|
||||||
if transId == "" {
|
if transId == "" {
|
||||||
err := fmt.Errorf("Invalid Transition '%s', Available: %s", trans, strings.Join(found, ", "))
|
err := fmt.Errorf("Invalid Transition '%s', Available: %s", trans, strings.Join(found, ", "))
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePost := func(json string) error {
|
handlePost := func(json string) error {
|
||||||
log.Debug("POST: %s", json)
|
log.Debugf("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) {
|
if c.getOptBool("dryrun", false) {
|
||||||
log.Debug("Dryrun mode, skipping POST")
|
log.Debugf("POST: %s", json)
|
||||||
|
log.Debugf("Dryrun mode, skipping POST")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
resp, err := c.post(uri, json)
|
resp, err := c.post(uri, json)
|
||||||
@@ -438,12 +533,14 @@ func (c *Cli) CmdTransition(issue string, trans string) error {
|
|||||||
}
|
}
|
||||||
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)
|
||||||
err := fmt.Errorf("Unexpected Response From POST")
|
err := fmt.Errorf("Unexpected Response From POST")
|
||||||
log.Error("%s:\n%s", err, logBuffer)
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -472,13 +569,14 @@ func (c *Cli) CmdTransition(issue string, trans string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cli) CmdComment(issue string) error {
|
func (c *Cli) CmdComment(issue string) error {
|
||||||
log.Debug("comment called")
|
log.Debugf("comment called")
|
||||||
|
|
||||||
handlePost := func(json string) error {
|
handlePost := func(json string) error {
|
||||||
log.Debug("JSON: %s", json)
|
log.Debugf("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) {
|
if c.getOptBool("dryrun", false) {
|
||||||
log.Debug("Dryrun mode, skipping POST")
|
log.Debugf("POST: %s", json)
|
||||||
|
log.Debugf("Dryrun mode, skipping POST")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
resp, err := c.post(uri, json)
|
resp, err := c.post(uri, json)
|
||||||
@@ -488,13 +586,15 @@ 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))
|
||||||
resp.Write(logBuffer)
|
resp.Write(logBuffer)
|
||||||
err := fmt.Errorf("Unexpected Response From POST")
|
err := fmt.Errorf("Unexpected Response From POST")
|
||||||
log.Error("%s:\n%s", err, logBuffer)
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -518,8 +618,116 @@ func (c *Cli) CmdComment(issue string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cli) CmdComponent(action string, project string, name string, desc string, lead string) error {
|
||||||
|
log.Debugf("component called")
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case "add":
|
||||||
|
default:
|
||||||
|
return errors.New(fmt.Sprintf("CmdComponent: %q is not a valid action", action))
|
||||||
|
}
|
||||||
|
|
||||||
|
json, err := jsonEncode(map[string]interface{}{
|
||||||
|
"name": name,
|
||||||
|
"description": desc,
|
||||||
|
"leadUserName": lead,
|
||||||
|
"project": project,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("%s/rest/api/2/component", c.endpoint)
|
||||||
|
if c.getOptBool("dryrun", false) {
|
||||||
|
log.Debugf("POST: %s", json)
|
||||||
|
log.Debugf("Dryrun mode, skipping POST")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
resp, err := c.post(uri, json)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.StatusCode == 201 {
|
||||||
|
if !c.opts["quiet"].(bool) {
|
||||||
|
fmt.Printf("OK %s %s\n", project, name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logBuffer := bytes.NewBuffer(make([]byte, 0))
|
||||||
|
resp.Write(logBuffer)
|
||||||
|
err := fmt.Errorf("Unexpected Response From POST")
|
||||||
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cli) CmdLabels(action string, issue string, labels []string) error {
|
||||||
|
log.Debugf("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.Debugf("JSON: %s", json)
|
||||||
|
uri := fmt.Sprintf("%s/rest/api/2/issue/%s", c.endpoint, issue)
|
||||||
|
if c.getOptBool("dryrun", false) {
|
||||||
|
log.Debugf("PUT: %s", json)
|
||||||
|
log.Debugf("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.Errorf("%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.Debugf("assign called")
|
||||||
|
|
||||||
json, err := jsonEncode(map[string]interface{}{
|
json, err := jsonEncode(map[string]interface{}{
|
||||||
"name": user,
|
"name": user,
|
||||||
@@ -530,7 +738,8 @@ 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) {
|
if c.getOptBool("dryrun", false) {
|
||||||
log.Debug("Dryrun mode, skipping PUT")
|
log.Debugf("PUT: %s", json)
|
||||||
|
log.Debugf("Dryrun mode, skipping PUT")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
resp, err := c.put(uri, json)
|
resp, err := c.put(uri, json)
|
||||||
@@ -539,12 +748,14 @@ func (c *Cli) CmdAssign(issue string, user string) error {
|
|||||||
}
|
}
|
||||||
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)
|
||||||
err := fmt.Errorf("Unexpected Response From PUT")
|
err := fmt.Errorf("Unexpected Response From PUT")
|
||||||
log.Error("%s:\n%s", err, logBuffer)
|
log.Errorf("%s:\n%s", err, logBuffer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -566,13 +777,35 @@ func (c *Cli) CmdExportTemplates() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if fh, err := os.OpenFile(templateFile, os.O_WRONLY|os.O_CREATE, 0644); err != nil {
|
if fh, err := os.OpenFile(templateFile, os.O_WRONLY|os.O_CREATE, 0644); err != nil {
|
||||||
log.Error("Failed to open %s for writing: %s", templateFile, err)
|
log.Errorf("Failed to open %s for writing: %s", templateFile, err)
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
defer fh.Close()
|
defer fh.Close()
|
||||||
log.Notice("Creating %s", templateFile)
|
log.Noticef("Creating %s", templateFile)
|
||||||
fh.Write([]byte(template))
|
fh.Write([]byte(template))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cli) CmdRequest(uri, content string) (err error) {
|
||||||
|
log.Debugf("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)
|
||||||
|
}
|
||||||
+124
-36
@@ -3,18 +3,20 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Netflix-Skunkworks/go-jira/jira/cli"
|
"github.com/Netflix-Skunkworks/go-jira"
|
||||||
"github.com/coryb/optigo"
|
"github.com/coryb/optigo"
|
||||||
"github.com/op/go-logging"
|
|
||||||
"gopkg.in/coryb/yaml.v2"
|
"gopkg.in/coryb/yaml.v2"
|
||||||
|
"gopkg.in/op/go-logging.v1"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var log = logging.MustGetLogger("jira")
|
var (
|
||||||
var format = "%{color}%{time:2006-01-02T15:04:05.000Z07:00} %{level:-5s} [%{shortfile}]%{color:reset} %{message}"
|
log = logging.MustGetLogger("jira")
|
||||||
|
format = "%{color}%{time:2006-01-02T15:04:05.000Z07:00} %{level:-5s} [%{shortfile}]%{color:reset} %{message}"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
logBackend := logging.NewLogBackend(os.Stderr, "", 0)
|
logBackend := logging.NewLogBackend(os.Stderr, "", 0)
|
||||||
@@ -28,7 +30,7 @@ func main() {
|
|||||||
|
|
||||||
user := os.Getenv("USER")
|
user := os.Getenv("USER")
|
||||||
home := os.Getenv("HOME")
|
home := os.Getenv("HOME")
|
||||||
defaultQueryFields := "summary,created,priority,status,reporter,assignee"
|
defaultQueryFields := "summary,created,updated,priority,status,reporter,assignee"
|
||||||
defaultSort := "priority asc, created"
|
defaultSort := "priority asc, created"
|
||||||
defaultMaxResults := 500
|
defaultMaxResults := 500
|
||||||
|
|
||||||
@@ -54,7 +56,8 @@ Usage:
|
|||||||
jira create [--noedit] [-p PROJECT] <Create Options>
|
jira create [--noedit] [-p PROJECT] <Create Options>
|
||||||
jira DUPLICATE dups ISSUE
|
jira DUPLICATE dups ISSUE
|
||||||
jira BLOCKER blocks ISSUE
|
jira BLOCKER blocks ISSUE
|
||||||
jira watch ISSUE [-w WATCHER]
|
jira vote ISSUE [--down]
|
||||||
|
jira watch ISSUE [-w WATCHER] [--remove]
|
||||||
jira (trans|transition) TRANSITION ISSUE [--noedit] <Edit Options>
|
jira (trans|transition) TRANSITION ISSUE [--noedit] <Edit Options>
|
||||||
jira ack ISSUE [--edit] <Edit Options>
|
jira ack ISSUE [--edit] <Edit Options>
|
||||||
jira close ISSUE [--edit] <Edit Options>
|
jira close ISSUE [--edit] <Edit Options>
|
||||||
@@ -63,23 +66,28 @@ Usage:
|
|||||||
jira start ISSUE [--edit] <Edit Options>
|
jira start ISSUE [--edit] <Edit Options>
|
||||||
jira stop ISSUE [--edit] <Edit Options>
|
jira stop ISSUE [--edit] <Edit Options>
|
||||||
jira comment ISSUE [--noedit] <Edit Options>
|
jira comment ISSUE [--noedit] <Edit Options>
|
||||||
|
jira (set,add,remove) labels ISSUE [LABEL] ...
|
||||||
jira take ISSUE
|
jira take ISSUE
|
||||||
jira (assign|give) ISSUE ASSIGNEE
|
jira (assign|give) ISSUE ASSIGNEE
|
||||||
jira fields
|
jira fields
|
||||||
jira issuelinktypes
|
jira issuelinktypes
|
||||||
jira transmeta ISSUE
|
jira transmeta ISSUE
|
||||||
jira editmeta ISSUE
|
jira editmeta ISSUE
|
||||||
|
jira add component [-p PROJECT] NAME DESCRIPTION LEAD
|
||||||
|
jira components [-p PROJECT]
|
||||||
jira issuetypes [-p PROJECT]
|
jira issuetypes [-p PROJECT]
|
||||||
jira createmeta [-p PROJECT] [-i ISSUETYPE]
|
jira createmeta [-p PROJECT] [-i ISSUETYPE]
|
||||||
jira transitions ISSUE
|
jira transitions ISSUE
|
||||||
jira export-templates [-d DIR] [-t template]
|
jira export-templates [-d DIR] [-t template]
|
||||||
jira (b|browse) ISSUE
|
jira (b|browse) ISSUE
|
||||||
jira login
|
jira login
|
||||||
|
jira request [-M METHOD] URI [DATA]
|
||||||
jira ISSUE
|
jira ISSUE
|
||||||
|
|
||||||
General Options:
|
General Options:
|
||||||
-b --browse Open your browser to the Jira issue
|
-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
|
||||||
|
-k --insecure disable TLS certificate verification
|
||||||
-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: %s)
|
-u --user=USER Username to use for authenticaion (default: %s)
|
||||||
@@ -133,6 +141,10 @@ Command Options:
|
|||||||
"start": "start",
|
"start": "start",
|
||||||
"stop": "stop",
|
"stop": "stop",
|
||||||
"comment": "comment",
|
"comment": "comment",
|
||||||
|
"label": "labels",
|
||||||
|
"labels": "labels",
|
||||||
|
"component": "component",
|
||||||
|
"components": "components",
|
||||||
"take": "take",
|
"take": "take",
|
||||||
"assign": "assign",
|
"assign": "assign",
|
||||||
"give": "assign",
|
"give": "assign",
|
||||||
@@ -146,6 +158,9 @@ Command Options:
|
|||||||
"export-templates": "export-templates",
|
"export-templates": "export-templates",
|
||||||
"browse": "browse",
|
"browse": "browse",
|
||||||
"login": "login",
|
"login": "login",
|
||||||
|
"req": "request",
|
||||||
|
"request": "request",
|
||||||
|
"vote": "vote",
|
||||||
}
|
}
|
||||||
|
|
||||||
defaults := map[string]interface{}{
|
defaults := map[string]interface{}{
|
||||||
@@ -154,11 +169,11 @@ Command Options:
|
|||||||
"directory": fmt.Sprintf("%s/.jira.d/templates", home),
|
"directory": fmt.Sprintf("%s/.jira.d/templates", home),
|
||||||
"sort": defaultSort,
|
"sort": defaultSort,
|
||||||
"max_results": defaultMaxResults,
|
"max_results": defaultMaxResults,
|
||||||
|
"method": "GET",
|
||||||
|
"quiet": false,
|
||||||
}
|
}
|
||||||
opts := make(map[string]interface{})
|
opts := make(map[string]interface{})
|
||||||
|
|
||||||
overrides := make(map[string]string)
|
|
||||||
|
|
||||||
setopt := func(name string, value interface{}) {
|
setopt := func(name string, value interface{}) {
|
||||||
opts[name] = value
|
opts[name] = value
|
||||||
}
|
}
|
||||||
@@ -166,7 +181,7 @@ Command Options:
|
|||||||
op := optigo.NewDirectAssignParser(map[string]interface{}{
|
op := optigo.NewDirectAssignParser(map[string]interface{}{
|
||||||
"h|help": usage,
|
"h|help": usage,
|
||||||
"version": func() {
|
"version": func() {
|
||||||
fmt.Println("version: 0.0.12")
|
fmt.Println(fmt.Sprintf("version: %s", jira.VERSION))
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
},
|
},
|
||||||
"v|verbose+": func() {
|
"v|verbose+": func() {
|
||||||
@@ -177,6 +192,7 @@ Command Options:
|
|||||||
"editor=s": setopt,
|
"editor=s": setopt,
|
||||||
"u|user=s": setopt,
|
"u|user=s": setopt,
|
||||||
"endpoint=s": setopt,
|
"endpoint=s": setopt,
|
||||||
|
"k|insecure": setopt,
|
||||||
"t|template=s": setopt,
|
"t|template=s": setopt,
|
||||||
"q|query=s": setopt,
|
"q|query=s": setopt,
|
||||||
"p|project=s": setopt,
|
"p|project=s": setopt,
|
||||||
@@ -184,23 +200,28 @@ Command Options:
|
|||||||
"a|assignee=s": setopt,
|
"a|assignee=s": setopt,
|
||||||
"i|issuetype=s": setopt,
|
"i|issuetype=s": setopt,
|
||||||
"w|watcher=s": setopt,
|
"w|watcher=s": setopt,
|
||||||
|
"remove": setopt,
|
||||||
"r|reporter=s": setopt,
|
"r|reporter=s": setopt,
|
||||||
"f|queryfields=s": setopt,
|
"f|queryfields=s": setopt,
|
||||||
|
"x|expand=s": setopt,
|
||||||
"s|sort=s": setopt,
|
"s|sort=s": setopt,
|
||||||
"l|limit|max_results=i": setopt,
|
"l|limit|max_results=i": setopt,
|
||||||
"o|override=s%": &overrides,
|
"o|override=s%": &opts,
|
||||||
"noedit": setopt,
|
"noedit": setopt,
|
||||||
"edit": setopt,
|
"edit": setopt,
|
||||||
"m|comment=s": setopt,
|
"m|comment=s": setopt,
|
||||||
"d|dir|directory=s": setopt,
|
"d|dir|directory=s": setopt,
|
||||||
|
"M|method=s": setopt,
|
||||||
|
"S|saveFile=s": setopt,
|
||||||
|
"Q|quiet": setopt,
|
||||||
|
"down": setopt,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := op.ProcessAll(os.Args[1:]); err != nil {
|
if err := op.ProcessAll(os.Args[1:]); err != nil {
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
usage(false)
|
usage(false)
|
||||||
}
|
}
|
||||||
args := op.Args
|
args := op.Args
|
||||||
opts["overrides"] = overrides
|
|
||||||
|
|
||||||
var command string
|
var command string
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
@@ -209,6 +230,7 @@ Command Options:
|
|||||||
args = args[1:]
|
args = args[1:]
|
||||||
} else if len(args) > 1 {
|
} else if len(args) > 1 {
|
||||||
// look at second arg for "dups" and "blocks" commands
|
// look at second arg for "dups" and "blocks" commands
|
||||||
|
// also for 'set/add/remove' actions like 'labels'
|
||||||
if alias, ok := jiraCommands[args[1]]; ok {
|
if alias, ok := jiraCommands[args[1]]; ok {
|
||||||
command = alias
|
command = alias
|
||||||
args = append(args[:1], args[2:]...)
|
args = append(args[:1], args[2:]...)
|
||||||
@@ -216,7 +238,7 @@ Command Options:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if command == "" {
|
if command == "" && len(args) > 0 {
|
||||||
command = args[0]
|
command = args[0]
|
||||||
args = args[1:]
|
args = args[1:]
|
||||||
}
|
}
|
||||||
@@ -228,48 +250,57 @@ Command Options:
|
|||||||
if value, ok := opts["command"].(string); ok {
|
if value, ok := opts["command"].(string); ok {
|
||||||
command = value
|
command = value
|
||||||
} else if _, ok := jiraCommands[command]; !ok || command == "" {
|
} else if _, ok := jiraCommands[command]; !ok || command == "" {
|
||||||
args = append([]string{command}, args...)
|
if command != "" {
|
||||||
|
args = append([]string{command}, args...)
|
||||||
|
}
|
||||||
command = "view"
|
command = "view"
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply defaults
|
// apply defaults
|
||||||
for k, v := range defaults {
|
for k, v := range defaults {
|
||||||
if _, ok := opts[k]; !ok {
|
if _, ok := opts[k]; !ok {
|
||||||
log.Debug("Setting %q to %#v from defaults", k, v)
|
log.Debugf("Setting %q to %#v from defaults", k, v)
|
||||||
opts[k] = v
|
opts[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("opts: %v", opts)
|
log.Debugf("opts: %v", opts)
|
||||||
log.Debug("args: %v", args)
|
log.Debugf("args: %v", args)
|
||||||
|
|
||||||
if _, ok := opts["endpoint"]; !ok {
|
if _, ok := opts["endpoint"]; !ok {
|
||||||
log.Error("endpoint option required. Either use --endpoint or set a enpoint option in your ~/.jira.d/config.yml file")
|
log.Errorf("endpoint option required. Either use --endpoint or set a endpoint option in your ~/.jira.d/config.yml file")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
c := cli.New(opts)
|
c := jira.New(opts)
|
||||||
|
|
||||||
log.Debug("opts: %s", opts)
|
log.Debugf("opts: %s", opts)
|
||||||
|
|
||||||
setEditing := func(dflt bool) {
|
setEditing := func(dflt bool) {
|
||||||
log.Debug("Default Editing: %t", dflt)
|
log.Debugf("Default Editing: %t", dflt)
|
||||||
if dflt {
|
if dflt {
|
||||||
if val, ok := opts["noedit"].(bool); ok && val {
|
if val, ok := opts["noedit"].(bool); ok && val {
|
||||||
log.Debug("Setting edit = false")
|
log.Debugf("Setting edit = false")
|
||||||
opts["edit"] = false
|
opts["edit"] = false
|
||||||
} else {
|
} else {
|
||||||
log.Debug("Setting edit = true")
|
log.Debugf("Setting edit = true")
|
||||||
opts["edit"] = true
|
opts["edit"] = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if _, ok := opts["edit"].(bool); !ok {
|
if _, ok := opts["edit"].(bool); !ok {
|
||||||
log.Debug("Setting edit = %t", dflt)
|
log.Debugf("Setting edit = %t", dflt)
|
||||||
opts["edit"] = dflt
|
opts["edit"] = dflt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requireArgs := func(count int) {
|
||||||
|
if len(args) < count {
|
||||||
|
log.Errorf("Not enough arguments. %d required, %d provided", count, len(args))
|
||||||
|
usage(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
switch command {
|
switch command {
|
||||||
case "login":
|
case "login":
|
||||||
@@ -289,7 +320,7 @@ Command Options:
|
|||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
if err = c.CmdEdit(issue.(map[string]interface{})["key"].(string)); err != nil {
|
if err = c.CmdEdit(issue.(map[string]interface{})["key"].(string)); err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case cli.NoChangesFound:
|
case jira.NoChangesFound:
|
||||||
log.Warning("No Changes found: %s", err)
|
log.Warning("No Changes found: %s", err)
|
||||||
err = nil
|
err = nil
|
||||||
continue
|
continue
|
||||||
@@ -300,8 +331,10 @@ Command Options:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "editmeta":
|
case "editmeta":
|
||||||
|
requireArgs(1)
|
||||||
err = c.CmdEditMeta(args[0])
|
err = c.CmdEditMeta(args[0])
|
||||||
case "transmeta":
|
case "transmeta":
|
||||||
|
requireArgs(1)
|
||||||
err = c.CmdTransitionMeta(args[0])
|
err = c.CmdTransitionMeta(args[0])
|
||||||
case "issuelinktypes":
|
case "issuelinktypes":
|
||||||
err = c.CmdIssueLinkTypes()
|
err = c.CmdIssueLinkTypes()
|
||||||
@@ -313,58 +346,113 @@ Command Options:
|
|||||||
setEditing(true)
|
setEditing(true)
|
||||||
err = c.CmdCreate()
|
err = c.CmdCreate()
|
||||||
case "transitions":
|
case "transitions":
|
||||||
|
requireArgs(1)
|
||||||
err = c.CmdTransitions(args[0])
|
err = c.CmdTransitions(args[0])
|
||||||
case "blocks":
|
case "blocks":
|
||||||
|
requireArgs(2)
|
||||||
err = c.CmdBlocks(args[0], args[1])
|
err = c.CmdBlocks(args[0], args[1])
|
||||||
case "dups":
|
case "dups":
|
||||||
|
requireArgs(2)
|
||||||
if err = c.CmdDups(args[0], args[1]); err == nil {
|
if err = c.CmdDups(args[0], args[1]); err == nil {
|
||||||
opts["resolution"] = "Duplicate"
|
opts["resolution"] = "Duplicate"
|
||||||
err = c.CmdTransition(args[0], "close")
|
err = c.CmdTransition(args[0], "close")
|
||||||
}
|
}
|
||||||
case "watch":
|
case "watch":
|
||||||
err = c.CmdWatch(args[0])
|
requireArgs(1)
|
||||||
|
watcher := c.GetOptString("watcher", opts["user"].(string))
|
||||||
|
remove := c.GetOptBool("remove", false)
|
||||||
|
err = c.CmdWatch(args[0], watcher, remove)
|
||||||
case "transition":
|
case "transition":
|
||||||
|
requireArgs(2)
|
||||||
setEditing(true)
|
setEditing(true)
|
||||||
err = c.CmdTransition(args[0], args[1])
|
err = c.CmdTransition(args[1], args[0])
|
||||||
case "close":
|
case "close":
|
||||||
|
requireArgs(1)
|
||||||
setEditing(false)
|
setEditing(false)
|
||||||
err = c.CmdTransition(args[0], "close")
|
err = c.CmdTransition(args[0], "close")
|
||||||
case "acknowledge":
|
case "acknowledge":
|
||||||
|
requireArgs(1)
|
||||||
setEditing(false)
|
setEditing(false)
|
||||||
err = c.CmdTransition(args[0], "acknowledge")
|
err = c.CmdTransition(args[0], "acknowledge")
|
||||||
case "reopen":
|
case "reopen":
|
||||||
|
requireArgs(1)
|
||||||
setEditing(false)
|
setEditing(false)
|
||||||
err = c.CmdTransition(args[0], "reopen")
|
err = c.CmdTransition(args[0], "reopen")
|
||||||
case "resolve":
|
case "resolve":
|
||||||
|
requireArgs(1)
|
||||||
setEditing(false)
|
setEditing(false)
|
||||||
err = c.CmdTransition(args[0], "resolve")
|
err = c.CmdTransition(args[0], "resolve")
|
||||||
case "start":
|
case "start":
|
||||||
|
requireArgs(1)
|
||||||
setEditing(false)
|
setEditing(false)
|
||||||
err = c.CmdTransition(args[0], "start")
|
err = c.CmdTransition(args[0], "start")
|
||||||
case "stop":
|
case "stop":
|
||||||
|
requireArgs(1)
|
||||||
setEditing(false)
|
setEditing(false)
|
||||||
err = c.CmdTransition(args[0], "stop")
|
err = c.CmdTransition(args[0], "stop")
|
||||||
case "comment":
|
case "comment":
|
||||||
|
requireArgs(1)
|
||||||
setEditing(true)
|
setEditing(true)
|
||||||
err = c.CmdComment(args[0])
|
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 "component":
|
||||||
|
requireArgs(2)
|
||||||
|
action := args[0]
|
||||||
|
project := opts["project"].(string)
|
||||||
|
name := args[1]
|
||||||
|
var lead string
|
||||||
|
var description string
|
||||||
|
if len(args) > 2 {
|
||||||
|
description = args[2]
|
||||||
|
}
|
||||||
|
if len(args) > 3 {
|
||||||
|
lead = args[2]
|
||||||
|
}
|
||||||
|
err = c.CmdComponent(action, project, name, description, lead)
|
||||||
|
case "components":
|
||||||
|
project := opts["project"].(string)
|
||||||
|
err = c.CmdComponents(project)
|
||||||
case "take":
|
case "take":
|
||||||
|
requireArgs(1)
|
||||||
err = c.CmdAssign(args[0], opts["user"].(string))
|
err = c.CmdAssign(args[0], opts["user"].(string))
|
||||||
case "browse":
|
case "browse":
|
||||||
|
requireArgs(1)
|
||||||
opts["browse"] = true
|
opts["browse"] = true
|
||||||
err = c.Browse(args[0])
|
err = c.Browse(args[0])
|
||||||
case "export-tempaltes":
|
case "export-templates":
|
||||||
err = c.CmdExportTemplates()
|
err = c.CmdExportTemplates()
|
||||||
case "assign":
|
case "assign":
|
||||||
|
requireArgs(2)
|
||||||
err = c.CmdAssign(args[0], args[1])
|
err = c.CmdAssign(args[0], args[1])
|
||||||
case "view":
|
case "view":
|
||||||
|
requireArgs(1)
|
||||||
err = c.CmdView(args[0])
|
err = c.CmdView(args[0])
|
||||||
|
case "vote":
|
||||||
|
requireArgs(1)
|
||||||
|
if val, ok := opts["down"]; ok {
|
||||||
|
err = c.CmdVote(args[0], !val.(bool))
|
||||||
|
} else {
|
||||||
|
err = c.CmdVote(args[0], true)
|
||||||
|
}
|
||||||
|
case "request":
|
||||||
|
requireArgs(1)
|
||||||
|
data := ""
|
||||||
|
if len(args) > 1 {
|
||||||
|
data = args[1]
|
||||||
|
}
|
||||||
|
err = c.CmdRequest(args[0], data)
|
||||||
default:
|
default:
|
||||||
log.Error("Unknown command %s", command)
|
log.Errorf("Unknown command %s", command)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
@@ -372,7 +460,7 @@ Command Options:
|
|||||||
|
|
||||||
func parseYaml(file string, opts map[string]interface{}) {
|
func parseYaml(file string, opts map[string]interface{}) {
|
||||||
if fh, err := ioutil.ReadFile(file); err == nil {
|
if fh, err := ioutil.ReadFile(file); err == nil {
|
||||||
log.Debug("Found Config file: %s", file)
|
log.Debugf("Found Config file: %s", file)
|
||||||
yaml.Unmarshal(fh, &opts)
|
yaml.Unmarshal(fh, &opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -399,9 +487,9 @@ func populateEnv(opts map[string]interface{}) {
|
|||||||
|
|
||||||
func loadConfigs(opts map[string]interface{}) {
|
func loadConfigs(opts map[string]interface{}) {
|
||||||
populateEnv(opts)
|
populateEnv(opts)
|
||||||
paths := cli.FindParentPaths(".jira.d/config.yml")
|
paths := jira.FindParentPaths(".jira.d/config.yml")
|
||||||
// prepend
|
// prepend
|
||||||
paths = append([]string{"/etc/jira-cli.yml"}, paths...)
|
paths = append([]string{"/etc/go-jira.yml"}, paths...)
|
||||||
|
|
||||||
// iterate paths in reverse
|
// iterate paths in reverse
|
||||||
for i := len(paths) - 1; i >= 0; i-- {
|
for i := len(paths) - 1; i >= 0; i-- {
|
||||||
@@ -412,21 +500,21 @@ func loadConfigs(opts map[string]interface{}) {
|
|||||||
if stat.Mode()&0111 == 0 {
|
if stat.Mode()&0111 == 0 {
|
||||||
parseYaml(file, tmp)
|
parseYaml(file, tmp)
|
||||||
} else {
|
} else {
|
||||||
log.Debug("Found Executable Config file: %s", file)
|
log.Debugf("Found Executable Config file: %s", file)
|
||||||
// it is executable, so run it and try to parse the output
|
// it is executable, so run it and try to parse the output
|
||||||
cmd := exec.Command(file)
|
cmd := exec.Command(file)
|
||||||
stdout := bytes.NewBufferString("")
|
stdout := bytes.NewBufferString("")
|
||||||
cmd.Stdout = stdout
|
cmd.Stdout = stdout
|
||||||
cmd.Stderr = bytes.NewBufferString("")
|
cmd.Stderr = bytes.NewBufferString("")
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
log.Error("%s is exectuable, but it failed to execute: %s\n%s", file, err, cmd.Stderr)
|
log.Errorf("%s is exectuable, but it failed to execute: %s\n%s", file, err, cmd.Stderr)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
yaml.Unmarshal(stdout.Bytes(), &tmp)
|
yaml.Unmarshal(stdout.Bytes(), &tmp)
|
||||||
}
|
}
|
||||||
for k, v := range tmp {
|
for k, v := range tmp {
|
||||||
if _, ok := opts[k]; !ok {
|
if _, ok := opts[k]; !ok {
|
||||||
log.Debug("Setting %q to %#v from %s", k, v, file)
|
log.Debugf("Setting %q to %#v from %s", k, v, file)
|
||||||
opts[k] = v
|
opts[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,
|
||||||
@@ -12,10 +12,12 @@ var all_templates = map[string]string{
|
|||||||
"view": default_view_template,
|
"view": default_view_template,
|
||||||
"edit": default_edit_template,
|
"edit": default_edit_template,
|
||||||
"transitions": default_transitions_template,
|
"transitions": default_transitions_template,
|
||||||
|
"components": default_components_template,
|
||||||
"issuetypes": default_issuetypes_template,
|
"issuetypes": default_issuetypes_template,
|
||||||
"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"
|
||||||
@@ -81,6 +83,9 @@ fields:
|
|||||||
const default_transitions_template = `{{ range .transitions }}{{.id }}: {{.name}}
|
const default_transitions_template = `{{ range .transitions }}{{.id }}: {{.name}}
|
||||||
{{end}}`
|
{{end}}`
|
||||||
|
|
||||||
|
const default_components_template = `{{ range . }}{{.id }}: {{.name}}
|
||||||
|
{{end}}`
|
||||||
|
|
||||||
const default_issuetypes_template = `{{ range .projects }}{{ range .issuetypes }}{{color "+bh"}}{{.name | append ":" | printf "%-13s" }}{{color "reset"}} {{.description}}
|
const default_issuetypes_template = `{{ range .projects }}{{ range .issuetypes }}{{color "+bh"}}{{.name | append ":" | printf "%-13s" }}{{color "reset"}} {{.description}}
|
||||||
{{end}}{{end}}`
|
{{end}}{{end}}`
|
||||||
|
|
||||||
+77
-28
@@ -1,4 +1,4 @@
|
|||||||
package cli
|
package jira
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@@ -7,39 +7,51 @@ 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"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func homedir() string {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return os.Getenv("USERPROFILE")
|
||||||
|
}
|
||||||
|
return os.Getenv("HOME")
|
||||||
|
}
|
||||||
|
|
||||||
func FindParentPaths(fileName string) []string {
|
func FindParentPaths(fileName string) []string {
|
||||||
cwd, _ := os.Getwd()
|
cwd, _ := os.Getwd()
|
||||||
|
|
||||||
paths := make([]string, 0)
|
paths := make([]string, 0)
|
||||||
|
|
||||||
// special case if homedir is not in current path then check there anyway
|
// special case if homedir is not in current path then check there anyway
|
||||||
homedir := os.Getenv("HOME")
|
homedir := homedir()
|
||||||
if !strings.HasPrefix(cwd, homedir) {
|
if !filepath.HasPrefix(cwd, homedir) {
|
||||||
file := fmt.Sprintf("%s/%s", homedir, fileName)
|
path := filepath.Join(homedir, fileName)
|
||||||
if _, err := os.Stat(file); err == nil {
|
if _, err := os.Stat(path); err == nil {
|
||||||
paths = append(paths, file)
|
paths = append(paths, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var dir string
|
path := filepath.Join(cwd, fileName)
|
||||||
for _, part := range strings.Split(cwd, string(os.PathSeparator)) {
|
if _, err := os.Stat(path); err == nil {
|
||||||
if dir == "/" {
|
paths = append(paths, path)
|
||||||
dir = fmt.Sprintf("/%s", part)
|
}
|
||||||
} else {
|
for true {
|
||||||
dir = fmt.Sprintf("%s/%s", dir, part)
|
cwd = filepath.Dir(cwd)
|
||||||
|
path := filepath.Join(cwd, fileName)
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
paths = append(paths, path)
|
||||||
}
|
}
|
||||||
file := fmt.Sprintf("%s/%s", dir, fileName)
|
if cwd[len(cwd)-1] == filepath.Separator {
|
||||||
if _, err := os.Stat(file); err == nil {
|
break
|
||||||
paths = append(paths, file)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return paths
|
return paths
|
||||||
@@ -56,8 +68,9 @@ func FindClosestParentPath(fileName string) (string, error) {
|
|||||||
func readFile(file string) string {
|
func readFile(file string) string {
|
||||||
var bytes []byte
|
var bytes []byte
|
||||||
var err error
|
var err error
|
||||||
|
log.Debugf("readFile: reading %q", file)
|
||||||
if bytes, err = ioutil.ReadFile(file); err != nil {
|
if bytes, err = ioutil.ReadFile(file); err != nil {
|
||||||
log.Error("Failed to read file %s: %s", file, err)
|
log.Errorf("Failed to read file %s: %s", file, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
return string(bytes)
|
return string(bytes)
|
||||||
@@ -100,8 +113,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
|
||||||
}
|
}
|
||||||
@@ -152,6 +176,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
|
||||||
@@ -171,13 +202,16 @@ 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.Errorf("Failed to parse template: %s", err)
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
if err := tmpl.Execute(out, data); err != nil {
|
if err := tmpl.Execute(out, data); err != nil {
|
||||||
log.Error("Failed to execute template: %s", err)
|
log.Errorf("Failed to execute template: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,7 +227,7 @@ func responseToJson(resp *http.Response, err error) (interface{}, error) {
|
|||||||
if resp.StatusCode == 400 {
|
if resp.StatusCode == 400 {
|
||||||
if val, ok := data.(map[string]interface{})["errorMessages"]; ok {
|
if val, ok := data.(map[string]interface{})["errorMessages"]; ok {
|
||||||
for _, errMsg := range val.([]interface{}) {
|
for _, errMsg := range val.([]interface{}) {
|
||||||
log.Error("%s", errMsg)
|
log.Errorf("%s", errMsg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,7 +240,7 @@ func jsonDecode(io io.Reader) interface{} {
|
|||||||
var data interface{}
|
var data interface{}
|
||||||
err = json.Unmarshal(content, &data)
|
err = json.Unmarshal(content, &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("JSON Parse Error: %s from %s", err, content)
|
log.Errorf("JSON Parse Error: %s from %s", err, content)
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
@@ -217,7 +251,7 @@ func jsonEncode(data interface{}) (string, error) {
|
|||||||
|
|
||||||
err := enc.Encode(data)
|
err := enc.Encode(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to encode data %s: %s", data, err)
|
log.Errorf("Failed to encode data %s: %s", data, err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return buffer.String(), nil
|
return buffer.String(), nil
|
||||||
@@ -227,13 +261,28 @@ func jsonWrite(file string, data interface{}) {
|
|||||||
fh, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
fh, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
defer fh.Close()
|
defer fh.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to open %s: %s", file, err)
|
log.Errorf("Failed to open %s: %s", file, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
enc := json.NewEncoder(fh)
|
enc := json.NewEncoder(fh)
|
||||||
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.Errorf("Failed to open %s: %s", file, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if out, err := yaml.Marshal(data); err != nil {
|
||||||
|
log.Errorf("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 {
|
||||||
@@ -269,7 +318,7 @@ func yamlFixup(data interface{}) (interface{}, error) {
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
err := fmt.Errorf("YAML: key %s is type '%T', require 'string'", key, k)
|
err := fmt.Errorf("YAML: key %s is type '%T', require 'string'", key, k)
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,7 +344,7 @@ func yamlFixup(data interface{}) (interface{}, error) {
|
|||||||
}
|
}
|
||||||
return copy, 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
|
||||||
@@ -306,16 +355,16 @@ func yamlFixup(data interface{}) (interface{}, error) {
|
|||||||
|
|
||||||
func mkdir(dir string) error {
|
func mkdir(dir string) error {
|
||||||
if stat, err := os.Stat(dir); err != nil && !os.IsNotExist(err) {
|
if stat, err := os.Stat(dir); err != nil && !os.IsNotExist(err) {
|
||||||
log.Error("Failed to stat %s: %s", dir, err)
|
log.Errorf("Failed to stat %s: %s", dir, err)
|
||||||
return err
|
return err
|
||||||
} else if err == nil && !stat.IsDir() {
|
} else if err == nil && !stat.IsDir() {
|
||||||
err := fmt.Errorf("%s exists and is not a directory!", dir)
|
err := fmt.Errorf("%s exists and is not a directory!", dir)
|
||||||
log.Error("%s", err)
|
log.Errorf("%s", err)
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
// dir does not exist, so try to create it
|
// dir does not exist, so try to create it
|
||||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
log.Error("Failed to mkdir -p %s: %s", dir, err)
|
log.Errorf("Failed to mkdir -p %s: %s", dir, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user