Compare commits

...

55 Commits

Author SHA1 Message Date
Cory Bennett b5417ef585 update version tag to prefix with v 2016-01-21 17:38:50 -08:00
Cory Bennett c5af781c41 Updated Changelog 2016-01-21 17:36:23 -08:00
Cory Bennett f2c4df9b3e [issue #28] check to make sure we got back issuetypes for create metadata 2016-01-21 17:35:18 -08:00
Cory Bennett 1dde7e06e6 gofmt 2016-01-21 10:52:15 -08:00
coryb 7bc1897792 Merge pull request #27 from blalor/insecure-skip-verify
Add insecure option for TLS endpoints
2016-01-21 10:15:29 -08:00
Brian Lalor 37aab3580b Add insecure option for TLS endpoints
This gives the option of disabling TLS certificate verification for
the server.

Closes #25
2016-01-21 12:30:23 -05:00
coryb 42990d8ca0 Merge pull request #21 from mikepea/label_command
Add 'labels' command to set/add/remove labels
2016-01-04 08:50:15 -08:00
Mike Pountney 8e662462da Correct naming of parameter: set/add/remove are actions.
'command' is not approprirate for the set/add/remove operations, and is
confusing. Rename to 'action' to remove this confusion.
2016-01-03 20:28:02 -08:00
Mike Pountney ad7bb2b724 Tweak CmdLabels args so that magic happens with CLI
The CLI looks for commands (like 'labels') in the first two positional args. This
is how commands like 'BLOCKER blocks ISSUE' work.

As per @coryb's comments in #21 - this allows us to support set/add/remove in a
consistent way for other types: components for example.

This commit rearranges the command to be as follows:

    jira (set/add/remove) labels ISSUE LABELS...

The options to CmdLabels have been reordered accordingly.
2016-01-03 20:10:49 -08:00
Mike Pountney a8cce44178 Merge branch 'master' into label_command 2016-01-03 19:50:47 -08:00
coryb 35955a7a93 Merge pull request #22 from mikepea/library_break_out
Expose key functionality for library consumption
2015-12-31 12:22:09 -08:00
Mike Pountney f349e25bb9 Expose ViewTicket as per FindIssues
This allows external users of the API to retreive issue details, in the same
manner that FindIssues provides for lists of issues.
2015-12-31 12:06:04 -08:00
Mike Pountney a92a93b282 Add exposed versions of getTemplate and runTemplate
GetTemplate and RunTemplate allow external users to access the template system,
and in particular allow them to provide their own Buffer to write the output to.

I've implemented exposed versions calling the private functions, rather than breaking
the internal API. If this isn't a concern, we should remove getTemplate and runTemplate
in a future commit.
2015-12-31 12:03:22 -08:00
Mike Pountney 8645ef11f1 go fmt 2015-12-31 11:52:55 -08:00
Mike Pountney e042a3e62a Add 'labels' command to set/add/remove labels
This adds 'labels' command, which allows for the setting, addition and removal
of labels on an issue.

'set' action resets the issue labels to the list provided.
'add' action adds the supplied labels to the issue
'remove' action removes the supplied labels from the issue

The API already gracefully handles duplication, removal of non-existant labels, and the
supplying of an empty list of labels (which is useful in the case of 'set')

Eg

jira labels TEST-123 add label1 label2 label3
jira labels TEST-123 remove label1 label2
jira labels TEST-123 set label1 label2 label3
jira labels TEST-123 set # clears any labels on the issue
jira labels TEST-123 add # no-op
jira labels TEST-123 remove # no-op

This mirrors the functionality of the API.
2015-12-24 11:30:52 -08:00
coryb a738d1515e Merge pull request #20 from mikepea/add_join_template_func
Add a 'join' func to the template engine
2015-12-23 12:05:34 -08:00
Mike Pountney d4f15ae5c6 Add a 'join' func to the template engine
I needed this so that I can display JIRA labels in my 'list' template.
2015-12-23 06:15:31 -08:00
Cory Bennett bc70b43868 make "jira" golang package, move code from jira/cli to root, move jira/main.go to main/main.go 2015-12-22 17:56:53 -08:00
Cory Bennett e24b431b7a Updated Changelog 2015-12-09 20:58:11 -08:00
Cory Bennett 101bc1da68 fix jira trans TRANS ISSUE (case sensitivity issue), also go fmt 2015-12-09 20:57:10 -08:00
Cory Bennett 63e035c5c1 Updated Changelog 2015-12-03 14:47:55 -08:00
Cory Bennett 40bafc9b66 need to default "quiet" to false 2015-12-03 14:47:31 -08:00
Cory Bennett 5d863ffed4 Updated Changelog 2015-12-03 12:56:18 -08:00
Cory Bennett 577394b0bd add --quiet command to not print the OK ..
add --saveFile option to print the issue/link to a file on create command
2015-12-03 12:55:14 -08:00
Cory Bennett c1a7e1bbdb fix overrides 2015-12-03 12:21:55 -08:00
Cory Bennett f904f3c089 add abstract request wrapper to allow you to access/process random apis
supported by Jira but not yet supported by go-jira
2015-12-03 11:35:51 -08:00
Cory Bennett e35e518368 Updated Changelog 2015-11-23 17:09:17 -08:00
Cory Bennett 159d142f37 jira edit should not require one arguemnt (allow for --query) 2015-11-23 17:08:40 -08:00
Cory Bennett df84d47552 fix CURVER in case of building from src tar.gz 2015-11-23 14:35:41 -08:00
Cory Bennett fa4ce5647d tweak build 2015-11-23 14:23:29 -08:00
Cory Bennett 713d300a57 Updated Changelog 2015-11-23 14:17:41 -08:00
Cory Bennett c8ae7fc685 [#17] print usage on missing arguments 2015-11-23 14:17:09 -08:00
Cory Bennett 80322b648e bump version 2015-11-17 12:30:11 -08:00
Cory Bennett f2076a0977 Updated Changelog 2015-11-17 12:30:11 -08:00
coryb 886adb5db2 Merge pull request #16 from oschrenk/fix-typo
s/enpoint/endpoint/g
2015-10-16 08:50:52 -07:00
Oliver Schrenk 3bdbdbdaff s/enpoint/endpoint/g 2015-10-16 11:12:25 +02:00
coryb 544b923fab Merge pull request #14 from mikepea/ls_with_updated
Add 'updated' to queryfields; dateFormat template command; bug fix.
2015-10-15 08:37:47 -07:00
Mike Pountney 13a69e6f44 Implement dateFormat template command
Wrapper around time.Format, so that we can make concise lists without
the whole JIRA ISO timestamp, eg:

{{ dateFormat "2006-01-02T15:04" .fields.updated }}
2015-10-15 12:43:03 +01:00
Mike Pountney fae9f94817 Add 'updated' field to default queryfields.
This is pretty essential if you want to get an idea of how stale a ticket is.
2015-10-15 12:35:07 +01:00
Mike Pountney 7d90672736 Fix export-templates option (typo) 2015-10-15 12:05:33 +01:00
Cory Bennett 20faa959aa when yaml element resolves to "\n" strip it out so we dont post it to jira 2015-10-06 11:54:05 -07:00
Cory Bennett aaff47d606 print PUT/POST data when using --dryrun to help debug 2015-10-06 11:50:47 -07:00
Cory Bennett 7bfb2946d4 bump version 2015-09-19 00:17:32 -07:00
Cory Bennett 9884281079 Updated Changelog 2015-09-19 00:17:32 -07:00
Cory Bennett 03fce96eb5 replace dead/deprecated code.google.com/p/gopass with golang.org/x/crypto/ssh/terminal for reading password from stdin 2015-09-19 00:17:08 -07:00
Cory Bennett 8f9e6f7d85 bump version 2015-09-18 23:26:16 -07:00
Cory Bennett 65c0240d34 Updated Changelog 2015-09-18 23:26:16 -07:00
Cory Bennett ccec749a0b fix exception from "jira create" 2015-09-18 23:24:50 -07:00
Cory Bennett 6a9901f171 add some debug messages to help diagnose login failures 2015-09-18 23:18:38 -07:00
Cory Bennett 868764ac86 bump version 2015-09-16 23:22:06 -07:00
Cory Bennett f1e7514a00 Updated Changelog 2015-09-16 23:22:06 -07:00
Cory Bennett 92d10c3498 update version when we create changelog entries 2015-09-16 23:21:36 -07:00
Cory Bennett a9c6b865b6 add --version 2015-09-16 23:16:51 -07:00
Cory Bennett 9cc55a13c1 Updated Changelog 2015-09-16 23:05:02 -07:00
Cory Bennett b116764d3e fix command line parser broken in 0.0.10 2015-09-16 23:04:36 -07:00
8 changed files with 395 additions and 63 deletions
+57
View File
@@ -1,5 +1,62 @@
# Changelog
## 0.0.20 - 2016-01-21
* [issue [#28](https://github.com/Netflix-Skunkworks/go-jira/issues/28)] check to make sure we got back issuetypes for create metadata [Cory Bennett] [[ee0e780](https://github.com/Netflix-Skunkworks/go-jira/commit/ee0e780)]
* Add insecure option for TLS endpoints [Brian Lalor] [[6a88bb9](https://github.com/Netflix-Skunkworks/go-jira/commit/6a88bb9)]
* Correct naming of parameter: set/add/remove are actions. [Mike Pountney] [[303784f](https://github.com/Netflix-Skunkworks/go-jira/commit/303784f)]
* Tweak CmdLabels args so that magic happens with CLI [Mike Pountney] [[40a7c65](https://github.com/Netflix-Skunkworks/go-jira/commit/40a7c65)]
* Expose ViewTicket as per FindIssues [Mike Pountney] [[8977f3d](https://github.com/Netflix-Skunkworks/go-jira/commit/8977f3d)]
* Add exposed versions of getTemplate and runTemplate [Mike Pountney] [[da6cbd5](https://github.com/Netflix-Skunkworks/go-jira/commit/da6cbd5)]
* Add 'labels' command to set/add/remove labels [Mike Pountney] [[230b52d](https://github.com/Netflix-Skunkworks/go-jira/commit/230b52d)]
* Add a 'join' func to the template engine [Mike Pountney] [[a7820fe](https://github.com/Netflix-Skunkworks/go-jira/commit/a7820fe)]
* make "jira" golang package, move code from jira/cli to root, move jira/main.go to main/main.go [Cory Bennett] [[7268b9e](https://github.com/Netflix-Skunkworks/go-jira/commit/7268b9e)]
## 0.0.19 - 2015-12-09
* fix jira trans TRANS ISSUE (case sensitivity issue), also go fmt [Cory Bennett] [[3c30f3b](https://github.com/Netflix-Skunkworks/go-jira/commit/3c30f3b)]
## 0.0.18 - 2015-12-03
* need to default "quiet" to false [Cory Bennett] [[4f4a89b](https://github.com/Netflix-Skunkworks/go-jira/commit/4f4a89b)]
## 0.0.17 - 2015-12-03
* add --quiet command to not print the OK .. add --saveFile option to print the issue/link to a file on create command [Cory Bennett] [[c9ac162](https://github.com/Netflix-Skunkworks/go-jira/commit/c9ac162)]
* fix overrides [Cory Bennett] [[eaddfe6](https://github.com/Netflix-Skunkworks/go-jira/commit/eaddfe6)]
* add abstract request wrapper to allow you to access/process random apis supported by Jira but not yet supported by go-jira [Cory Bennett] [[90ef56a](https://github.com/Netflix-Skunkworks/go-jira/commit/90ef56a)]
## 0.0.16 - 2015-11-23
* jira edit should not require one arguemnt (allow for --query) [Cory Bennett] [[a1eb4a1](https://github.com/Netflix-Skunkworks/go-jira/commit/a1eb4a1)]
## 0.0.15 - 2015-11-23
* [[#17](https://github.com/Netflix-Skunkworks/go-jira/issues/17)] print usage on missing arguments [Cory Bennett] [[fd2a2fe](https://github.com/Netflix-Skunkworks/go-jira/commit/fd2a2fe)]
## 0.0.14 - 2015-11-17
* s/enpoint/endpoint/g [Oliver Schrenk] [[c5d251d](https://github.com/Netflix-Skunkworks/go-jira/commit/c5d251d)]
* Implement dateFormat template command [Mike Pountney] [[68d3bae](https://github.com/Netflix-Skunkworks/go-jira/commit/68d3bae)]
* Add 'updated' field to default queryfields. [Mike Pountney] [[91e2475](https://github.com/Netflix-Skunkworks/go-jira/commit/91e2475)]
* Fix export-templates option (typo) [Mike Pountney] [[4d7fdb8](https://github.com/Netflix-Skunkworks/go-jira/commit/4d7fdb8)]
* when yaml element resolves to "\n" strip it out so we dont post it to jira [Cory Bennett] [[47ced2f](https://github.com/Netflix-Skunkworks/go-jira/commit/47ced2f)]
* print PUT/POST data when using --dryrun to help debug [Cory Bennett] [[618f245](https://github.com/Netflix-Skunkworks/go-jira/commit/618f245)]
## 0.0.13 - 2015-09-19
* replace dead/deprecated code.google.com/p/gopass with golang.org/x/crypto/ssh/terminal for reading password from stdin [Cory Bennett] [[909eb06](https://github.com/Netflix-Skunkworks/go-jira/commit/909eb06)]
## 0.0.12 - 2015-09-18
* fix exception from "jira create" [Cory Bennett] [[9348a4b](https://github.com/Netflix-Skunkworks/go-jira/commit/9348a4b)]
* add some debug messages to help diagnose login failures [Cory Bennett] [[1c08a7d](https://github.com/Netflix-Skunkworks/go-jira/commit/1c08a7d)]
## 0.0.11 - 2015-09-16
* add --version [Cory Bennett] [[8385ee2](https://github.com/Netflix-Skunkworks/go-jira/commit/8385ee2)]
* fix command line parser broken in 0.0.10 [Cory Bennett] [[15ae929](https://github.com/Netflix-Skunkworks/go-jira/commit/15ae929)]
## 0.0.10 - 2015-09-15
* allow for command aliasing in conjunction with executable config files. Issue #5 [Cory Bennett] [[23590d4](https://github.com/Netflix-Skunkworks/go-jira/commit/23590d4)]
+37 -17
View File
@@ -14,54 +14,74 @@ PLATFORMS= \
$(NULL)
DIST=$(shell pwd)/dist
export GOPATH=$(shell pwd)
build:
cd src/github.com/Netflix-Skunkworks/go-jira/jira; \
go get -v
GOBIN ?= $(shell pwd)
NAME=jira
BIN ?= $(GOBIN)/$(NAME)
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 ../../.. $@
go get -v $* $*/main
cross-setup:
for p in $(PLATFORMS); do \
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
all:
rm -rf $(DIST); \
mkdir -p $(DIST); \
cd src/github.com/Netflix-Skunkworks/go-jira/jira; \
go get -d; \
for p in $(PLATFORMS); do \
echo "Building for $$p"; \
GOOS=$${p/-*/} GOARCH=$${p/*-/} go build -v -ldflags -s -o $(DIST)/jira-$$p; \
done
${MAKE} build GOOS=$${p/-*/} GOARCH=$${p/*-/} BIN=$(DIST)/$(NAME)-$$p; \
done
fmt:
gofmt -s -w jira
gofmt -s -w main/*.go *.go
install:
export GOBIN=~/bin && ${MAKE} build
${MAKE} GOBIN=~/bin build
CURVER ?= $(shell git fetch --tags && git tag | tail -1)
NEWVER ?= $(shell echo $(CURVER) | awk -F. '{print $$1"."$$2"."$$3+1}')
TODAY := $(shell date +%Y-%m-%d)
changes:
@git log --pretty=format:"* %s [%cn] [%h]" --no-merges ^$(CURVER) HEAD jira | grep -v gofmt | grep -v "bump version"
@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.md.new; \
echo "## $(NEWVER) - $(TODAY)" >> 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{\#(\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; \
git commit -m "Updated Changelog" CHANGELOG.md; \
git tag $(NEWVER)
git tag v$(NEWVER)
version:
@echo $(patsubst v%,%,$(CURVER))
clean:
rm -rf pkg dist bin && find src \! -path \*/go-jira\* -delete
rm -rf pkg dist bin src ./$(NAME)
+1 -1
View File
@@ -211,7 +211,7 @@ General Options:
Query Options:
-a --assignee=USER Username assigned the issue
-c --component=COMPONENT Component to Search for
-f --queryfields=FIELDS Fields that are used in "list" template: (default: 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
-l --limit=VAL Maximum number of results to return in query (default: 500)
-p --project=PROJECT Project to Search for
+39 -3
View File
@@ -1,7 +1,8 @@
package cli
package jira
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"github.com/kballard/go-shellquote"
@@ -18,7 +19,10 @@ import (
"time"
)
var log = logging.MustGetLogger("jira.cli")
var (
log = logging.MustGetLogger("jira")
VERSION string
)
type Cli struct {
endpoint *url.URL
@@ -33,15 +37,26 @@ func New(opts map[string]interface{}) *Cli {
endpoint, _ := opts["endpoint"].(string)
url, _ := url.Parse(strings.TrimRight(endpoint, "/"))
transport := &http.Transport{
TLSClientConfig: &tls.Config{},
}
if project, ok := opts["project"].(string); ok {
opts["project"] = strings.ToUpper(project)
}
if insecureSkipVerify, ok := opts["insecure"].(bool); ok {
transport.TLSClientConfig.InsecureSkipVerify = insecureSkipVerify
}
cli := &Cli{
endpoint: url,
opts: opts,
cookieFile: fmt.Sprintf("%s/.jira.d/cookies.js", homedir),
ua: &http.Client{Jar: cookieJar},
ua: &http.Client{
Jar: cookieJar,
Transport: transport,
},
}
cli.ua.Jar.SetCookies(url, cli.loadCookies())
@@ -174,6 +189,10 @@ func (c *Cli) makeRequest(req *http.Request) (resp *http.Response, err error) {
return resp, nil
}
func (c *Cli) GetTemplate(name string) string {
return c.getTemplate(name)
}
func (c *Cli) getTemplate(name string) string {
if override, ok := c.opts["template"].(string); ok {
if _, err := os.Stat(override); err == nil {
@@ -354,6 +373,23 @@ func (c *Cli) Browse(issue string) error {
return nil
}
func (c *Cli) SaveData(data interface{}) error {
if val, ok := c.opts["saveFile"].(string); ok && val != "" {
yamlWrite(val, data)
}
return nil
}
func (c *Cli) ViewIssue(issue string) (interface{}, error) {
uri := fmt.Sprintf("%s/rest/api/2/issue/%s", c.endpoint, issue)
data, err := responseToJson(c.get(uri))
if err != nil {
return nil, err
} else {
return data, nil
}
}
func (c *Cli) FindIssues() (interface{}, error) {
var query string
var ok bool
+143 -20
View File
@@ -1,10 +1,11 @@
package cli
package jira
import (
"bytes"
"code.google.com/p/gopass"
"fmt"
"golang.org/x/crypto/ssh/terminal"
"net/http"
"net/http/httputil"
"os"
"strings"
// "github.com/kr/pretty"
@@ -16,13 +17,17 @@ func (c *Cli) CmdLogin() error {
req, _ := http.NewRequest("GET", uri, nil)
user, _ := c.opts["user"].(string)
prompt := fmt.Sprintf("Enter Password for %s: ", user)
passwd, _ := gopass.GetPass(prompt)
fmt.Printf("Enter Password for %s: ", user)
pwbytes, _ := terminal.ReadPassword(int(os.Stdin.Fd()))
passwd := string(pwbytes)
req.SetBasicAuth(user, passwd)
log.Info("%s %s", req.Method, req.URL.String())
if resp, err := c.makeRequest(req); err != nil {
return err
} else {
out, _ := httputil.DumpResponse(resp, true)
log.Debug("%s", out)
if resp.StatusCode == 403 {
// probably got this, need to redirect the user to login manually
// X-Authentication-Denied-Reason: CAPTCHA_CHALLENGE; login-url=https://jira/login.jsp
@@ -75,12 +80,10 @@ func (c *Cli) CmdList() error {
func (c *Cli) CmdView(issue string) error {
log.Debug("view called")
c.Browse(issue)
uri := fmt.Sprintf("%s/rest/api/2/issue/%s", c.endpoint, issue)
data, err := responseToJson(c.get(uri))
data, err := c.ViewIssue(issue)
if err != nil {
return err
}
return runTemplate(c.getTemplate("view"), data, nil)
}
@@ -110,6 +113,7 @@ func (c *Cli) CmdEdit(issue string) error {
issueData,
func(json string) error {
if c.getOptBool("dryrun", false) {
log.Debug("PUT: %s", json)
log.Debug("Dryrun mode, skipping PUT")
return nil
}
@@ -120,7 +124,9 @@ func (c *Cli) CmdEdit(issue string) error {
if resp.StatusCode == 204 {
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
} else {
logBuffer := bytes.NewBuffer(make([]byte, 0))
@@ -218,7 +224,7 @@ func (c *Cli) CmdCreate() error {
issueData := make(map[string]interface{})
issueData["overrides"] = c.opts
issueData["overrides"].(map[string]string)["issuetype"] = issuetype
issueData["overrides"].(map[string]interface{})["issuetype"] = issuetype
if val, ok := data.(map[string]interface{})["projects"]; ok {
if len(val.([]interface{})) == 0 {
@@ -227,6 +233,11 @@ func (c *Cli) CmdCreate() error {
return err
}
if val, ok = val.([]interface{})[0].(map[string]interface{})["issuetypes"]; ok {
if len(val.([]interface{})) == 0 {
err = fmt.Errorf("Project '%s' does not support issuetype '%s'. Unable to create issue.", project, issuetype)
log.Error("%s", err)
return err
}
issueData["meta"] = val.([]interface{})[0]
}
}
@@ -240,6 +251,7 @@ func (c *Cli) CmdCreate() error {
log.Debug("JSON: %s", json)
uri := fmt.Sprintf("%s/rest/api/2/issue", c.endpoint)
if c.getOptBool("dryrun", false) {
log.Debug("POST: %s", json)
log.Debug("Dryrun mode, skipping POST")
return nil
}
@@ -253,10 +265,16 @@ func (c *Cli) CmdCreate() error {
if json, err := responseToJson(resp, nil); err != nil {
return err
} else {
key := json.(map[string]interface{})["key"]
c.Browse(key.(string))
fmt.Printf("OK %s %s/browse/%s\n", key, c.endpoint, key)
key := json.(map[string]interface{})["key"].(string)
link := fmt.Sprintf("%s/browse/%s", 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
} else {
@@ -301,6 +319,7 @@ func (c *Cli) CmdBlocks(blocker string, issue string) error {
uri := fmt.Sprintf("%s/rest/api/2/issueLink", c.endpoint)
if c.getOptBool("dryrun", false) {
log.Debug("POST: %s", json)
log.Debug("Dryrun mode, skipping POST")
return nil
}
@@ -310,7 +329,9 @@ func (c *Cli) CmdBlocks(blocker string, issue string) error {
}
if resp.StatusCode == 201 {
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 {
logBuffer := bytes.NewBuffer(make([]byte, 0))
resp.Write(logBuffer)
@@ -341,6 +362,7 @@ func (c *Cli) CmdDups(duplicate string, issue string) error {
uri := fmt.Sprintf("%s/rest/api/2/issueLink", c.endpoint)
if c.getOptBool("dryrun", false) {
log.Debug("POST: %s", json)
log.Debug("Dryrun mode, skipping POST")
return nil
}
@@ -350,7 +372,9 @@ func (c *Cli) CmdDups(duplicate string, issue string) error {
}
if resp.StatusCode == 201 {
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 {
logBuffer := bytes.NewBuffer(make([]byte, 0))
resp.Write(logBuffer)
@@ -372,6 +396,7 @@ func (c *Cli) CmdWatch(issue string) error {
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/watchers", c.endpoint, issue)
if c.getOptBool("dryrun", false) {
log.Debug("POST: %s", json)
log.Debug("Dryrun mode, skipping POST")
return nil
}
@@ -381,7 +406,9 @@ func (c *Cli) CmdWatch(issue string) error {
}
if resp.StatusCode == 204 {
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 {
logBuffer := bytes.NewBuffer(make([]byte, 0))
resp.Write(logBuffer)
@@ -408,7 +435,7 @@ func (c *Cli) CmdTransition(issue string, trans string) error {
name := transition.(map[string]interface{})["name"].(string)
id := transition.(map[string]interface{})["id"].(string)
found = append(found, name)
if strings.Contains(strings.ToLower(name), trans) {
if strings.Contains(strings.ToLower(name), strings.ToLower(trans)) {
transName = name
transId = id
transMeta = transition.(map[string]interface{})
@@ -425,6 +452,7 @@ func (c *Cli) CmdTransition(issue string, trans string) error {
// os.Exit(0)
uri = fmt.Sprintf("%s/rest/api/2/issue/%s/transitions", c.endpoint, issue)
if c.getOptBool("dryrun", false) {
log.Debug("POST: %s", json)
log.Debug("Dryrun mode, skipping POST")
return nil
}
@@ -434,7 +462,9 @@ func (c *Cli) CmdTransition(issue string, trans string) error {
}
if resp.StatusCode == 204 {
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 {
logBuffer := bytes.NewBuffer(make([]byte, 0))
resp.Write(logBuffer)
@@ -474,6 +504,7 @@ func (c *Cli) CmdComment(issue string) error {
log.Debug("JSON: %s", json)
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/comment", c.endpoint, issue)
if c.getOptBool("dryrun", false) {
log.Debug("POST: %s", json)
log.Debug("Dryrun mode, skipping POST")
return nil
}
@@ -484,7 +515,9 @@ func (c *Cli) CmdComment(issue string) error {
if resp.StatusCode == 201 {
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
} else {
logBuffer := bytes.NewBuffer(make([]byte, 0))
@@ -514,6 +547,71 @@ func (c *Cli) CmdComment(issue string) error {
return nil
}
func (c *Cli) CmdLabels(action string, issue string, labels []string) error {
log.Debug("label called")
if action != "add" && action != "remove" && action != "set" {
return fmt.Errorf("action must be 'add', 'set' or 'remove': %q is invalid", action)
}
handlePut := func(json string) error {
log.Debug("JSON: %s", json)
uri := fmt.Sprintf("%s/rest/api/2/issue/%s", c.endpoint, issue)
if c.getOptBool("dryrun", false) {
log.Debug("PUT: %s", json)
log.Debug("Dryrun mode, skipping POST")
return nil
}
resp, err := c.put(uri, json)
if err != nil {
return err
}
if resp.StatusCode == 204 {
c.Browse(issue)
if !c.opts["quiet"].(bool) {
fmt.Printf("OK %s %s/browse/%s\n", issue, c.endpoint, issue)
}
return nil
} else {
logBuffer := bytes.NewBuffer(make([]byte, 0))
resp.Write(logBuffer)
err := fmt.Errorf("Unexpected Response From PUT")
log.Error("%s:\n%s", err, logBuffer)
return err
}
}
var labels_json string
var err error
if action == "set" {
labelsActions := make([]map[string][]string, 1)
labelsActions[0] = map[string][]string{
"set": labels,
}
labels_json, err = jsonEncode(map[string]interface{}{
"labels": labelsActions,
})
} else {
labelsActions := make([]map[string]string, len(labels))
for i, label := range labels {
labelActionMap := map[string]string{
action: label,
}
labelsActions[i] = labelActionMap
}
labels_json, err = jsonEncode(map[string]interface{}{
"labels": labelsActions,
})
}
if err != nil {
return err
}
json := fmt.Sprintf("{ \"update\": %s }", labels_json)
return handlePut(json)
}
func (c *Cli) CmdAssign(issue string, user string) error {
log.Debug("assign called")
@@ -526,6 +624,7 @@ func (c *Cli) CmdAssign(issue string, user string) error {
uri := fmt.Sprintf("%s/rest/api/2/issue/%s/assignee", c.endpoint, issue)
if c.getOptBool("dryrun", false) {
log.Debug("PUT: %s", json)
log.Debug("Dryrun mode, skipping PUT")
return nil
}
@@ -535,7 +634,9 @@ func (c *Cli) CmdAssign(issue string, user string) error {
}
if resp.StatusCode == 204 {
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 {
logBuffer := bytes.NewBuffer(make([]byte, 0))
resp.Write(logBuffer)
@@ -572,3 +673,25 @@ func (c *Cli) CmdExportTemplates() error {
}
return nil
}
func (c *Cli) CmdRequest(uri, content string) (err error) {
log.Debug("request called")
if !strings.HasPrefix(uri, "http") {
uri = fmt.Sprintf("%s%s", c.endpoint, uri)
}
method := strings.ToUpper(c.opts["method"].(string))
var data interface{}
if method == "GET" {
data, err = responseToJson(c.get(uri))
} else if method == "POST" {
data, err = responseToJson(c.post(uri, content))
} else if method == "PUT" {
data, err = responseToJson(c.put(uri, content))
}
if err != nil {
return err
}
return runTemplate(c.getTemplate("request"), data, nil)
}
+76 -18
View File
@@ -3,7 +3,7 @@ package main
import (
"bytes"
"fmt"
"github.com/Netflix-Skunkworks/go-jira/jira/cli"
"github.com/Netflix-Skunkworks/go-jira"
"github.com/coryb/optigo"
"github.com/op/go-logging"
"gopkg.in/coryb/yaml.v2"
@@ -13,8 +13,10 @@ import (
"strings"
)
var log = logging.MustGetLogger("jira")
var format = "%{color}%{time:2006-01-02T15:04:05.000Z07:00} %{level:-5s} [%{shortfile}]%{color:reset} %{message}"
var (
log = logging.MustGetLogger("jira")
format = "%{color}%{time:2006-01-02T15:04:05.000Z07:00} %{level:-5s} [%{shortfile}]%{color:reset} %{message}"
)
func main() {
logBackend := logging.NewLogBackend(os.Stderr, "", 0)
@@ -28,7 +30,7 @@ func main() {
user := os.Getenv("USER")
home := os.Getenv("HOME")
defaultQueryFields := "summary,created,priority,status,reporter,assignee"
defaultQueryFields := "summary,created,updated,priority,status,reporter,assignee"
defaultSort := "priority asc, created"
defaultMaxResults := 500
@@ -63,6 +65,7 @@ Usage:
jira start ISSUE [--edit] <Edit Options>
jira stop ISSUE [--edit] <Edit Options>
jira comment ISSUE [--noedit] <Edit Options>
jira (set,add,remove) labels ISSUE [LABEL] ...
jira take ISSUE
jira (assign|give) ISSUE ASSIGNEE
jira fields
@@ -75,15 +78,18 @@ Usage:
jira export-templates [-d DIR] [-t template]
jira (b|browse) ISSUE
jira login
jira request [-M METHOD] URI [DATA]
jira ISSUE
General Options:
-b --browse Open your browser to the Jira issue
-e --endpoint=URI URI to use for jira
-k --insecure disable TLS certificate verification
-h --help Show this usage
-t --template=FILE Template file to use for output/editing
-u --user=USER Username to use for authenticaion (default: %s)
-v --verbose Increase output logging
--version Print version
Query Options:
-a --assignee=USER Username assigned the issue
@@ -132,6 +138,8 @@ Command Options:
"start": "start",
"stop": "stop",
"comment": "comment",
"label": "labels",
"labels": "labels",
"take": "take",
"assign": "assign",
"give": "assign",
@@ -145,6 +153,8 @@ Command Options:
"export-templates": "export-templates",
"browse": "browse",
"login": "login",
"req": "request",
"request": "request",
}
defaults := map[string]interface{}{
@@ -153,17 +163,21 @@ Command Options:
"directory": fmt.Sprintf("%s/.jira.d/templates", home),
"sort": defaultSort,
"max_results": defaultMaxResults,
"method": "GET",
"quiet": false,
}
opts := make(map[string]interface{})
overrides := make(map[string]string)
setopt := func(name string, value interface{}) {
opts[name] = value
}
op := optigo.NewDirectAssignParser(map[string]interface{}{
"h|help": usage,
"version": func() {
fmt.Println(fmt.Sprintf("version: %s", jira.VERSION))
os.Exit(0)
},
"v|verbose+": func() {
logging.SetLevel(logging.GetLevel("")+1, "")
},
@@ -172,6 +186,7 @@ Command Options:
"editor=s": setopt,
"u|user=s": setopt,
"endpoint=s": setopt,
"k|insecure": setopt,
"t|template=s": setopt,
"q|query=s": setopt,
"p|project=s": setopt,
@@ -183,11 +198,14 @@ Command Options:
"f|queryfields=s": setopt,
"s|sort=s": setopt,
"l|limit|max_results=i": setopt,
"o|override=s%": &overrides,
"o|override=s%": &opts,
"noedit": setopt,
"edit": setopt,
"m|comment=s": setopt,
"d|dir|directory=s": setopt,
"M|method=s": setopt,
"S|saveFile=s": setopt,
"Q|quiet": setopt,
})
if err := op.ProcessAll(os.Args[1:]); err != nil {
@@ -195,7 +213,6 @@ Command Options:
usage(false)
}
args := op.Args
opts["overrides"] = overrides
var command string
if len(args) > 0 {
@@ -204,6 +221,7 @@ Command Options:
args = args[1:]
} else if len(args) > 1 {
// look at second arg for "dups" and "blocks" commands
// also for 'set/add/remove' actions like 'labels'
if alias, ok := jiraCommands[args[1]]; ok {
command = alias
args = append(args[:1], args[2:]...)
@@ -211,7 +229,7 @@ Command Options:
}
}
if command == "" {
if command == "" && len(args) > 0 {
command = args[0]
args = args[1:]
}
@@ -222,8 +240,10 @@ Command Options:
// check to see if it was set in the configs:
if value, ok := opts["command"].(string); ok {
command = value
} else {
args = append([]string{command}, args...)
} else if _, ok := jiraCommands[command]; !ok || command == "" {
if command != "" {
args = append([]string{command}, args...)
}
command = "view"
}
@@ -239,11 +259,11 @@ Command Options:
log.Debug("args: %v", args)
if _, ok := opts["endpoint"]; !ok {
log.Error("endpoint option required. Either use --endpoint or set a enpoint option in your ~/.jira.d/config.yml file")
log.Error("endpoint option required. Either use --endpoint or set a endpoint option in your ~/.jira.d/config.yml file")
os.Exit(1)
}
c := cli.New(opts)
c := jira.New(opts)
log.Debug("opts: %s", opts)
@@ -265,6 +285,13 @@ Command Options:
}
}
requireArgs := func(count int) {
if len(args) < count {
log.Error("Not enough arguments. %d required, %d provided", count, len(args))
usage(false)
}
}
var err error
switch command {
case "login":
@@ -284,7 +311,7 @@ Command Options:
for _, issue := range issues {
if err = c.CmdEdit(issue.(map[string]interface{})["key"].(string)); err != nil {
switch err.(type) {
case cli.NoChangesFound:
case jira.NoChangesFound:
log.Warning("No Changes found: %s", err)
err = nil
continue
@@ -295,8 +322,10 @@ Command Options:
}
}
case "editmeta":
requireArgs(1)
err = c.CmdEditMeta(args[0])
case "transmeta":
requireArgs(1)
err = c.CmdTransitionMeta(args[0])
case "issuelinktypes":
err = c.CmdIssueLinkTypes()
@@ -308,51 +337,80 @@ Command Options:
setEditing(true)
err = c.CmdCreate()
case "transitions":
requireArgs(1)
err = c.CmdTransitions(args[0])
case "blocks":
requireArgs(2)
err = c.CmdBlocks(args[0], args[1])
case "dups":
requireArgs(2)
if err = c.CmdDups(args[0], args[1]); err == nil {
opts["resolution"] = "Duplicate"
err = c.CmdTransition(args[0], "close")
}
case "watch":
requireArgs(1)
err = c.CmdWatch(args[0])
case "transition":
requireArgs(2)
setEditing(true)
err = c.CmdTransition(args[0], args[1])
err = c.CmdTransition(args[1], args[0])
case "close":
requireArgs(1)
setEditing(false)
err = c.CmdTransition(args[0], "close")
case "acknowledge":
requireArgs(1)
setEditing(false)
err = c.CmdTransition(args[0], "acknowledge")
case "reopen":
requireArgs(1)
setEditing(false)
err = c.CmdTransition(args[0], "reopen")
case "resolve":
requireArgs(1)
setEditing(false)
err = c.CmdTransition(args[0], "resolve")
case "start":
requireArgs(1)
setEditing(false)
err = c.CmdTransition(args[0], "start")
case "stop":
requireArgs(1)
setEditing(false)
err = c.CmdTransition(args[0], "stop")
case "comment":
requireArgs(1)
setEditing(true)
err = c.CmdComment(args[0])
case "labels":
requireArgs(2)
action := args[0]
issue := args[1]
labels := args[2:]
err = c.CmdLabels(action, issue, labels)
case "take":
requireArgs(1)
err = c.CmdAssign(args[0], opts["user"].(string))
case "browse":
requireArgs(1)
opts["browse"] = true
err = c.Browse(args[0])
case "export-tempaltes":
case "export-templates":
err = c.CmdExportTemplates()
case "assign":
requireArgs(2)
err = c.CmdAssign(args[0], args[1])
case "view":
requireArgs(1)
err = c.CmdView(args[0])
case "request":
requireArgs(1)
data := ""
if len(args) > 1 {
data = args[1]
}
err = c.CmdRequest(args[0], data)
default:
log.Error("Unknown command %s", command)
os.Exit(1)
@@ -394,9 +452,9 @@ func populateEnv(opts map[string]interface{}) {
func loadConfigs(opts map[string]interface{}) {
populateEnv(opts)
paths := cli.FindParentPaths(".jira.d/config.yml")
paths := jira.FindParentPaths(".jira.d/config.yml")
// prepend
paths = append([]string{"/etc/jira-cli.yml"}, paths...)
paths = append([]string{"/etc/go-jira.yml"}, paths...)
// iterate paths in reverse
for i := len(paths) - 1; i >= 0; i-- {
+2 -1
View File
@@ -1,4 +1,4 @@
package cli
package jira
var all_templates = map[string]string{
"debug": default_debug_template,
@@ -16,6 +16,7 @@ var all_templates = map[string]string{
"create": default_create_template,
"comment": default_comment_template,
"transition": default_transition_template,
"request": default_debug_template,
}
const default_debug_template = "{{ . | toJson}}\n"
+40 -3
View File
@@ -1,4 +1,4 @@
package cli
package jira
import (
"bufio"
@@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"github.com/mgutz/ansi"
"gopkg.in/coryb/yaml.v2"
"io"
"io/ioutil"
"net/http"
@@ -100,8 +101,19 @@ func fuzzyAge(start string) (string, error) {
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 {
out = os.Stdout
}
@@ -152,6 +164,13 @@ func runTemplate(templateContent string, data interface{}, out io.Writer) error
"split": func(sep string, content string) []string {
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 {
if len(content) > max {
var buffer bytes.Buffer
@@ -171,6 +190,9 @@ func runTemplate(templateContent string, data interface{}, out io.Writer) error
"age": func(content string) (string, error) {
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 {
log.Error("Failed to parse template: %s", err)
@@ -234,6 +256,21 @@ func jsonWrite(file string, data interface{}) {
enc.Encode(data)
}
func yamlWrite(file string, data interface{}) {
fh, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
defer fh.Close()
if err != nil {
log.Error("Failed to open %s: %s", file, err)
os.Exit(1)
}
if out, err := yaml.Marshal(data); err != nil {
log.Error("Failed to marshal yaml %v: %s", data, err)
os.Exit(1)
} else {
fh.Write(out)
}
}
func promptYN(prompt string, yes bool) bool {
reader := bufio.NewReader(os.Stdin)
if !yes {
@@ -295,7 +332,7 @@ func yamlFixup(data interface{}) (interface{}, error) {
}
return copy, nil
case string:
if d == "" {
if d == "" || d == "\n" {
return nil, nil
}
return d, nil