mirror of
https://github.com/Threnklyn/jira.git
synced 2026-05-19 20:53:27 +02:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd9976ae4e | |||
| f3aa2f4c1a | |||
| f6230ca8c6 | |||
| 412174f8a9 | |||
| 52085417e6 | |||
| 7a2490c0e6 | |||
| 437532ae89 | |||
| 69b565eeaa | |||
| cc393a3498 | |||
| c6ba4c681b | |||
| 63bc2ae15a | |||
| 7d6a5d143d | |||
| 0ca0f09aa8 | |||
| 75242a5204 | |||
| e6faa4eab1 | |||
| 9b53a617a7 | |||
| d5eed3a635 | |||
| 4017339b56 | |||
| a40b17deed | |||
| 33807cbbec | |||
| 989c072b94 | |||
| d187eee826 | |||
| 6d34ef3f28 | |||
| 7852883202 | |||
| cb70941aad | |||
| 24fd8f6fad |
+2
-6
@@ -1,6 +1,6 @@
|
||||
sudo: true
|
||||
before_install:
|
||||
- sudo apt-get update && sudo apt-get install -y libgnome-keyring-dev
|
||||
- sudo apt-get update && sudo apt-get install -y pass gnupg
|
||||
|
||||
language: go
|
||||
|
||||
@@ -14,8 +14,4 @@ script:
|
||||
- make vet
|
||||
- make lint
|
||||
- make
|
||||
- JIRACLOUD=1 ./t/100basic.t -w -a 2>&1
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- t/.maven-cache
|
||||
- make prove 2>&1
|
||||
|
||||
+15
-2
@@ -1,10 +1,23 @@
|
||||
# Changelog
|
||||
|
||||
## 0.1.12 - 2017-03-22
|
||||
|
||||
* Implement "browse" subcommand on Windows [Claus Brod] [[ca333d8](https://github.com/Netflix-Skunkworks/go-jira/commit/ca333d8)]
|
||||
|
||||
## 0.1.11 - 2017-02-26
|
||||
|
||||
* [[#69](https://github.com/Netflix-Skunkworks/go-jira/issues/69)] add subtask command [Cory Bennett] [[21a2ed5](https://github.com/Netflix-Skunkworks/go-jira/commit/21a2ed5)]
|
||||
|
||||
## 0.1.10 - 2017-02-08
|
||||
|
||||
* set GPG_TTY in .bashrc [Cory Bennett] [[b1e552f](https://github.com/Netflix-Skunkworks/go-jira/commit/b1e552f)]
|
||||
* force password in case password already exists [Cory Bennett] [[d5a2c3b](https://github.com/Netflix-Skunkworks/go-jira/commit/d5a2c3b)]
|
||||
* refactor password source, allow for "pass" to be used, update tests to use `password-source: pass` [Cory Bennett] [[5a71939](https://github.com/Netflix-Skunkworks/go-jira/commit/5a71939)]
|
||||
|
||||
## 0.1.9 - 2016-12-18
|
||||
|
||||
* only warn about needing login when not already running the login command [Cory Bennett] [[6c24e55](https://github.com/Netflix-Skunkworks/go-jira/commit/6c24e55)]
|
||||
* fix(http): Add proxy transport [William Hearn] [[4bd740b](https://github.com/Netflix-Skunkworks/go-jira/commit/4bd740b)]
|
||||
* fix(http): Add proxy transport [William Hearn] [[2dff6c9](https://github.com/Netflix-Skunkworks/go-jira/commit/2dff6c9)]
|
||||
* fix(http): Add proxy transport [William Hearn] [[4bd740b](https://github.com/Netflix-Skunkworks/go-jira/commit/4bd740b)] [[2dff6c9](https://github.com/Netflix-Skunkworks/go-jira/commit/2dff6c9)]
|
||||
|
||||
## 0.1.8 - 2016-11-24
|
||||
|
||||
|
||||
@@ -73,7 +73,10 @@ cross-setup:
|
||||
cd $(GOROOT)/src && sudo GOROOT_BOOTSTRAP=$(GOROOT) GOOS=$${p/-*/} GOARCH=$${p/*-/} bash ./make.bash --no-clean; \
|
||||
done
|
||||
|
||||
all: src/gopkg.in/Netflix-Skunkworks/go-jira.v0
|
||||
all:
|
||||
git push --tags
|
||||
rm -rf src
|
||||
${MAKE} src/gopkg.in/Netflix-Skunkworks/go-jira.v0
|
||||
docker pull karalabe/xgo-latest
|
||||
rm -rf dist
|
||||
mkdir -p dist
|
||||
@@ -119,3 +122,11 @@ version:
|
||||
|
||||
clean:
|
||||
rm -rf pkg dist bin src ./$(NAME)
|
||||
|
||||
export GNUPGHOME=$(CWD)/t/.gnupg
|
||||
export PASSWORD_STORE_DIR=$(CWD)/t/.password-store
|
||||
export JIRACLOUD=1
|
||||
|
||||
prove:
|
||||
chmod -R g-rwx,o-rwx $(GNUPGHOME)
|
||||
OSHT_VERBOSE=1 prove -v
|
||||
|
||||
@@ -9,7 +9,7 @@ simple command line client for Atlassian's Jira service written in Go
|
||||
## Synopsis
|
||||
|
||||
```bash
|
||||
jira ls -p GOJIRA # list all unresolved issues for project GOJRIA
|
||||
jira ls -p GOJIRA # list all unresolved issues for project GOJIRA
|
||||
jira ls -p GOJIRA -a mothra # as above also assigned to user mothra
|
||||
jira ls -p GOJIRA -w mothra # lists GOJIRA unresolved issues watched by user mothra
|
||||
jira ls -p GOJIRA -r mothra # list GOJIRA unresolved issues reported by user mothra
|
||||
@@ -21,7 +21,7 @@ jira GOJIRA-321 # same as above
|
||||
jira edit GOJIRA-321 # open up the issue in an editor, when you exit the
|
||||
# editor the issue will post the updates to the server
|
||||
|
||||
# edit the issue, using the overirdes on the command line, skip the interactive editor:
|
||||
# edit the issue, using the overrides on the command line, skip the interactive editor:
|
||||
jira edit GOJIRA-321 --noedit \
|
||||
-o assignee=mothra \
|
||||
-o comment="mothra, please take care of this." \
|
||||
@@ -91,7 +91,7 @@ a child directory of your homedir, then your homedir will also be inspected for
|
||||
discovered **go-jira** will load a **config.yml** if found. The configuration properties found in a file closests to your current working directory
|
||||
will have precedence. Properties overriden with command line options will have final precedence.
|
||||
|
||||
The complicated configuration heirarchy is used because **go-jira** attempts to be context aware. For example, if you are working on a "foo" project and
|
||||
The complicated configuration hierarchy is used because **go-jira** attempts to be context aware. For example, if you are working on a "foo" project and
|
||||
you `cd` into your project workspace, wouldn't it be nice if `jira ls` automatically knew to list only issues related to the "foo" project? Likewise when you
|
||||
`cd` to the "bar" project then `jira ls` should only list issues related to "bar" project. You can do this with by creating a configuration under your project
|
||||
workspace at **./.jira.d/config.yml** that looks like:
|
||||
@@ -110,7 +110,7 @@ endpoint: https://jira.mycompany.com
|
||||
EOM
|
||||
```
|
||||
|
||||
Then use `jira login` to authenticate yourself.
|
||||
Then use `jira login` to authenticate yourself as $USER. To change your username, use the `-u` CLI flag or set `user:` in your config.yml
|
||||
|
||||
### Dynamic Configuration
|
||||
|
||||
@@ -182,6 +182,61 @@ jira create --dryrun -t debug --editor /bin/cat
|
||||
```
|
||||
This will attempt to fetch metadata for your default project (you can provide any options that you would normally specify for the `create` operation). It uses the `--dryrun` option to prevent any actual updates being sent to Jira. The `-t debug` is like before to cause the input to be serialized to JSON and printed for your inspection. Finally the `--editor /bin/cat` will cause `go-jira` to just print the template rather than open up an editor and wait for you to edit/save it.
|
||||
|
||||
### Authentication
|
||||
|
||||
By default `go-jira` will prompt for a password automatically when we receive an 403 http response. Then after authentication we cache the JSESSSION cookie returned by the service and reuse that on subsequent requests. Typically this cookie will be valid for several hours (depending on the service configuration). Many deployments of Jira (like the cloud services on atlassian.net) have "websudo" enabled which will prevent the cookie based authentcation from working. On these deployments you have a few options with `go-jira`. You can enable a `password-source` via `.jira.d/config.yml` with possible values of `keyring` or `pass`.
|
||||
|
||||
#### keyring password source
|
||||
**Note: Version 0.1.9 required.**
|
||||
On OSX and Linux there are a few keyring providers that `go-jira` can use (via this [golang module](https://github.com/tmc/keyring)). To integrate `go-jira` with a supported keyring just add this configuration to `$HOME/.jira.d/config.yml`:
|
||||
```yaml
|
||||
password-source: keyring
|
||||
```
|
||||
After setting this and issuing a `jira login`, your credentials will be stored in your platform's backend (e.g. Keychain for Mac OS X) automatically. Subsequent operations, like a `jira ls`, should "just work" from there.
|
||||
|
||||
#### `pass` password source
|
||||
**Note: Version 0.1.9 required.**
|
||||
An alternative to the keyring password source is the `pass` tool (documentation [here](https://www.passwordstore.org/)). This uses gpg to encrypt/decrypt passwords on demand and by using `gpg-agent` you can cache the gpg credentials for a period of time so you will not be prompted repeatedly for decrypting the passwords. The advantage over the keyring integration is that `pass` can be used on more platforms than OSX and Linux, although it does require more setup. To use `pass` for password storage and retrieval via `go-jira` just add this configuration to `$HOME/.jira.d/config.yml`:
|
||||
```yaml
|
||||
password-source: pass
|
||||
```
|
||||
|
||||
This assumes you have already setup `pass` correctly on your system. Specifically you will need to have created a gpg key like this:
|
||||
|
||||
```
|
||||
$ gpg --gen-key
|
||||
```
|
||||
|
||||
Then you will need the GPG Key ID you want associated with `pass`. First list the available keys:
|
||||
```
|
||||
$ gpg --list-keys
|
||||
/home/gojira/.gnupg/pubring.gpg
|
||||
-------------------------------------------------
|
||||
pub 2048R/A307D709 2016-12-18
|
||||
uid Go Jira <gojira@example.com>
|
||||
sub 2048R/F9A047B8 2016-12-18
|
||||
```
|
||||
|
||||
Then initialize the `pass` tool to use the correct key:
|
||||
```
|
||||
$ pass init "Go Jira <gojira@example.com>"
|
||||
```
|
||||
|
||||
You probably want to setup gpg-agent so that you dont have to type in your gpg passphrase all the time. You can get `gpg-agent` to automatically start by adding something like this to your `$HOME/.bashrc`
|
||||
```bash
|
||||
if [ -f $HOME/.gpg-agent-info ]; then
|
||||
. $HOME/.gpg-agent-info
|
||||
export GPG_AGENT_INFO
|
||||
fi
|
||||
# verify sock file from GPG_AGENT_INFO is actually present
|
||||
if [ ! -S "${GPG_AGENT_INFO%%:*}" ]; then
|
||||
# set passphrase cache so I only have to type my passphrase once a day
|
||||
eval $(gpg-agent --default-cache-ttl 604800 --daemon --write-env-file $HOME/.gpg-agent-info)
|
||||
fi
|
||||
export GPG_TTY=$(tty)
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/kballard/go-shellquote"
|
||||
"github.com/tmc/keyring"
|
||||
"gopkg.in/coryb/yaml.v2"
|
||||
"gopkg.in/op/go-logging.v1"
|
||||
)
|
||||
@@ -215,11 +214,11 @@ func (c *Cli) makeRequest(req *http.Request) (resp *http.Response, err error) {
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
if val, ok := c.opts["password-keyring"].(bool); ok && val && !strings.HasSuffix(req.URL.Path, "/rest/auth/1/session") {
|
||||
if source, ok := c.opts["password-source"]; ok && !strings.HasSuffix(req.URL.Path, "/rest/auth/1/session") {
|
||||
user, _ := c.opts["user"].(string)
|
||||
password, _ := keyring.Get("go-jira", user)
|
||||
password := c.GetPass(user)
|
||||
if password == "" {
|
||||
log.Warning("No password for user %s in keyring, please run the 'login' command first", user)
|
||||
log.Warning("No password for user %s in %s, please run the 'login' command first", user, source)
|
||||
} else {
|
||||
req.SetBasicAuth(user, password)
|
||||
}
|
||||
@@ -452,6 +451,8 @@ func (c *Cli) Browse(issue string) error {
|
||||
return exec.Command("open", fmt.Sprintf("%s/browse/%s", c.endpoint, issue)).Run()
|
||||
} else if runtime.GOOS == "linux" {
|
||||
return exec.Command("xdg-open", fmt.Sprintf("%s/browse/%s", c.endpoint, issue)).Run()
|
||||
} else if runtime.GOOS == "windows" {
|
||||
return exec.Command("cmd", "/c", "start", fmt.Sprintf("%s/browse/%s", c.endpoint, issue)).Run()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
+97
-36
@@ -10,8 +10,6 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/howeyc/gopass"
|
||||
"github.com/tmc/keyring"
|
||||
"gopkg.in/Netflix-Skunkworks/go-jira.v0/data"
|
||||
// "github.com/kr/pretty"
|
||||
)
|
||||
@@ -23,13 +21,7 @@ func (c *Cli) CmdLogin() error {
|
||||
req, _ := http.NewRequest("GET", uri, nil)
|
||||
user, _ := c.opts["user"].(string)
|
||||
|
||||
fmt.Printf("Jira Password [%s]: ", user)
|
||||
pw, err := gopass.GetPasswdMasked()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
passwd := string(pw)
|
||||
|
||||
passwd := c.GetPass(user)
|
||||
req.SetBasicAuth(user, passwd)
|
||||
|
||||
resp, err := c.makeRequest(req)
|
||||
@@ -55,13 +47,8 @@ func (c *Cli) CmdLogin() error {
|
||||
log.Warning("Authentication Failed: %s", reason)
|
||||
continue
|
||||
}
|
||||
if val, ok := c.opts["password-keyring"].(bool); ok && val {
|
||||
// save password in keychain so that it can be used for subsequent http requests
|
||||
err := keyring.Set("go-jira", user, passwd)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to set password in keyring: %s", err)
|
||||
return err
|
||||
}
|
||||
if _, ok := c.opts["password-source"]; ok {
|
||||
return c.SetPass(user, passwd)
|
||||
}
|
||||
break
|
||||
} else {
|
||||
@@ -389,31 +376,15 @@ func (c *Cli) CmdCreate() error {
|
||||
issuetype = c.defaultIssueType()
|
||||
}
|
||||
|
||||
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))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
issueData := make(map[string]interface{})
|
||||
issueData["overrides"] = c.opts
|
||||
issueData["overrides"].(map[string]interface{})["issuetype"] = issuetype
|
||||
|
||||
if val, ok := data.(map[string]interface{})["projects"]; ok {
|
||||
if len(val.([]interface{})) == 0 {
|
||||
err = fmt.Errorf("Project '%s' or issuetype '%s' unknown. Unable to create issue.", project, issuetype)
|
||||
log.Errorf("%s", err)
|
||||
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.Errorf("%s", err)
|
||||
return err
|
||||
}
|
||||
issueData["meta"] = val.([]interface{})[0]
|
||||
}
|
||||
meta, err := c.createIssueMetaData(project, issuetype)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
issueData["meta"] = meta
|
||||
|
||||
sanitizedType := strings.ToLower(strings.Replace(issuetype, " ", "", -1))
|
||||
return c.editTemplate(
|
||||
@@ -459,6 +430,96 @@ func (c *Cli) CmdCreate() error {
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Cli) createIssueMetaData(project, issuetype string) (interface{}, error) {
|
||||
uri := fmt.Sprintf("%s/rest/api/2/issue/createmeta?projectKeys=%s&issuetypeNames=%s&expand=projects.issuetypes.fields", c.endpoint, project, url.QueryEscape(issuetype))
|
||||
metaData, err := responseToJSON(c.get(uri))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if val, ok := metaData.(map[string]interface{})["projects"]; ok {
|
||||
if len(val.([]interface{})) == 0 {
|
||||
err = fmt.Errorf("Project '%s' or issuetype '%s' unknown. Unable to create issue.", project, issuetype)
|
||||
log.Errorf("%s", err)
|
||||
return nil, 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.Errorf("%s", err)
|
||||
return nil, err
|
||||
}
|
||||
return val.([]interface{})[0], nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// CmdSubtask sends the create-metadata to the "subtask" template for editing, then
|
||||
// will parse the edited document as YAML and submit the document to jira.
|
||||
func (c *Cli) CmdSubtask(issue string) error {
|
||||
log.Debugf("subtask called")
|
||||
|
||||
uri := fmt.Sprintf("%s/rest/api/2/issue/%s", c.endpoint, issue)
|
||||
parentData, err := responseToJSON(c.get(uri))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
subtaskData := make(map[string]interface{})
|
||||
subtaskData["parent"] = parentData
|
||||
subtaskData["overrides"] = c.opts
|
||||
|
||||
project := parentData.(map[string]interface{})["fields"].(map[string]interface{})["project"].(map[string]interface{})["key"].(string)
|
||||
meta, err := c.createIssueMetaData(project, "Sub-task")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
subtaskData["meta"] = meta
|
||||
|
||||
return c.editTemplate(
|
||||
c.getTemplate("subtask"),
|
||||
"subtask-",
|
||||
subtaskData,
|
||||
func(json string) error {
|
||||
uri := fmt.Sprintf("%s/rest/api/2/issue", 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 {
|
||||
// response: {"id":"410836","key":"PROJ-238","self":"https://jira/rest/api/2/issue/410836"}
|
||||
json, err := responseToJSON(resp, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// CmdIssueLinkTypes will send the issue link type data to the "issuelinktypes" template.
|
||||
func (c *Cli) CmdIssueLinkTypes() error {
|
||||
log.Debugf("Transitions called")
|
||||
|
||||
+10
-4
@@ -3,14 +3,15 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"gopkg.in/Netflix-Skunkworks/go-jira.v0"
|
||||
"github.com/coryb/optigo"
|
||||
"gopkg.in/coryb/yaml.v2"
|
||||
"gopkg.in/op/go-logging.v1"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/coryb/optigo"
|
||||
"gopkg.in/Netflix-Skunkworks/go-jira.v0"
|
||||
"gopkg.in/coryb/yaml.v2"
|
||||
"gopkg.in/op/go-logging.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -60,6 +61,7 @@ Usage:
|
||||
jira add worklog ISSUE <Worklog Options>
|
||||
jira edit [--noedit] <Edit Options> [ISSUE | <Query Options>]
|
||||
jira create [--noedit] [-p PROJECT] <Create Options>
|
||||
jira subtask ISSUE [--noedit] <Create Options>
|
||||
jira DUPLICATE dups ISSUE
|
||||
jira BLOCKER blocks ISSUE
|
||||
jira vote ISSUE [--down]
|
||||
@@ -145,6 +147,7 @@ Command Options:
|
||||
"view": "view",
|
||||
"edit": "edit",
|
||||
"create": "create",
|
||||
"subtask": "subtask",
|
||||
"dups": "dups",
|
||||
"blocks": "blocks",
|
||||
"watch": "watch",
|
||||
@@ -376,6 +379,9 @@ Command Options:
|
||||
case "create":
|
||||
setEditing(true)
|
||||
err = c.CmdCreate()
|
||||
case "subtask":
|
||||
setEditing(true)
|
||||
err = c.CmdSubtask(args[0])
|
||||
case "transitions":
|
||||
requireArgs(1)
|
||||
err = c.CmdTransitions(args[0])
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package jira
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/howeyc/gopass"
|
||||
"github.com/tmc/keyring"
|
||||
)
|
||||
|
||||
func (c *Cli) GetPass(user string) string {
|
||||
passwd := ""
|
||||
if source, ok := c.opts["password-source"].(string); ok {
|
||||
if source == "keyring" {
|
||||
passwd, _ = keyring.Get("go-jira", user)
|
||||
} else if source == "pass" {
|
||||
if bin, err := exec.LookPath("pass"); err == nil {
|
||||
buf := bytes.NewBufferString("")
|
||||
cmd := exec.Command(bin, fmt.Sprintf("GoJira/%s", user))
|
||||
cmd.Stdout = buf
|
||||
cmd.Stderr = buf
|
||||
if err := cmd.Run(); err == nil {
|
||||
passwd = strings.TrimSpace(buf.String())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Warningf("Unknown password-source: %s", source)
|
||||
}
|
||||
}
|
||||
|
||||
if passwd != "" {
|
||||
return passwd
|
||||
}
|
||||
fmt.Printf("Jira Password [%s]: ", user)
|
||||
pw, err := gopass.GetPasswdMasked()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
passwd = string(pw)
|
||||
return passwd
|
||||
}
|
||||
|
||||
func (c *Cli) SetPass(user, passwd string) error {
|
||||
log.Debugf("SetPass called: %s => %s", user, passwd)
|
||||
if source, ok := c.opts["password-source"].(string); ok {
|
||||
log.Debugf("password-source: %s", source)
|
||||
if source == "keyring" {
|
||||
// save password in keychain so that it can be used for subsequent http requests
|
||||
err := keyring.Set("go-jira", user, passwd)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to set password in keyring: %s", err)
|
||||
return err
|
||||
}
|
||||
} else if source == "pass" {
|
||||
log.Debugf("processing %s", source)
|
||||
if bin, err := exec.LookPath("pass"); err == nil {
|
||||
log.Debugf("using %s", bin)
|
||||
in := bytes.NewBufferString(fmt.Sprintf("%s\n%s\n", passwd, passwd))
|
||||
out := bytes.NewBufferString("")
|
||||
cmd := exec.Command(bin, "insert", "--force", fmt.Sprintf("GoJira/%s", user))
|
||||
cmd.Stdin = in
|
||||
cmd.Stdout = out
|
||||
cmd.Stderr = out
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("Failed to insert password: %s", out.String())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Unknown password-source: %s", source)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
# Options for GnuPG
|
||||
# Copyright 1998, 1999, 2000, 2001, 2002, 2003,
|
||||
# 2010 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; as a special exception the author gives
|
||||
# unlimited permission to copy and/or distribute it, with or without
|
||||
# modifications, as long as this notice is preserved.
|
||||
#
|
||||
# This file is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
# Unless you specify which option file to use (with the command line
|
||||
# option "--options filename"), GnuPG uses the file ~/.gnupg/gpg.conf
|
||||
# by default.
|
||||
#
|
||||
# An options file can contain any long options which are available in
|
||||
# GnuPG. If the first non white space character of a line is a '#',
|
||||
# this line is ignored. Empty lines are also ignored.
|
||||
#
|
||||
# See the man page for a list of options.
|
||||
|
||||
# Uncomment the following option to get rid of the copyright notice
|
||||
|
||||
#no-greeting
|
||||
|
||||
# If you have more than 1 secret key in your keyring, you may want to
|
||||
# uncomment the following option and set your preferred keyid.
|
||||
|
||||
#default-key 621CC013
|
||||
|
||||
# If you do not pass a recipient to gpg, it will ask for one. Using
|
||||
# this option you can encrypt to a default key. Key validation will
|
||||
# not be done in this case. The second form uses the default key as
|
||||
# default recipient.
|
||||
|
||||
#default-recipient some-user-id
|
||||
#default-recipient-self
|
||||
|
||||
# Use --encrypt-to to add the specified key as a recipient to all
|
||||
# messages. This is useful, for example, when sending mail through a
|
||||
# mail client that does not automatically encrypt mail to your key.
|
||||
# In the example, this option allows you to read your local copy of
|
||||
# encrypted mail that you've sent to others.
|
||||
|
||||
#encrypt-to some-key-id
|
||||
|
||||
# By default GnuPG creates version 4 signatures for data files as
|
||||
# specified by OpenPGP. Some earlier (PGP 6, PGP 7) versions of PGP
|
||||
# require the older version 3 signatures. Setting this option forces
|
||||
# GnuPG to create version 3 signatures.
|
||||
|
||||
#force-v3-sigs
|
||||
|
||||
# Because some mailers change lines starting with "From " to ">From "
|
||||
# it is good to handle such lines in a special way when creating
|
||||
# cleartext signatures; all other PGP versions do it this way too.
|
||||
|
||||
#no-escape-from-lines
|
||||
|
||||
# If you do not use the Latin-1 (ISO-8859-1) charset, you should tell
|
||||
# GnuPG which is the native character set. Please check the man page
|
||||
# for supported character sets. This character set is only used for
|
||||
# metadata and not for the actual message which does not undergo any
|
||||
# translation. Note that future version of GnuPG will change to UTF-8
|
||||
# as default character set. In most cases this option is not required
|
||||
# as GnuPG is able to figure out the correct charset at runtime.
|
||||
|
||||
#charset utf-8
|
||||
|
||||
# Group names may be defined like this:
|
||||
# group mynames = paige 0x12345678 joe patti
|
||||
#
|
||||
# Any time "mynames" is a recipient (-r or --recipient), it will be
|
||||
# expanded to the names "paige", "joe", and "patti", and the key ID
|
||||
# "0x12345678". Note there is only one level of expansion - you
|
||||
# cannot make an group that points to another group. Note also that
|
||||
# if there are spaces in the recipient name, this will appear as two
|
||||
# recipients. In these cases it is better to use the key ID.
|
||||
|
||||
#group mynames = paige 0x12345678 joe patti
|
||||
|
||||
# Lock the file only once for the lifetime of a process. If you do
|
||||
# not define this, the lock will be obtained and released every time
|
||||
# it is needed, which is usually preferable.
|
||||
|
||||
#lock-once
|
||||
|
||||
# GnuPG can send and receive keys to and from a keyserver. These
|
||||
# servers can be HKP, email, or LDAP (if GnuPG is built with LDAP
|
||||
# support).
|
||||
#
|
||||
# Example HKP keyserver:
|
||||
# hkp://keys.gnupg.net
|
||||
# hkp://subkeys.pgp.net
|
||||
#
|
||||
# Example email keyserver:
|
||||
# mailto:pgp-public-keys@keys.pgp.net
|
||||
#
|
||||
# Example LDAP keyservers:
|
||||
# ldap://keyserver.pgp.com
|
||||
#
|
||||
# Regular URL syntax applies, and you can set an alternate port
|
||||
# through the usual method:
|
||||
# hkp://keyserver.example.net:22742
|
||||
#
|
||||
# Most users just set the name and type of their preferred keyserver.
|
||||
# Note that most servers (with the notable exception of
|
||||
# ldap://keyserver.pgp.com) synchronize changes with each other. Note
|
||||
# also that a single server name may actually point to multiple
|
||||
# servers via DNS round-robin. hkp://keys.gnupg.net is an example of
|
||||
# such a "server", which spreads the load over a number of physical
|
||||
# servers. To see the IP address of the server actually used, you may use
|
||||
# the "--keyserver-options debug".
|
||||
|
||||
keyserver hkp://keys.gnupg.net
|
||||
#keyserver mailto:pgp-public-keys@keys.nl.pgp.net
|
||||
#keyserver ldap://keyserver.pgp.com
|
||||
|
||||
# Common options for keyserver functions:
|
||||
#
|
||||
# include-disabled : when searching, include keys marked as "disabled"
|
||||
# on the keyserver (not all keyservers support this).
|
||||
#
|
||||
# no-include-revoked : when searching, do not include keys marked as
|
||||
# "revoked" on the keyserver.
|
||||
#
|
||||
# verbose : show more information as the keys are fetched.
|
||||
# Can be used more than once to increase the amount
|
||||
# of information shown.
|
||||
#
|
||||
# use-temp-files : use temporary files instead of a pipe to talk to the
|
||||
# keyserver. Some platforms (Win32 for one) always
|
||||
# have this on.
|
||||
#
|
||||
# keep-temp-files : do not delete temporary files after using them
|
||||
# (really only useful for debugging)
|
||||
#
|
||||
# http-proxy="proxy" : set the proxy to use for HTTP and HKP keyservers.
|
||||
# This overrides the "http_proxy" environment variable,
|
||||
# if any.
|
||||
#
|
||||
# auto-key-retrieve : automatically fetch keys as needed from the keyserver
|
||||
# when verifying signatures or when importing keys that
|
||||
# have been revoked by a revocation key that is not
|
||||
# present on the keyring.
|
||||
#
|
||||
# no-include-attributes : do not include attribute IDs (aka "photo IDs")
|
||||
# when sending keys to the keyserver.
|
||||
|
||||
#keyserver-options auto-key-retrieve
|
||||
|
||||
# Display photo user IDs in key listings
|
||||
|
||||
# list-options show-photos
|
||||
|
||||
# Display photo user IDs when a signature from a key with a photo is
|
||||
# verified
|
||||
|
||||
# verify-options show-photos
|
||||
|
||||
# Use this program to display photo user IDs
|
||||
#
|
||||
# %i is expanded to a temporary file that contains the photo.
|
||||
# %I is the same as %i, but the file isn't deleted afterwards by GnuPG.
|
||||
# %k is expanded to the key ID of the key.
|
||||
# %K is expanded to the long OpenPGP key ID of the key.
|
||||
# %t is expanded to the extension of the image (e.g. "jpg").
|
||||
# %T is expanded to the MIME type of the image (e.g. "image/jpeg").
|
||||
# %f is expanded to the fingerprint of the key.
|
||||
# %% is %, of course.
|
||||
#
|
||||
# If %i or %I are not present, then the photo is supplied to the
|
||||
# viewer on standard input. If your platform supports it, standard
|
||||
# input is the best way to do this as it avoids the time and effort in
|
||||
# generating and then cleaning up a secure temp file.
|
||||
#
|
||||
# If no photo-viewer is provided, GnuPG will look for xloadimage, eog,
|
||||
# or display (ImageMagick). On Mac OS X and Windows, the default is
|
||||
# to use your regular JPEG image viewer.
|
||||
#
|
||||
# Some other viewers:
|
||||
# photo-viewer "qiv %i"
|
||||
# photo-viewer "ee %i"
|
||||
#
|
||||
# This one saves a copy of the photo ID in your home directory:
|
||||
# photo-viewer "cat > ~/photoid-for-key-%k.%t"
|
||||
#
|
||||
# Use your MIME handler to view photos:
|
||||
# photo-viewer "metamail -q -d -b -c %T -s 'KeyID 0x%k' -f GnuPG"
|
||||
|
||||
# Passphrase agent
|
||||
#
|
||||
# We support the old experimental passphrase agent protocol as well as
|
||||
# the new Assuan based one (currently available in the "newpg" package
|
||||
# at ftp.gnupg.org/gcrypt/alpha/aegypten/). To make use of the agent,
|
||||
# you have to run an agent as daemon and use the option
|
||||
#
|
||||
# use-agent
|
||||
#
|
||||
# which tries to use the agent but will fallback to the regular mode
|
||||
# if there is a problem connecting to the agent. The normal way to
|
||||
# locate the agent is by looking at the environment variable
|
||||
# GPG_AGENT_INFO which should have been set during gpg-agent startup.
|
||||
# In certain situations the use of this variable is not possible, thus
|
||||
# the option
|
||||
#
|
||||
# --gpg-agent-info=<path>:<pid>:1
|
||||
#
|
||||
# may be used to override it.
|
||||
|
||||
# Automatic key location
|
||||
#
|
||||
# GnuPG can automatically locate and retrieve keys as needed using the
|
||||
# auto-key-locate option. This happens when encrypting to an email
|
||||
# address (in the "user@example.com" form), and there are no
|
||||
# user@example.com keys on the local keyring. This option takes the
|
||||
# following arguments, in the order they are to be tried:
|
||||
#
|
||||
# cert = locate a key using DNS CERT, as specified in RFC-4398.
|
||||
# GnuPG can handle both the PGP (key) and IPGP (URL + fingerprint)
|
||||
# CERT methods.
|
||||
#
|
||||
# pka = locate a key using DNS PKA.
|
||||
#
|
||||
# ldap = locate a key using the PGP Universal method of checking
|
||||
# "ldap://keys.(thedomain)". For example, encrypting to
|
||||
# user@example.com will check ldap://keys.example.com.
|
||||
#
|
||||
# keyserver = locate a key using whatever keyserver is defined using
|
||||
# the keyserver option.
|
||||
#
|
||||
# You may also list arbitrary keyservers here by URL.
|
||||
#
|
||||
# Try CERT, then PKA, then LDAP, then hkp://subkeys.net:
|
||||
#auto-key-locate cert pka ldap hkp://subkeys.pgp.net
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,8 +1,8 @@
|
||||
#!/bin/sh
|
||||
echo password-source: pass
|
||||
if [ -z "$JIRACLOUD" ]; then
|
||||
echo endpoint: http://localhost:8080
|
||||
echo user: gojira
|
||||
echo password-keyring: true
|
||||
else
|
||||
echo endpoint: https://go-jira.atlassian.net
|
||||
echo user: gojira@example.com
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Go Jira <gojira@example.com>
|
||||
@@ -0,0 +1 @@
|
||||
…(ΡαΆω GΈώ20,ΧΎ„¶ι―’«$Ggu©y1_a-ΟI'ΥΈοΘ}�Ν£4
¨s@,]?.£�Pξs>uθ¤QpΓx�ΛΠΡA�|x]"‘*ΒΎf„ς—λΫ£B2ΕνytΌΡ±c…¥ο β8L Ιvg�ΗΚi]��ΙKΥzu0yKΩ.“ §ΣBΑw|ƒ†_K�'zθ΅Ύ“c¦ύυάά…ύ)?ΔoT #�G8PΧΙπΩ=�υP-–,Ί}¥άώΝX:,�ΊfB'=WG²I±¨
0BΈ£ξJΞ3Η εϋο��ΛΞσ;�;;)Iι_UMf�α•τ�’}·’,―CΒhπ�Ομ£ο®μ\‰�kκtΞ&ωµNύΌΐ½άjξ™�}ςά
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
…(Ñá¢ù G¸ü�©oCº™*šâj0OÊ!=ldÿ§ô~2%p7•>´·kbñ�#d›‹'¥d|_à{±�ºa¶[EŠÔο�=mÂm½ð°Lí&‹À',^‰ý$¡¨!HÞ>]Ð4WïôêÔi+q=†N ‘2¿1´K;_% ~Ø ™¶£Õ‡ØÄ~\¶'–
: Ï[*:ï´ËFÃáSÌooApö¦*dËËH¬ôz‘È-vÝÞ#¼5†¡Ü[…«Ü„zê,e¸˜È¤H‘„e#"=¾$y5kÐ+$Ë Ô¸šÇϨ~Õç�’÷�¾Ë3×Ïçj®¹3O£|WéÝÿîA#TI)ö±
«âU}ô¹Ò;M3³ÞŒžëÁ9.ª}i�T΂Š
Ëì0S s…Jp…ìå’*½çѶPob‘¼™(*ò
|
||||
Binary file not shown.
+4
-4
@@ -13,7 +13,7 @@ PLAN 86
|
||||
|
||||
# reset login
|
||||
RUNS $jira logout
|
||||
echo "gojira123" | RUNS $jira login
|
||||
RUNS $jira login
|
||||
|
||||
# cleanup from previous failed test executions
|
||||
($jira ls | awk -F: '{print $1}' | while read issue; do ../jira done $issue; done) | sed 's/^/# CLEANUP: /g'
|
||||
@@ -21,7 +21,7 @@ echo "gojira123" | RUNS $jira login
|
||||
###############################################################################
|
||||
## Create an issue
|
||||
###############################################################################
|
||||
RUNS $jira -v -v -v create -o summary=summary -o description=description --noedit --saveFile issue.props
|
||||
RUNS $jira create -o summary=summary -o description=description --noedit --saveFile issue.props
|
||||
issue=$(awk '/issue/{print $2}' issue.props)
|
||||
|
||||
DIFF <<EOF
|
||||
@@ -205,7 +205,7 @@ EOF
|
||||
jira="$jira --user mothra"
|
||||
|
||||
RUNS $jira logout
|
||||
echo "mothra123" | RUNS $jira login
|
||||
RUNS $jira login
|
||||
|
||||
###############################################################################
|
||||
## vote for main issue, verify it shows when viewing the issue
|
||||
@@ -271,7 +271,7 @@ OK $issue $ENDPOINT/browse/$issue
|
||||
EOF
|
||||
|
||||
# FIXME we probably need a watchers command to wrap this?
|
||||
RUNS sh -c "$jira req /rest/api/2/issue/$issue/watchers | jq -r .watchers[].name"
|
||||
RUNS sh -c "$jira req /rest/api/2/issue/$issue/watchers | jq -r .watchers[].name | sort"
|
||||
DIFF <<EOF
|
||||
gojira
|
||||
mothra
|
||||
|
||||
@@ -13,7 +13,7 @@ PLAN 8
|
||||
|
||||
# reset login
|
||||
RUNS $jira logout
|
||||
echo "gojira123" | RUNS $jira login
|
||||
RUNS $jira login
|
||||
|
||||
# cleanup from previous failed test executions
|
||||
($jira ls | awk -F: '{print $1}' | while read issue; do ../jira done $issue; done) | sed 's/^/# CLEANUP: /g'
|
||||
|
||||
+1
-1
@@ -258,7 +258,7 @@ OK $issue $ENDPOINT/browse/$issue
|
||||
EOF
|
||||
|
||||
# FIXME we probably need a watchers command to wrap this?
|
||||
RUNS sh -c "$jira req /rest/api/2/issue/$issue/watchers | jq -r .watchers[].name"
|
||||
RUNS sh -c "$jira req /rest/api/2/issue/$issue/watchers | jq -r .watchers[].name | sort"
|
||||
DIFF <<EOF
|
||||
gojira
|
||||
mothra
|
||||
|
||||
+1
-1
@@ -258,7 +258,7 @@ OK $issue $ENDPOINT/browse/$issue
|
||||
EOF
|
||||
|
||||
# FIXME we probably need a watchers command to wrap this?
|
||||
RUNS sh -c "$jira req /rest/api/2/issue/$issue/watchers | jq -r .watchers[].name"
|
||||
RUNS sh -c "$jira req /rest/api/2/issue/$issue/watchers | jq -r .watchers[].name | sort"
|
||||
DIFF <<EOF
|
||||
gojira
|
||||
mothra
|
||||
|
||||
+1
-1
@@ -258,7 +258,7 @@ OK $issue $ENDPOINT/browse/$issue
|
||||
EOF
|
||||
|
||||
# FIXME we probably need a watchers command to wrap this?
|
||||
RUNS sh -c "$jira req /rest/api/2/issue/$issue/watchers | jq -r .watchers[].name"
|
||||
RUNS sh -c "$jira req /rest/api/2/issue/$issue/watchers | jq -r .watchers[].name | sort"
|
||||
DIFF <<EOF
|
||||
gojira
|
||||
mothra
|
||||
|
||||
+1
-1
@@ -269,7 +269,7 @@ OK $issue $ENDPOINT/browse/$issue
|
||||
EOF
|
||||
|
||||
# FIXME we probably need a watchers command to wrap this?
|
||||
RUNS sh -c "$jira req /rest/api/2/issue/$issue/watchers | jq -r .watchers[].name"
|
||||
RUNS sh -c "$jira req /rest/api/2/issue/$issue/watchers | jq -r .watchers[].name | sort"
|
||||
DIFF <<EOF
|
||||
gojira
|
||||
mothra
|
||||
|
||||
+1
-1
@@ -260,7 +260,7 @@ OK $issue $ENDPOINT/browse/$issue
|
||||
EOF
|
||||
|
||||
# FIXME we probably need a watchers command to wrap this?
|
||||
RUNS sh -c "$jira req /rest/api/2/issue/$issue/watchers | jq -r .watchers[].name"
|
||||
RUNS sh -c "$jira req /rest/api/2/issue/$issue/watchers | jq -r .watchers[].name | sort"
|
||||
DIFF <<EOF
|
||||
gojira
|
||||
mothra
|
||||
|
||||
@@ -15,6 +15,7 @@ var allTemplates = map[string]string{
|
||||
"components": defaultComponentsTemplate,
|
||||
"issuetypes": defaultIssuetypesTemplate,
|
||||
"create": defaultCreateTemplate,
|
||||
"subtask": defaultSubtaskTemplate,
|
||||
"comment": defaultCommentTemplate,
|
||||
"transition": defaultTransitionTemplate,
|
||||
"request": defaultDebugTemplate,
|
||||
@@ -140,6 +141,30 @@ fields:
|
||||
- name: {{.}}{{end}}
|
||||
- name:{{end}}`
|
||||
|
||||
const defaultSubtaskTemplate = `{{/* create subtask template */ -}}
|
||||
fields:
|
||||
project:
|
||||
key: {{ .parent.fields.project.key }}
|
||||
summary: {{ or .overrides.summary "" }}{{if .meta.fields.priority.allowedValues}}
|
||||
priority: # Values: {{ range .meta.fields.priority.allowedValues }}{{.name}}, {{end}}
|
||||
name: {{ or .overrides.priority ""}}{{end}}{{if .meta.fields.components.allowedValues}}
|
||||
components: # Values: {{ range .meta.fields.components.allowedValues }}{{.name}}, {{end}}{{ range split "," (or .overrides.components "")}}
|
||||
- name: {{ . }}{{end}}{{end}}
|
||||
description: |~
|
||||
{{ or .overrides.description "" | indent 4 }}{{if .meta.fields.assignee}}
|
||||
assignee:
|
||||
name: {{ or .overrides.assignee "" }}{{end}}{{if .meta.fields.reporter}}
|
||||
reporter:
|
||||
name: {{ or .overrides.reporter .overrides.user }}{{end}}{{if .meta.fields.customfield_10110}}
|
||||
# watchers
|
||||
customfield_10110: {{ range split "," (or .overrides.watchers "")}}
|
||||
- name: {{.}}{{end}}
|
||||
- name:{{end}}
|
||||
issuetype:
|
||||
name: Sub-task
|
||||
parent:
|
||||
key: {{ .parent.key }}`
|
||||
|
||||
const defaultCommentTemplate = `body: |~
|
||||
{{ or .overrides.comment "" | indent 2 }}
|
||||
`
|
||||
|
||||
Reference in New Issue
Block a user