Compare commits

..

281 Commits

Author SHA1 Message Date
Cory Bennett a54a6eb2d0 refactor, wip 2016-10-01 00:48:42 -07:00
Cory Bennett 070c38520b fix name for branch 2016-10-01 00:43:12 -07:00
Cory Bennett 89175df121 reorg repo, move library code to lib subdir, add glade 2016-09-18 09:25:26 -07:00
Cory Bennett ac0d2a07ce remove extraneous tee 2016-08-29 01:00:35 -07:00
Cory Bennett 413d46d66a reduce mega verbosity 2016-08-29 01:00:01 -07:00
Cory Bennett 68bb0f2014 up plan count for the sleep 2016-08-29 00:58:23 -07:00
Cory Bennett ef49884512 add missing src directory 2016-08-29 00:49:10 -07:00
Cory Bennett 568d453aa8 move start.log to directory 2016-08-29 00:23:05 -07:00
Cory Bennett a2a03c5221 try travis cache and log atlas-run start output 2016-08-29 00:19:27 -07:00
Cory Bennett a69b5ac8e5 break setup to debug in travis 2016-08-29 00:04:21 -07:00
Cory Bennett 10326d146f wait for docker service to start? 2016-08-28 23:47:34 -07:00
Cory Bennett ec4889dd9b make maven cache directory local so we can ensure it is always there for travis testing 2016-08-28 23:36:37 -07:00
Cory Bennett b71e391297 make prove verbose 2016-08-28 23:32:03 -07:00
Cory Bennett 44cdebfcff golint 2016-08-28 23:30:25 -07:00
Cory Bennett a20f233f4b require docker, run prove after build 2016-08-28 23:24:12 -07:00
Cory Bennett cfa25f4c17 use altas-run to build jira test service in docker container 2016-08-28 23:10:54 -07:00
Cory Bennett e4cc9c6967 add "rank" command allow ordering backlog issues in agile projects 2016-08-26 19:44:38 -07:00
Cory Bennett 6d23899dd5 Updated Changelog 2016-08-24 12:27:42 -07:00
coryb f049998b1c Merge pull request #52 from dbrower/master
Prefer transition names which match exactly
2016-08-24 12:26:56 -07:00
Don Brower e40f9c10c3 Prefer transition names which match exactly
Some transitions are substrings of other transitions, and the current
loop sometimes never chose the correct one. For example, it would never
choose "QA" if there were also a transtion "Deploy to QA". Ditto for
"Open" and "Reopen".
2016-08-24 14:36:23 -04:00
Cory Bennett 693b3e4d40 update tempates to make them more readable with space trimming added to go-1.6 2016-08-21 23:51:45 -07:00
Cory Bennett 15db036caf add simple test for the "table" list output 2016-08-21 23:51:30 -07:00
Cory Bennett 61fc22c30c Updated Changelog 2016-08-21 14:39:54 -07:00
Cory Bennett cc3fbee507 make "worklogs" command print output through template
allow "add worklog" command to open edit template
2016-08-21 14:31:30 -07:00
Cory Bennett d08ef155c8 remove extra newline at end of worklogs template 2016-08-21 14:31:11 -07:00
Cory Bennett ab1cd27c1d adding worklog related templates 2016-08-21 14:27:50 -07:00
Cory Bennett 6f38b76fd6 add vet and link make targets 2016-08-21 14:22:27 -07:00
Cory Bennett 154f75ab1e Updated Changelog 2016-08-21 12:42:47 -07:00
Cory Bennett 8bd22a02f5 add GoDoc badge 2016-08-21 10:24:07 -07:00
Cory Bennett b6f0fb1d87 add travis badge 2016-08-21 10:16:06 -07:00
Cory Bennett 5ee9355407 add travis build 2016-08-21 00:46:03 -07:00
Cory Bennett 5a4e17cb49 update for golint 2016-08-21 00:45:07 -07:00
Cory Bennett 355fb4243b fix for go vet 2016-08-19 10:19:04 -07:00
Cory Bennett 543e3c832c [#51] add missing data directory with generated structs 2016-08-19 08:52:42 -07:00
Cory Bennett 3a8687cca5 Updated Changelog 2016-08-12 13:32:13 -07:00
Cory Bennett 7ee40f7dfb ignore t/.jira.d/templates which are exported in 000setup.t 2016-08-12 13:31:16 -07:00
Cory Bennett 7c3d16b619 add tests for "Task Management" type projects 2016-08-12 13:30:24 -07:00
Cory Bennett d4f2f175ff add tests for "Process Management" type projects 2016-08-12 13:29:56 -07:00
Cory Bennett 577f67fbca add tests for "Project Management" type projects 2016-08-12 13:29:01 -07:00
Cory Bennett 2c91905fd7 when running "dups" on a Process Management Project type, you have to start/stop the task to resolve it 2016-08-12 13:27:51 -07:00
Cory Bennett a328c2dec3 allow for defaultResolution option for transition command 2016-08-12 12:13:37 -07:00
Cory Bennett 5303f44078 add tests for Kanban Software Project type issues 2016-08-10 10:52:45 -07:00
Cory Bennett e21d4c4fae add tests for Scrum Software Project type issues 2016-08-10 10:52:17 -07:00
Cory Bennett 96d81268a6 add test for Basic Software Project type issues 2016-08-10 10:51:55 -07:00
Cory Bennett 5ed1d15e2d add comments 2016-08-10 10:50:04 -07:00
Cory Bennett 30d8ace20d add extra "mojira" user for testing voting and ownership reassignment 2016-08-10 10:49:11 -07:00
Cory Bennett 5d39b23b02 add "backlog" command for Kanban related Issues 2016-08-10 10:48:02 -07:00
Cory Bennett 37c07fa0b5 fix --noedit flag with "dups" command 2016-08-09 23:51:04 -07:00
Cory Bennett 6f73b8cf0d add "votes" and "labels" to default view template 2016-08-09 23:48:28 -07:00
Cory Bennett 45dbb00683 gofmt 2016-08-09 23:16:41 -07:00
Cory Bennett d96bd464b1 gofmt 2016-08-09 23:16:20 -07:00
Cory Bennett 30fd301a61 add "blockerType" config param, for issueLinkType use for "blocks" command 2016-08-09 23:09:31 -07:00
Cory Bennett a823c59af1 add "debug" make target 2016-08-09 23:06:46 -07:00
Cory Bennett 4b822b100a update gitter room 2016-08-08 00:10:04 -07:00
Cory Bennett 704bce3b23 fix typo 2016-08-05 13:11:31 -07:00
Cory Bennett 3711ff7202 add gitter badge 2016-08-05 13:09:17 -07:00
Cory Bennett a713cfa360 make the bootstrapping simpler, move more setup to 000setup.t 2016-08-04 11:11:03 -07:00
Cory Bennett 25debaad5b fix links 2016-08-03 09:58:00 -07:00
Cory Bennett b6cc91b934 mention osht for testing 2016-08-03 09:56:54 -07:00
Cory Bennett 403a362c5c start adding some basic integration tests 2016-08-03 00:13:53 -07:00
Cory Bennett 0c807b45fe default issuetype to "Bug" for project that have Bug, otherwise try "Task" 2016-08-03 00:09:09 -07:00
Cory Bennett 8238fe87b6 make view template only show fields that have values 2016-08-02 23:16:08 -07:00
Cory Bennett adc2aceb89 make default create template only display fields if they are valid fields for the project 2016-08-02 22:44:24 -07:00
Cory Bennett f5f3e28b23 ignore empty json fields when processing templates 2016-08-02 22:36:51 -07:00
Cory Bennett 469def0296 allow JIRA_LOG_FORMAT env variable to control log output format 2016-08-02 20:12:30 -07:00
Cory Bennett 752a94d94b remove extraneous debug 2016-08-02 20:06:40 -07:00
Cory Bennett 8ad91be1c6 add logout command
modify password prompt to echo masked password
2016-08-02 20:04:11 -07:00
Cory Bennett f93fe7909c tweak cookies to store hostname
dump all http request/response with --verbose
2016-08-02 19:23:24 -07:00
Cory Bennett f54267b523 load configs in order of closest to cwd (/etc/go-jira.yml is last) 2016-08-02 19:20:23 -07:00
Cory Bennett 54df77d181 [#48] fix quoting for "make install" so it actually installs to homedir 2016-08-01 11:31:06 -07:00
Cory Bennett 642db779db Updated Changelog 2016-07-30 20:13:00 -07:00
Cory Bennett dd7d1ccd3a [#43] add support for jira done|todo|prog commands 2016-07-30 20:05:13 -07:00
Cory Bennett fdce34dfc0 [issue #46] add documentation for how to create/edit templates 2016-07-08 12:12:02 -07:00
coryb e7a78a1cc1 Merge pull request #24 from mikepea/edit_template_common
Reporter is not generally editable.
2016-06-30 08:53:34 -07:00
Cory Bennett 055b61d400 Updated Changelog 2016-06-29 23:11:04 -07:00
Cory Bennett 0980f8e197 [#44] Close tmpfile before rename to work around "The process cannot access the file because it is being used by another process" error on windows. 2016-06-29 23:09:27 -07:00
Cory Bennett 4f0dbf5c7b trim out unused platforms, we can add then back in on request
publish windows binaries as .exe
2016-06-29 23:08:54 -07:00
Cory Bennett 0e4e85aafd fix for versions with 'v' prefix 2016-06-28 23:07:06 -07:00
Cory Bennett fa4ac4b7ec fix v in version prefix 2016-06-28 23:02:03 -07:00
Cory Bennett a86642f2f4 Updated Changelog 2016-06-28 23:00:49 -07:00
Cory Bennett adcedc4855 use USERPROFILE instead of HOME for windows, rework paths to use
filepath.Join for better cross platform support
2016-06-29 06:57:59 +01:00
Cory Bennett d5156d54fe Merge branch 'master' of github.com:Netflix-Skunkworks/go-jira 2016-06-29 01:40:21 +01:00
Cory Bennett ab5c185bf3 make binary name .exe for windows 2016-06-29 01:26:28 +01:00
Cory Bennett 1cf943df13 update Makefile so it builds under cygwin on windows 2016-06-27 15:51:21 -07:00
Cory Bennett 1fef349ba2 fix build under Cygwin on Windows 2016-06-27 15:46:14 -07:00
coryb 42e787e042 Merge pull request #39 from mikepea/system_template_dir
Include templates from a system path
2016-03-30 14:47:17 -07:00
Mike Pountney cf10f53789 Include templates from a system path
I've found that several of the built-in templates need a bit of work
to be useful with our JIRA installation (eg #24), so this allows for admins
to make a site-specific set of defaults easily.
2016-03-30 20:20:55 +01:00
coryb 95403a4948 Merge pull request #38 from jglick/patch-1
Noting jira login
2016-03-30 08:48:21 -07:00
Jesse Glick e5053e88cd Noting jira login 2016-03-30 09:39:49 -04:00
coryb 964cf6b4c0 Merge pull request #37 from tobyjoe/add-resource-expansion
Added support for the ```expand``` option for Issues
2016-02-22 09:04:54 -08:00
tobyjoe fb4afc971a Added support for the ``expand`` option for Issues
The ```expand``` option is used to specify resource expansion in the
Jira REST API.

It's particularly useful for things like fetching the ```changelog``` of
an Issue.

This PR adds the support to the ```ListIssues``` and ```ViewIssue```
functions of the ```jira.Cli``` struct.

I'm happy to add tests, but there is currently no test suite in the
master branch, so I did not want to bog down the PR with tangential features.
2016-02-22 08:46:08 -05:00
Cory Bennett 7bfc6e810c change for api changes to go-logging 2016-02-11 15:11:06 -08:00
coryb 7ef8c4fe63 fix #36, build instructions needed updating 2016-02-10 17:03:27 -08:00
coryb b86921351a Merge pull request #35 from QuinnyPig/fix-readme
Fix path for installation instructions
2016-02-02 12:06:48 -08:00
Corey Quinn 4c51e5b1a6 Fix path for command 2016-02-02 11:19:54 -08:00
coryb e4fa47d48c Merge pull request #34 from jonathanio/fix/issuetypes-url-escaping
Fix issuetype calls adding URL escaping
2016-02-01 08:46:21 -08:00
Jonathan Wright e4a25e2ec2 Fix issuetype calls adding URL escaping
It is valid within JIRA to have issue types with spaces (for example we use
"Pro-Active Task") however the issuetype variable is not escaped prior to
formatting into the uri string.

This fix imports "net/url" and escapes all inclusions of issuetype into uri.
The only other variables formatted into uri are c.endpoint and issue which
shouldn't contain special characters.
2016-02-01 11:10:30 +00:00
Cory Bennett 47e9467de1 Updated Changelog 2016-01-29 09:04:49 -08:00
Cory Bennett a573c4e68e Merge branch 'master' of github.com:Netflix-Skunkworks/go-jira 2016-01-29 09:02:39 -08:00
coryb eb096f4cb3 Merge pull request #33 from mikepea/make_jirad
Fixes #32 - make path to cookieFile if it's not present
2016-01-29 09:01:29 -08:00
Mike Pountney 66445793e6 Fixes #32 - make path to cookieFile if it's not present
TL;DR, this ensures ~/jira.d is present, with 0755 perms.

If ~/jira.d isn't present, we can't write to the cookieFile, which
breaks CmdLogin. This is particularly an issue when using /etc/go-jira.yml
to get an entire team using go-jira easily :)

This fixes this by ensuring the cookieFile dir is present before
writing to it.
2016-01-29 11:12:25 +00:00
coryb 8190ad9925 Merge pull request #31 from mikepea/component_mgmt
Add component/components support: add and list for now.
2016-01-28 16:46:06 -08:00
Cory Bennett 2ec88ce5b0 Merge branch 'master' of github.com:Netflix-Skunkworks/go-jira 2016-01-28 16:42:08 -08:00
Mike Pountney d7b32269e1 Add component/components support: add and list for now.
This adds the basics of the component API:

* listing of components assigned to a project
* adding new components to a project.

The interface to 'add component' is similar to that of 'labels', as
previously discussed. We can implement remove/update later.
2016-01-29 00:22:26 +00:00
coryb cb67107a3e Merge pull request #30 from mikepea/unwatch_support
Support for removing a given watcher
2016-01-23 22:54:05 -08:00
coryb 3bf28d28e1 Merge pull request #26 from mikepea/vote_support
Add 'vote' and 'unvote'
2016-01-23 22:52:31 -08:00
Mike Pountney 383847a9d6 Tweak the CmdWatch contract and add watcher remove support
This adjusts the CmdWatch interface as per discussion in
https://github.com/Netflix-Skunkworks/go-jira/pull/26

It also exposes public versions of the c.getOptString and c.getOptBool
utility functions, again as discussed.

The interface to CmdWatch now includes the user to be watched (rather than
depending on the opt[] map. This makes CmdWatch more useful externally.

A '--remove' option has been created, to allow for removal of a given watcher.
This was deliberately not included in the defaults map, as it is specifically only
used for 'watch' command right now. It should be moved up to a default if it becomes
a more common option, I guess (as 'remove is false' isn't a bad default)
2016-01-24 03:00:39 +00:00
Mike Pountney 797edefc3f Amend vote/unvote to be vote/vote --down
This simplifies the interface to voting, as per the discussion in
https://github.com/Netflix-Skunkworks/go-jira/pull/26

Basically, DRY out the logic into a single CmdVote function based
around an 'up' bool. Similarly, make a single CLI command with an
option to do the downvote.
2016-01-24 02:23:08 +00:00
Mike Pountney 7b651d73c2 Merge branch 'master' into vote_support 2016-01-23 18:06:16 +00:00
Cory Bennett f6612c094b update version tag to prefix with v 2016-01-21 17:38:50 -08:00
Cory Bennett b0ed964e3e Updated Changelog 2016-01-21 17:36:23 -08:00
Cory Bennett ee0e780fa4 [issue #28] check to make sure we got back issuetypes for create metadata 2016-01-21 17:35:18 -08:00
Cory Bennett c5fe9f5383 gofmt 2016-01-21 10:52:15 -08:00
coryb 3f20139b75 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 6a88bb9c00 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
Mike Pountney c95e66e081 Add 'vote' and 'unvote'
This adds support for voting on issues via CmdVote() and CmdUnvote()

Voting on issues is always done as the logged in user, it appears you
can't case a vote for another user:

https://docs.atlassian.com/jira/REST/latest/#api/2/issue-addVote

This required adding a cli.delete() handler, naturally with no content
(as per RFC2616)

This is ripe for DRY-ing out, but I will leave that for a future PR.

Worth noting is that you cannot vote for your own issues, this results in:

    2016-01-13T21:35:41.315Z ERROR [cli.go:184] response status: 404 Not Found
    2016-01-13T21:35:41.315Z ERROR [commands.go:439] Unexpected Response From POST:
    {snip}
    {"errorMessages":["You cannot vote for an issue you have reported."],"errors":{}}
2016-01-13 21:41:34 +00:00
coryb 6734532719 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 a637b43f0e Reporter is not generally editable.
Support the lowest common denominator for default_edit_template, as per #23

`reporter` is not usually editable by unprivileged users. Even if it is left as-is,
the PUT request is trying to update it, resulting in the following error:

    {"errorMessages":[],"errors":{"reporter":"Field 'reporter' cannot be set. It is not on the appropriate screen, or unknown."}}

Commenting out the field leaves the information visible, so is a reasonable compromise.
2016-01-03 22:25:05 -08:00
Mike Pountney 303784fd56 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 40a7c65366 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 4f988b42f8 Merge branch 'master' into label_command 2016-01-03 19:50:47 -08:00
coryb 8712b4af6b 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 8977f3dd9b 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 da6cbd5021 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 408743b3df go fmt 2015-12-31 11:52:55 -08:00
Mike Pountney 230b52d528 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 425b571212 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 a7820fe7d5 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 7268b9e346 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 2df21d3023 Updated Changelog 2015-12-09 20:58:11 -08:00
Cory Bennett 3c30f3b039 fix jira trans TRANS ISSUE (case sensitivity issue), also go fmt 2015-12-09 20:57:10 -08:00
Cory Bennett dd419b161c Updated Changelog 2015-12-03 14:47:55 -08:00
Cory Bennett 4f4a89b972 need to default "quiet" to false 2015-12-03 14:47:31 -08:00
Cory Bennett d8d574a84a Updated Changelog 2015-12-03 12:56:18 -08:00
Cory Bennett c9ac162990 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 eaddfe6788 fix overrides 2015-12-03 12:21:55 -08:00
Cory Bennett 90ef56accd 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 228d7d0803 Updated Changelog 2015-11-23 17:09:17 -08:00
Cory Bennett a1eb4a1e90 jira edit should not require one arguemnt (allow for --query) 2015-11-23 17:08:40 -08:00
Cory Bennett e67ffb632f fix CURVER in case of building from src tar.gz 2015-11-23 14:35:41 -08:00
Cory Bennett 4c8bf5ddff tweak build 2015-11-23 14:23:29 -08:00
Cory Bennett b9c5dad3a1 Updated Changelog 2015-11-23 14:17:41 -08:00
Cory Bennett fd2a2fe65f [#17] print usage on missing arguments 2015-11-23 14:17:09 -08:00
Cory Bennett 7662885d0d bump version 2015-11-17 12:30:11 -08:00
Cory Bennett 8a7cd2275f Updated Changelog 2015-11-17 12:30:11 -08:00
coryb 1133dae127 Merge pull request #16 from oschrenk/fix-typo
s/enpoint/endpoint/g
2015-10-16 08:50:52 -07:00
Oliver Schrenk c5d251dec8 s/enpoint/endpoint/g 2015-10-16 11:12:25 +02:00
coryb 83da78dfdc 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 68d3baeac6 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 91e24759e0 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 4d7fdb87a9 Fix export-templates option (typo) 2015-10-15 12:05:33 +01:00
Cory Bennett 47ced2fa24 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 618f245c35 print PUT/POST data when using --dryrun to help debug 2015-10-06 11:50:47 -07:00
Cory Bennett 9d48ca222a bump version 2015-09-19 00:17:32 -07:00
Cory Bennett 6f3dba4e43 Updated Changelog 2015-09-19 00:17:32 -07:00
Cory Bennett 909eb06364 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 3977e530f7 bump version 2015-09-18 23:26:16 -07:00
Cory Bennett 52b1126b28 Updated Changelog 2015-09-18 23:26:16 -07:00
Cory Bennett 9348a4b4c0 fix exception from "jira create" 2015-09-18 23:24:50 -07:00
Cory Bennett 1c08a7d609 add some debug messages to help diagnose login failures 2015-09-18 23:18:38 -07:00
Cory Bennett bb9097b3ec bump version 2015-09-16 23:22:06 -07:00
Cory Bennett 355d35ff58 Updated Changelog 2015-09-16 23:22:06 -07:00
Cory Bennett 0ae6a0790a update version when we create changelog entries 2015-09-16 23:21:36 -07:00
Cory Bennett 8385ee2afb add --version 2015-09-16 23:16:51 -07:00
Cory Bennett 23347ddcad Updated Changelog 2015-09-16 23:05:02 -07:00
Cory Bennett 15ae929703 fix command line parser broken in 0.0.10 2015-09-16 23:04:36 -07:00
Cory Bennett e2c27f18d2 update changelog 2015-09-15 23:43:57 -07:00
Cory Bennett 2ee842270a Updated Changelog 2015-09-15 23:41:20 -07:00
Cory Bennett 07e3f7bea6 gofmt 2015-09-15 23:40:41 -07:00
Cory Bennett 67f30f7d46 update usage 2015-09-15 23:29:56 -07:00
Cory Bennett 23590d4173 allow for command aliasing in conjunction with executable config files 2015-09-15 23:27:06 -07:00
Cory Bennett ef7a57ec24 update usage 2015-09-15 22:55:19 -07:00
Cory Bennett 4a19089dd7 Updated Changelog 2015-09-15 20:34:19 -07:00
Cory Bennett f84e77fd84 * use forked yaml.v2 so as to not lose line terminations present in jira fields
* adding a |~ literal yaml syntax to just chomp a single newline (again to preserve
existing formatting in jira fields)
* for indent/comment allow for unicode line termination characters that yaml will use for parsing
2015-09-15 19:31:38 -07:00
Cory Bennett 4265913001 fix "edit" default option, change how defaults are dealt with for filters 2015-09-14 12:31:53 -07:00
Cory Bennett 968a9df71f for edit template add issue id as comment, also add "comments" as comment
so you can review the comment details while editing
2015-09-14 12:30:26 -07:00
Cory Bennett d6648687fb add "comment" template filter to comment out multiline statements 2015-09-14 12:29:30 -07:00
Cory Bennett c0070cfd58 add getOpt wrappers to get options with defaults 2015-09-14 12:29:05 -07:00
Cory Bennett d229ac1156 make --dryrun work
refactor config/option loading so command options override settings in config files
allow query options to be used on the "edit" command to iterate editing
2015-09-14 00:14:13 -07:00
Cory Bennett f8c8ddf78e move remove duplication for defaults 2015-09-13 17:31:28 -07:00
Cory Bennett 39318ebf75 adding "clean" target 2015-09-13 17:13:55 -07:00
Cory Bennett 7bbd571078 use optigo for option parsing, drop docopt 2015-09-13 17:00:03 -07:00
Cory Bennett ea67a77863 allow "abort: true" to be set while editing to cancel the edit operation 2015-09-11 23:47:09 -07:00
Cory Bennett 99f62a9e99 gofmt 2015-09-11 22:13:23 -07:00
Cory Bennett 85f617c13c tweak changelog generation 2015-09-11 22:13:12 -07:00
Cory Bennett 0a4d6aece8 fix missing arg of Sprintf 2015-09-11 22:12:38 -07:00
Cory Bennett e69b65cd7d if no changes are made on edit templates then abort edit 2015-09-11 22:04:02 -07:00
Cory Bennett 0ede380834 updated for 0.0.8 2015-09-11 21:18:27 -07:00
Cory Bennett faf0793312 bump version 2015-07-31 11:12:24 -07:00
Cory Bennett eeb63441dc update for max_results option 2015-07-31 11:11:36 -07:00
coryb 8d3a6ddd80 Merge pull request #11 from mikepea/max_results_option
Add --max_results option for 'ls'
2015-07-31 10:37:04 -07:00
Mike Pountney e06ff0cc54 Add --max_results option for 'ls'
This closes #10

Shifts the hardcoded maxResults value for the cmdList json body
into a 'max_results' option.

Note that testing against our JIRA instance, in a project with
more than 1000 open issues, suggests that the JIRA has an internal
limit of 1000 results in a single query.
2015-07-31 17:38:45 +01:00
Cory Bennett cfd09c306b update Changelog 2015-07-01 10:22:06 -07:00
Cory Bennett 6374897b58 fix the "all" build 2015-07-01 10:20:48 -07:00
Cory Bennett 4f45159dbf add cross-compile setup task 2015-07-01 09:13:03 -07:00
Cory Bennett c3d62c0fa1 udpate version 2015-07-01 09:12:40 -07:00
Cory Bennett b9e5564dff Merge branch 'master' of github.com:Netflix-Skunkworks/go-jira 2015-07-01 08:50:40 -07:00
coryb e8631c6b6d Merge pull request #9 from nelfin/quickfix/take-user
fix "take" command not honouring user option
2015-06-30 23:29:09 -07:00
Andrew Haigh 8f1d2b9f76 fix "take" command not honouring user option
"take" was simply a partial on "assign", but accidentally used the value
of $USER from the environment rather than `opts["user"]`, preventing the
user from overriding this value in config.yml or as a command-line
argument.
2015-07-01 16:21:26 +10:00
Cory Bennett 06f57fea5b fix typo 2015-03-02 14:54:24 -08:00
Cory Bennett 39b14b7ff1 strip binaries when building "all" 2015-03-02 14:34:38 -08:00
Cory Bennett 2d2e5e26d0 updating 2015-02-27 17:52:07 -08:00
Cory Bennett 7b2122742b bump version 2015-02-27 17:50:08 -08:00
Cory Bennett 701f091668 allow --sort= to disable sort override 2015-02-24 17:49:21 -08:00
Cory Bennett 82fd9b9ef3 fix default JIRA_OPERATION env variable 2015-02-24 17:48:59 -08:00
Cory Bennett ebf170085c automatically close duplicate issues with "Duplicate" resolution 2015-02-23 12:09:40 -08:00
Cory Bennett 050848adea set JIRA_OPERATION to "view" when no operation used (ie: jira GOJIRA-123) 2015-02-23 12:08:54 -08:00
Cory Bennett f35903005c add --sort option to "list" command 2015-02-23 12:08:33 -08:00
Cory Bennett 9531f3fef1 adding changlog 2015-02-21 23:36:42 -08:00
Cory Bennett 093a58de12 bump version 2015-02-21 23:27:53 -08:00
Cory Bennett ec221cb41a fix typo 2015-02-21 21:35:31 -08:00
Cory Bennett f4b5428a37 update README.md 2015-02-21 21:32:42 -08:00
Cory Bennett 7186fb3e3e handle editor having arguments 2015-02-21 21:15:02 -08:00
Cory Bennett 3e6f2b396b add more template error handling 2015-02-20 15:03:40 -08:00
Cory Bennett d9e4db2f7a gofmt 2015-02-20 14:39:01 -08:00
Cory Bennett e59cd45c2d gofmt 2015-02-20 14:37:55 -08:00
Cory Bennett 4db2e4e8b4 allow create template to specify defalt watchers with -o watchers=... 2015-02-20 14:29:33 -08:00
Cory Bennett affd313cf7 update README.md 2015-02-20 14:29:19 -08:00
Cory Bennett 7a2f7f5726 if config files are executable then run them and parse the output 2015-02-20 13:37:24 -08:00
Cory Bennett c7f1133500 update usage 2015-02-19 18:03:33 -08:00
Cory Bennett 1d76e8f289 bump version 2015-02-19 13:09:32 -08:00
Cory Bennett 343fbb6e56 add --template option to export-templates to export a single template 2015-02-19 13:07:46 -08:00
Cory Bennett 63a1fd9c60 update for table template 2015-02-19 12:38:12 -08:00
Cory Bennett 8954ec1cfa add "table" template to be used with "list" command 2015-02-19 12:36:51 -08:00
Cory Bennett 21d3c884bc prevent line wrapping 2015-02-19 11:17:33 -08:00
Cory Bennett d5330fd78e bump version 2015-02-19 10:31:20 -08:00
Cory Bennett 2dcf665d38 [issue #8] detect X-Seraph-Loginreason: AUTHENTICATION_DENIED header to catch login failures 2015-02-19 10:05:51 -08:00
coryb 4c2d77ccca Merge pull request #7 from jaybuff/empty-projects
validate project
2015-02-18 21:06:55 -08:00
Jay Buffington 1b69d126c0 project should always be uppercase
Jira docs say as much:
https://confluence.atlassian.com/display/JIRA/Changing+the+Project+Key+Format#ChangingtheProjectKeyFormat-prerequisites
2015-02-18 19:26:36 -08:00
Jay Buffington 4924dfaab3 if response is 400, check json for errorMessages and log them 2015-02-18 17:39:37 -08:00
Jay Buffington dc5ae42c58 validate project 2015-02-18 17:32:56 -08:00
Cory Bennett 93ef7a2a3e bump version 2015-02-18 15:02:46 -08:00
Cory Bennett 56e3cec6d4 adding simple Makefile to make cross compiling a bit easier 2015-02-18 15:01:47 -08:00
Cory Bennett 13d77c1b4d adding missing --override arguments to various transition commands
add browse command
2015-02-18 15:01:12 -08:00
coryb 4aeba2e34e Update README.md 2015-02-18 14:43:20 -08:00
coryb 4152c37335 Update README.md 2015-02-18 14:38:47 -08:00
coryb 97fccbddeb Update README.md 2015-02-18 13:38:46 -08:00
coryb 2ac1a3e7f2 Update README.md 2015-02-18 13:36:28 -08:00
coryb 0edb1a354c Update README.md 2015-02-18 13:35:53 -08:00
Cory Bennett 8f1e7b6619 add download link 2015-02-18 13:29:20 -08:00
Cory Bennett 646020f8f5 update usage in readme 2015-02-18 11:38:12 -08:00
Cory Bennett 484135e6eb fill in issuetype before running the "create" template 2015-02-18 11:34:24 -08:00
Cory Bennett f7ab5753e9 minor tweak to prevent exceptions in some templates
set default for "commit" command to use editor
2015-02-17 16:03:32 -08:00
Cory Bennett 005607d799 added created field to "view" template 2015-02-17 11:09:24 -08:00
Cory Bennett 165e1144ed fix view template for empty description 2015-02-17 10:40:11 -08:00
Cory Bennett 68644878fc add "transition" editable template so you can modify other fields during transitions
add --noedit/--edit options for various commands
sanitize the yaml from the editor so we dont send empty strings when unedited
2015-02-17 10:19:54 -08:00
Cory Bennett 2baf87af9f add --watcher and --reporter options to "list" 2015-02-16 15:27:47 -08:00
Cory Bennett 766923be02 dont write response to log unless request, otherwise response body will have
been read and unavailable to parse later in client code
2015-02-16 15:19:25 -08:00
Cory Bennett 70fcdd8ef4 [issue #4] make sure ~/.jira.d/tmp exists before we write to it during create/edit 2015-02-16 14:31:39 -08:00
Cory Bennett 018f12e3c9 add --queryfields option to restrict data returned from jira search api. This is for performance
as the jira issue data can be quite large and we use almost none of it in the "list" template
2015-02-16 14:06:36 -08:00
Cory Bennett 12fc734069 dont default issuetype unless it is a require param, otherwise "ls" is restricted 2015-02-16 13:21:15 -08:00
Cory Bennett 21d9f74635 adding --browse option to most command which should open the issue in your favorite browser 2015-02-16 10:34:04 -08:00
Cory Bennett b7bdb43471 Merge branch 'jaybuff-nil-assignee' 2015-02-16 09:51:43 -08:00
Cory Bennett 961ac38a08 fix inner assignee template 2015-02-16 09:51:22 -08:00
Cory Bennett 172ac70046 Merge branch 'nil-assignee' of https://github.com/jaybuff/go-jira into jaybuff-nil-assignee 2015-02-16 09:50:57 -08:00
Cory Bennett f206ae3ba2 fix indent for override coments in edit template 2015-02-16 08:53:37 -08:00
coryb 52966e0aa8 Merge pull request #2 from jaybuff/clean-up
Clean up
2015-02-16 08:50:36 -08:00
Jay Buffington 7d25d7715f default view template shouldn't fail when assignee is nil
The default view template was failing with this error whenever I tried to
view a jira that had no assignee:
assignee: 2015-02-16T08:31:58.564-08:00 ERROR [util.go:109] Failed to execute template: template: template:7:20: executing "template" at <.fields.assignee.nam...>: nil pointer evaluating interface {}.name
2015-02-16 08:48:10 -08:00
Jay Buffington 30e2237b6a clean up README (mostly spelling fixes) 2015-02-16 08:44:12 -08:00
Jay Buffington 88af72543a add .gitignore 2015-02-16 08:32:18 -08:00
Cory Bennett 3a0867d0ed [issue #1] trim trailing / on uri 2015-02-15 21:02:28 -08:00
Cory Bennett deb3a7255f . 2015-02-13 16:58:57 -08:00
Cory Bennett 51e0a341e8 update for golang minver 2015-02-13 16:58:26 -08:00
Cory Bennett c64f5cf44e udpate usage 2015-02-13 16:48:57 -08:00
Cory Bennett 1054d506b3 tweak usage ordering
fix looking for generic "create" template
2015-02-13 16:48:10 -08:00
Cory Bennett 6f3e25d1a7 udpate Editor section 2015-02-13 16:48:03 -08:00
Cory Bennett 8557c4f384 update 2015-02-13 16:38:20 -08:00
Cory Bennett 1cff26364b dont default endpoint make users specify it on command line or in config 2015-02-13 16:38:01 -08:00
Cory Bennett 5422bfb152 tweak getTemplate routine 2015-02-13 16:03:03 -08:00
Cory Bennett a872b66777 go fmt 2015-02-13 15:44:07 -08:00
Cory Bennett eb0c0823b0 adding export-templates command
updated edit template to allow for -o overrides
2015-02-13 14:07:20 -08:00
Cory Bennett 9c890a02a8 sort usage options, update README 2015-02-13 13:19:10 -08:00
Cory Bennett 618af1a6e1 add options to ls to allow for dynamically creating some simple JQL 2015-02-13 13:16:18 -08:00
Cory Bennett e433c536c0 add take/give commands 2015-02-13 12:52:35 -08:00
Cory Bennett 4a33dd8e20 adding "comment" command 2015-02-13 12:33:47 -08:00
Cory Bennett 0b5e00d2e3 handle various issue state transitions 2015-02-13 12:07:03 -08:00
Cory Bennett d6ad05ac9c adding commands:
* create
* dups
* blocks
* watch
2015-02-12 23:41:39 -08:00
Cory Bennett 081fda79f3 work in progress, minor refactor. Added commands:
* login
* editmeta ISSUE
* edit ISSUE
* issuetypes [-p PROJECT]
* createmeta [-p PROJECT] [-i ISSUETYPE]
* transitions ISSUE

make --template argumetn work
2015-02-12 15:50:08 -08:00
Cory Bennett df67697fb7 need to set GOBIN for install to work 2015-02-10 16:31:48 -08:00
Cory Bennett e6b113d3fb update readme, change config location 2015-02-10 16:27:16 -08:00
Cory Bennett 477c3e980c initial checkin, work in progress 2015-02-10 16:17:13 -08:00
217 changed files with 3734 additions and 13609 deletions
-2
View File
@@ -1,2 +0,0 @@
# one of these users must approve the PR before merging
* @coryb @mvdan @vanniktech @mikepea
+14 -10
View File
@@ -1,12 +1,16 @@
bin/
pkg/
src/code.google.com/
src/github.com/docopt/
src/github.com/mgutz/
src/github.com/op/
src/gopkg.in/
jira
jira.exe
schemas/*.json
/_t/.gnupg/random_seed
/_t/issue.props
/_t/attach.props
/_t/garbage.bin
/_t/attach1.txt
/_t/binary.out
/_t/foobar.bin
/_t/.password-store/GoJira/api-token:gojira@corybennett.org.gpg
/_t/.password-store/GoJira/api-token:mothra@corybennett.org.gpg
dist
t/issue.props
t/.jira.d/templates
dist/
src/
t/.maven-cache
vendor/
-12
View File
@@ -1,12 +0,0 @@
config:
stop: true
password-source: pass
endpoint: https://go-jira.atlassian.net
user: admin
login: atlassian@corybennett.org
queries:
todo: |
resolution = unresolved {{if .project}}AND project = '{{.project}}'{{end}} AND status = 'To Do'
open: |
resolution = unresolved {{if .project}}AND project = '{{.project}}'{{end}} AND status = 'Open'
+15 -12
View File
@@ -1,19 +1,22 @@
addons:
apt:
packages:
- pass
- gnupg
# docker required for the tests (run via prove)
sudo: required
services:
- docker
language: go
go:
- 1.12.x
env:
- GO111MODULE=on
- 1.6
matrix:
fast_finish: true
script:
- go test ./...
- go vet -composites=false ./...
- make vet
- make lint
- make
- make prove 2>&1
- prove -v
cache:
directories:
- t/.maven-cache
-223
View File
@@ -1,228 +1,5 @@
# Changelog
## 1.0.20 - 2018-08-04
* [[#201](https://github.com/Netflix-Skunkworks/go-jira/issues/201)] update required library, failing to populate cookiejar from cookies file [Cory Bennett] [[ee69afa](https://github.com/Netflix-Skunkworks/go-jira/commit/ee69afa)]
## 1.0.19 - 2018-08-02
* [[#199](https://github.com/Netflix-Skunkworks/go-jira/issues/199)] [[#198](https://github.com/Netflix-Skunkworks/go-jira/issues/198)] update http client library, fix issues with internal login retries [Cory Bennett] [[0cba806](https://github.com/Netflix-Skunkworks/go-jira/commit/0cba806)]
## 1.0.18 - 2018-07-29
* [[#196](https://github.com/Netflix-Skunkworks/go-jira/issues/196)] add `jira session` command to show session information if user is authenticated [Cory Bennett] [[f592107](https://github.com/Netflix-Skunkworks/go-jira/commit/f592107)]
* [[#192](https://github.com/Netflix-Skunkworks/go-jira/issues/192)] fix template usage for 'fixVersions' in transition template [Cory Bennett] [[c9a4e30](https://github.com/Netflix-Skunkworks/go-jira/commit/c9a4e30)]
* move HandleExit to the jiracli package [Cory Bennett] [[ef9b731](https://github.com/Netflix-Skunkworks/go-jira/commit/ef9b731)]
* they broke golang.org/x/net: ERROR: vendor/golang.org/x/net/proxy/socks5.go:11:2: use of internal package not allowed [Cory Bennett] [[7191c77](https://github.com/Netflix-Skunkworks/go-jira/commit/7191c77)]
* udpate deps [Cory Bennett] [[d16bcc2](https://github.com/Netflix-Skunkworks/go-jira/commit/d16bcc2)]
* udpate for figtree api changes [Cory Bennett] [[07ba74b](https://github.com/Netflix-Skunkworks/go-jira/commit/07ba74b)]
* udpate to use latest dep, update figtree [Cory Bennett] [[462ef1c](https://github.com/Netflix-Skunkworks/go-jira/commit/462ef1c)]
* [[#171](https://github.com/Netflix-Skunkworks/go-jira/issues/171)] change proposed PasswordPath to PasswordName [Cory Bennett] [[213a7e0](https://github.com/Netflix-Skunkworks/go-jira/commit/213a7e0)]
* add pass path to config [dvogt23] [[fa01ff5](https://github.com/Netflix-Skunkworks/go-jira/commit/fa01ff5)]
## 1.0.17 - 2018-04-15
* fix IsTerminal usage for windows [Cory Bennett] [[7f9595c](https://github.com/Netflix-Skunkworks/go-jira/commit/7f9595c)]
* [[#166](https://github.com/Netflix-Skunkworks/go-jira/issues/166)] fix issue when editing templates specified with full path [Cory Bennett] [[d787ac0](https://github.com/Netflix-Skunkworks/go-jira/commit/d787ac0)]
* only prompt on logout if stdin and stdout are terminals [Cory Bennett] [[09a61c3](https://github.com/Netflix-Skunkworks/go-jira/commit/09a61c3)]
* [[#163](https://github.com/Netflix-Skunkworks/go-jira/issues/163)] fix url path join logic [Cory Bennett] [[9146346](https://github.com/Netflix-Skunkworks/go-jira/commit/9146346)]
* [[#160](https://github.com/Netflix-Skunkworks/go-jira/issues/160)] prompt when api-token is invalid to get new token [Cory Bennett] [[e639cce](https://github.com/Netflix-Skunkworks/go-jira/commit/e639cce)]
* [[#157](https://github.com/Netflix-Skunkworks/go-jira/issues/157)] add `password-directory: path` to allow overriding PASSWORD_STORE_DIR from configs [Cory Bennett] [[06b26c9](https://github.com/Netflix-Skunkworks/go-jira/commit/06b26c9)]
* [[#160](https://github.com/Netflix-Skunkworks/go-jira/issues/160)] allow `jira logout` to delete your api-token from keychain [Cory Bennett] [[bd3cf99](https://github.com/Netflix-Skunkworks/go-jira/commit/bd3cf99)]
## 1.0.16 - 2018-04-01
* [[#159](https://github.com/Netflix-Skunkworks/go-jira/issues/159)] fix `slice bounds out of range` error in `abbrev` template function [Cory Bennett] [[359bec2](https://github.com/Netflix-Skunkworks/go-jira/commit/359bec2)]
* [[#158](https://github.com/Netflix-Skunkworks/go-jira/issues/158)] always print usage to stdout [Cory Bennett] [[79c83f6](https://github.com/Netflix-Skunkworks/go-jira/commit/79c83f6)]
## 1.0.15 - 2018-03-08
* [[#147](https://github.com/Netflix-Skunkworks/go-jira/issues/147)] [[#148](https://github.com/Netflix-Skunkworks/go-jira/issues/148)] add support for api token based authentication [Cory Bennett] [[edb0662](https://github.com/Netflix-Skunkworks/go-jira/commit/edb0662)]
* refactor to simplify main [Cory Bennett] [[43ebc84](https://github.com/Netflix-Skunkworks/go-jira/commit/43ebc84)] [[0d7c1a7](https://github.com/Netflix-Skunkworks/go-jira/commit/0d7c1a7)]
* [[#145](https://github.com/Netflix-Skunkworks/go-jira/issues/145)] fix to match AuthProvider interface [Cory Bennett] [[80325a5](https://github.com/Netflix-Skunkworks/go-jira/commit/80325a5)]
* [[#141](https://github.com/Netflix-Skunkworks/go-jira/issues/141)] better handling in responseError for non-json error responses [Cory Bennett] [[20a9666](https://github.com/Netflix-Skunkworks/go-jira/commit/20a9666)]
* Update unexportTemplates.go [GitHub] [[6da9974](https://github.com/Netflix-Skunkworks/go-jira/commit/6da9974)]
* [[#139](https://github.com/Netflix-Skunkworks/go-jira/issues/139)] add shellquote and toMinJson template functions [Cory Bennett] [[8c7ca38](https://github.com/Netflix-Skunkworks/go-jira/commit/8c7ca38)]
* [[#137](https://github.com/Netflix-Skunkworks/go-jira/issues/137)] update kingpeon dep to allow access to dynamic command structure [Cory Bennett] [[425fa63](https://github.com/Netflix-Skunkworks/go-jira/commit/425fa63)]
* field name is "comment" not "comments" [Cory Bennett] [[464742c](https://github.com/Netflix-Skunkworks/go-jira/commit/464742c)]
## 1.0.14 - 2017-11-04
* [[#131](https://github.com/Netflix-Skunkworks/go-jira/issues/131)] fix parsing global options before command execution (allow unixproxy/socksproxy to be set in config.yml) [Cory Bennett] [[042bc48](https://github.com/Netflix-Skunkworks/go-jira/commit/042bc48)]
* add/update deps [Cory Bennett] [[a2e36e8](https://github.com/Netflix-Skunkworks/go-jira/commit/a2e36e8)]
* add support for using socks proxy [onionjake] [[ff985f9](https://github.com/Netflix-Skunkworks/go-jira/commit/ff985f9)]
## 1.0.13 - 2017-10-28
* fix transition command [Cory Bennett] [[9597f9b](https://github.com/Netflix-Skunkworks/go-jira/commit/9597f9b)]
* fix default values to load after parsing configs [Cory Bennett] [[c9b5054](https://github.com/Netflix-Skunkworks/go-jira/commit/c9b5054)]
* add test to make sure IssueType.Fields does not disappear on regeneration [Cory Bennett] [[3966def](https://github.com/Netflix-Skunkworks/go-jira/commit/3966def)]
* add tests for validating changes to auto-generated jiradata files [Cory Bennett] [[41d1a7c](https://github.com/Netflix-Skunkworks/go-jira/commit/41d1a7c)]
* Fix typo in 'logout' command help [Cory Bennett] [[9000777](https://github.com/Netflix-Skunkworks/go-jira/commit/9000777)]
* Add URL escaping to an additional issuetype call [Cory Bennett] [[7bfa241](https://github.com/Netflix-Skunkworks/go-jira/commit/7bfa241)]
* Add --resolution option [Cory Bennett] [[e6600cf](https://github.com/Netflix-Skunkworks/go-jira/commit/e6600cf)]
* Create Metadata Not Populated Correctly [Dillon Buchanan] [[093c510](https://github.com/Netflix-Skunkworks/go-jira/commit/093c510)]
* add regexReplace template function [Dirk Heilig] [[d3e294e](https://github.com/Netflix-Skunkworks/go-jira/commit/d3e294e)]
## 1.0.12 - 2017-10-04
* add `{{env.VARNAME}}` template support to allow use of env vars [Cory Bennett] [[4d74554](https://github.com/Netflix-Skunkworks/go-jira/commit/4d74554)]
## 1.0.11 - 2017-09-26
* [[#115](https://github.com/Netflix-Skunkworks/go-jira/issues/115)] fix transition template for description [Cory Bennett] [[986cc78](https://github.com/Netflix-Skunkworks/go-jira/commit/986cc78)]
* update edit command to set queryFields on search to match what is used in template [Cory Bennett] [[3913726](https://github.com/Netflix-Skunkworks/go-jira/commit/3913726)]
* fix edit with query loop, allow continuation when not submitting previous issue [Cory Bennett] [[0ba8aa0](https://github.com/Netflix-Skunkworks/go-jira/commit/0ba8aa0)]
* fix edit when priority is not set [Cory Bennett] [[098eb99](https://github.com/Netflix-Skunkworks/go-jira/commit/098eb99)]
* flatten CommandRegistry list to make it more readable [Cory Bennett] [[2ddaed2](https://github.com/Netflix-Skunkworks/go-jira/commit/2ddaed2)]
## 1.0.10 - 2017-09-18
* clean up usage formatting, print aliases [Cory Bennett] [[9f433ac](https://github.com/Netflix-Skunkworks/go-jira/commit/9f433ac)]
* fix edit [Cory Bennett] [[4c6b36c](https://github.com/Netflix-Skunkworks/go-jira/commit/4c6b36c)]
* fix named query template expansion [Cory Bennett] [[a8eaa97](https://github.com/Netflix-Skunkworks/go-jira/commit/a8eaa97)]
* fix usage message [Cory Bennett] [[cd3cfd8](https://github.com/Netflix-Skunkworks/go-jira/commit/cd3cfd8)]
## 1.0.9 - 2017-09-17
* need issuetype to use the default list table template now [Cory Bennett] [[3e8b9bd](https://github.com/Netflix-Skunkworks/go-jira/commit/3e8b9bd)]
* [[#102](https://github.com/Netflix-Skunkworks/go-jira/issues/102)] add issuetype into the default queryfields and add it to the default `table` list template [Cory Bennett] [[c9d8dfb](https://github.com/Netflix-Skunkworks/go-jira/commit/c9d8dfb)]
## 1.0.8 - 2017-09-17
* [[#100](https://github.com/Netflix-Skunkworks/go-jira/issues/100)] add support for posting, fetching, listing and removing attachments [Cory Bennett] [[66eb7bf](https://github.com/Netflix-Skunkworks/go-jira/commit/66eb7bf)]
## 1.0.7 - 2017-09-15
* [[#87](https://github.com/Netflix-Skunkworks/go-jira/issues/87)] add various commands for interacting with epics [Cory Bennett] [[893454f](https://github.com/Netflix-Skunkworks/go-jira/commit/893454f)]
## 1.0.6 - 2017-09-13
* tweaks for templates in named queries to work better [Cory Bennett] [[00cba79](https://github.com/Netflix-Skunkworks/go-jira/commit/00cba79)]
* [[#99](https://github.com/Netflix-Skunkworks/go-jira/issues/99)] add support for named queries to be stored in configs [Cory Bennett] [[fb43753](https://github.com/Netflix-Skunkworks/go-jira/commit/fb43753)]
* [[#98](https://github.com/Netflix-Skunkworks/go-jira/issues/98)] add `--status` option for JQL filter on status with `list` command [Cory Bennett] [[5da04c1](https://github.com/Netflix-Skunkworks/go-jira/commit/5da04c1)]
## 1.0.5 - 2017-09-11
* use --gjq for GJson Query to filter json response data [Cory Bennett] [[608e586](https://github.com/Netflix-Skunkworks/go-jira/commit/608e586)]
* fix field tag syntax [Cory Bennett] [[2c552ac](https://github.com/Netflix-Skunkworks/go-jira/commit/2c552ac)]
* add '{{jira}}' template macro to refer to path of currently running jira command [Cory Bennett] [[941824d](https://github.com/Netflix-Skunkworks/go-jira/commit/941824d)]
## 1.0.4 - 2017-09-08
* update deps for kingpeon update use os.exec instead of syscall.exec for windows [Cory Bennett] [[86b963b](https://github.com/Netflix-Skunkworks/go-jira/commit/86b963b)]
## 1.0.3 - 2017-09-06
* [[#66](https://github.com/Netflix-Skunkworks/go-jira/issues/66)] add --started option to `jira worklog add` to change the start time for worklog [Cory Bennett] [[e6faee1](https://github.com/Netflix-Skunkworks/go-jira/commit/e6faee1)]
* [[#45](https://github.com/Netflix-Skunkworks/go-jira/issues/45)] automatically add comment to issue even if transition does not support comment updates during transtion [Cory Bennett] [[c4be59c](https://github.com/Netflix-Skunkworks/go-jira/commit/c4be59c)]
## 1.0.2 - 2017-09-06
* update dependencies [Cory Bennett] [[aa876cd](https://github.com/Netflix-Skunkworks/go-jira/commit/aa876cd)]
* update for github.com/AlecAivazis/survey => gopkg.in/AlecAivazis/survey.v1 package [Cory Bennett] [[9453179](https://github.com/Netflix-Skunkworks/go-jira/commit/9453179)]
* use stdout to determin output terminal size [Cory Bennett] [[4d79af4](https://github.com/Netflix-Skunkworks/go-jira/commit/4d79af4)]
## 1.0.1 - 2017-09-06
* [[#13](https://github.com/Netflix-Skunkworks/go-jira/issues/13)] change default input syntax to not require escaping for special characters [Cory Bennett] [[1106558](https://github.com/Netflix-Skunkworks/go-jira/commit/1106558)]
## 1.0.0 - 2017-09-05
* fix build for windows [Cory Bennett] [[1b854da](https://github.com/Netflix-Skunkworks/go-jira/commit/1b854da)]
* change the default log output format [Cory Bennett] [[f1b8c64](https://github.com/Netflix-Skunkworks/go-jira/commit/f1b8c64)]
* tweak auto-login so it does not print the standard `jira login` command output [Cory Bennett] [[49f6cdc](https://github.com/Netflix-Skunkworks/go-jira/commit/49f6cdc)]
* add --quiet global option [Cory Bennett] [[c226077](https://github.com/Netflix-Skunkworks/go-jira/commit/c226077)]
* refactor to allow for --insecure and --unixproxy arguments [Cory Bennett] [[c0358eb](https://github.com/Netflix-Skunkworks/go-jira/commit/c0358eb)]
* handle html response on expired cookies (require X-Ausername header to always be present) [Cory Bennett] [[21920c5](https://github.com/Netflix-Skunkworks/go-jira/commit/21920c5)]
* allow login prompt to be interrupted [Cory Bennett] [[7ab6c22](https://github.com/Netflix-Skunkworks/go-jira/commit/7ab6c22)]
* fmt -> log typo [Cory Bennett] [[bccf09f](https://github.com/Netflix-Skunkworks/go-jira/commit/bccf09f)]
* make ~/.jira.d directory if not already present [Cory Bennett] [[e72479c](https://github.com/Netflix-Skunkworks/go-jira/commit/e72479c)]
* fix go vet [Cory Bennett] [[e04b506](https://github.com/Netflix-Skunkworks/go-jira/commit/e04b506)]
* fix tests [Cory Bennett] [[ba35f55](https://github.com/Netflix-Skunkworks/go-jira/commit/ba35f55)]
* add OK printf [Cory Bennett] [[dc02181](https://github.com/Netflix-Skunkworks/go-jira/commit/dc02181)]
* change --method to use -M for backwards compat [Cory Bennett] [[b120c0b](https://github.com/Netflix-Skunkworks/go-jira/commit/b120c0b)]
* add resolution to dup'd issues when necessary [Cory Bennett] [[2638396](https://github.com/Netflix-Skunkworks/go-jira/commit/2638396)]
* call correct function for `labels remove|set` commands [Cory Bennett] [[ad1a62a](https://github.com/Netflix-Skunkworks/go-jira/commit/ad1a62a)]
* data argument is optional (for GET and DELETE requests) [Cory Bennett] [[4b60313](https://github.com/Netflix-Skunkworks/go-jira/commit/4b60313)]
* fix usage, overrides not serialized correctly [Cory Bennett] [[84119a2](https://github.com/Netflix-Skunkworks/go-jira/commit/84119a2)]
* fix `jira ISSUE-123` command line parsing [Cory Bennett] [[fa4ac25](https://github.com/Netflix-Skunkworks/go-jira/commit/fa4ac25)]
* add logger object to jiracmd [Cory Bennett] [[aed952b](https://github.com/Netflix-Skunkworks/go-jira/commit/aed952b)]
* refactor for GlobalOptions and CommonOptions [Cory Bennett] [[979da1f](https://github.com/Netflix-Skunkworks/go-jira/commit/979da1f)]
* move commands from jiracli package to jiracmd package [Cory Bennett] [[0a5510b](https://github.com/Netflix-Skunkworks/go-jira/commit/0a5510b)]
* use jiracli.Error object to disambiguate between kingpin errors and cli errors [Cory Bennett] [[fb1bfeb](https://github.com/Netflix-Skunkworks/go-jira/commit/fb1bfeb)]
* fix stray newline for list table template [Cory Bennett] [[36c26c5](https://github.com/Netflix-Skunkworks/go-jira/commit/36c26c5)]
* fix dynamic table output when not on tty [Cory Bennett] [[3942f6f](https://github.com/Netflix-Skunkworks/go-jira/commit/3942f6f)]
* when using --verbose set the JIRA_DEBUG environment variable so custom-commands can auto enable verbose output [Cory Bennett] [[da9a2b2](https://github.com/Netflix-Skunkworks/go-jira/commit/da9a2b2)]
* make `jira ISSUE-123` usage call `jira view ISSUE-123` [Cory Bennett] [[ec0858b](https://github.com/Netflix-Skunkworks/go-jira/commit/ec0858b)]
* integrate kingpeon library to allow for custom commands via configuration [Cory Bennett] [[301a61f](https://github.com/Netflix-Skunkworks/go-jira/commit/301a61f)]
* use terminal width to adjust list table output [Cory Bennett] [[2a081dd](https://github.com/Netflix-Skunkworks/go-jira/commit/2a081dd)]
* set yaml/json tags for option structs [Cory Bennett] [[f52d2c4](https://github.com/Netflix-Skunkworks/go-jira/commit/f52d2c4)]
* update generated data files [Cory Bennett] [[c89f11d](https://github.com/Netflix-Skunkworks/go-jira/commit/c89f11d)]
* automatically login when anonymous user detected [Cory Bennett] [[21add54](https://github.com/Netflix-Skunkworks/go-jira/commit/21add54)]
* refactor trivial objects in favor of arguments to static functions [Cory Bennett] [[1f345ce](https://github.com/Netflix-Skunkworks/go-jira/commit/1f345ce)]
* set JIRA_OPERATION when parsing configs. Use figtree config types for options to make defaulting work [Cory Bennett] [[5716a7c](https://github.com/Netflix-Skunkworks/go-jira/commit/5716a7c)]
* add better handing for usage error [Cory Bennett] [[b235dcc](https://github.com/Netflix-Skunkworks/go-jira/commit/b235dcc)]
* adding `request` command, removing dead code [Cory Bennett] [[56b1c9d](https://github.com/Netflix-Skunkworks/go-jira/commit/56b1c9d)]
* adding Do required for request language [Cory Bennett] [[a1c2849](https://github.com/Netflix-Skunkworks/go-jira/commit/a1c2849)]
* add `browse` command and implement -b option for most operations [Cory Bennett] [[a91b9d5](https://github.com/Netflix-Skunkworks/go-jira/commit/a91b9d5)]
* fix IssueAssign [Cory Bennett] [[f32cc70](https://github.com/Netflix-Skunkworks/go-jira/commit/f32cc70)]
* merge in update for upstream changes [#104](https://github.com/Netflix-Skunkworks/go-jira/issues/104) [Cory Bennett] [[19d8686](https://github.com/Netflix-Skunkworks/go-jira/commit/19d8686)]
* add `export-templates` command [Cory Bennett] [[abaad56](https://github.com/Netflix-Skunkworks/go-jira/commit/abaad56)]
* add `issuetypes` command [Cory Bennett] [[da39323](https://github.com/Netflix-Skunkworks/go-jira/commit/da39323)]
* add `components` command [Cory Bennett] [[0bd3ca2](https://github.com/Netflix-Skunkworks/go-jira/commit/0bd3ca2)]
* add `component add` command [Cory Bennett] [[cc90610](https://github.com/Netflix-Skunkworks/go-jira/commit/cc90610)]
* add `take`, `unassign` and `assign|give` commands [Cory Bennett] [[959524a](https://github.com/Netflix-Skunkworks/go-jira/commit/959524a)]
* adding `labels [add|set|remove]` commands [Cory Bennett] [[9161861](https://github.com/Netflix-Skunkworks/go-jira/commit/9161861)]
* add `comment` command [Cory Bennett] [[f0b08c5](https://github.com/Netflix-Skunkworks/go-jira/commit/f0b08c5)]
* add `watch` command [Cory Bennett] [[ec0ac3c](https://github.com/Netflix-Skunkworks/go-jira/commit/ec0ac3c)]
* add `rank ISSUE after|before ISSUE` command [Cory Bennett] [[8b863d2](https://github.com/Netflix-Skunkworks/go-jira/commit/8b863d2)]
* add `vote` command [Cory Bennett] [[a08c92f](https://github.com/Netflix-Skunkworks/go-jira/commit/a08c92f)]
* add `issuelinktypes` command [Cory Bennett] [[37f81a4](https://github.com/Netflix-Skunkworks/go-jira/commit/37f81a4)]
* add `issuelink` command [Cory Bennett] [[aacc9f4](https://github.com/Netflix-Skunkworks/go-jira/commit/aacc9f4)]
* fix closing duplicate issue on `dup` command [Cory Bennett] [[fc696c3](https://github.com/Netflix-Skunkworks/go-jira/commit/fc696c3)]
* rewrite checkpoint [Cory Bennett] [[36632a5](https://github.com/Netflix-Skunkworks/go-jira/commit/36632a5)]
## 0.1.14 - 2017-05-10
* fix unsafe casting for --quiet flag [Cory Bennett] [[6f29f43](https://github.com/Netflix-Skunkworks/go-jira/commit/6f29f43)]
* [[#80](https://github.com/Netflix-Skunkworks/go-jira/issues/80)] add `jira unassign` and `jira give ISSUE --default` commands [Cory Bennett] [[03d8633](https://github.com/Netflix-Skunkworks/go-jira/commit/03d8633)]
## 0.1.13 - 2017-04-24
* work around `github.com/tmc/keyring` compile error for windows [Cory Bennett] [[85298e9](https://github.com/Netflix-Skunkworks/go-jira/commit/85298e9)]
* Added generic issuelink command [David Reuss] [[cc54d11](https://github.com/Netflix-Skunkworks/go-jira/commit/cc54d11)]
* Added --start parameter for pagination on results [David Reuss] [[9b94d9e](https://github.com/Netflix-Skunkworks/go-jira/commit/9b94d9e)]
## 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)] [[2dff6c9](https://github.com/Netflix-Skunkworks/go-jira/commit/2dff6c9)]
## 0.1.8 - 2016-11-24
* [[#12](https://github.com/Netflix-Skunkworks/go-jira/issues/12)] integrate with keyring for password storage and provide http basic auth credentials for every request since most jira services have websudo enabled with does not allow cookie based authentication [Cory Bennett] [[b8a6e57](https://github.com/Netflix-Skunkworks/go-jira/commit/b8a6e57)]
* Cleaning up usage [Jay Shirley] [[8add52b](https://github.com/Netflix-Skunkworks/go-jira/commit/8add52b)]
* Update usage [Jay Shirley] [[b56e32a](https://github.com/Netflix-Skunkworks/go-jira/commit/b56e32a)]
* use gopkg.in for links to maintain version compatibility [Cory Bennett] [[1414d1f](https://github.com/Netflix-Skunkworks/go-jira/commit/1414d1f)]
* golint [Cory Bennett] [[44cdebf](https://github.com/Netflix-Skunkworks/go-jira/commit/44cdebf)]
* add "rank" command allow ordering backlog issues in agile projects [Cory Bennett] [[e4cc9c6](https://github.com/Netflix-Skunkworks/go-jira/commit/e4cc9c6)]
* Adding a unixproxy mechanism [Jay Shirley] [[5b9c0dd](https://github.com/Netflix-Skunkworks/go-jira/commit/5b9c0dd)]
## 0.1.7 - 2016-08-24
* Prefer transition names which match exactly [Don Brower] [[e40f9c1](https://github.com/Netflix-Skunkworks/go-jira/commit/e40f9c1)]
+63 -50
View File
@@ -1,5 +1,13 @@
PLATFORMS= \
freebsd/amd64 \
linux/386 \
linux/amd64 \
windows/386 \
windows/amd64 \
darwin/amd64 \
$(NULL)
NAME=jira
GO?=go
OS=$(shell uname -s)
ifeq ($(filter CYGWIN%,$(OS)),$(OS))
@@ -13,45 +21,75 @@ else
BIN ?= $(GOBIN)$(SEP)$(NAME)
endif
GOPATH ?= $(CWD)
export GOPATH
DIST=$(CWD)$(SEP)dist
GOBIN ?= $(CWD)
CURVER ?= $(patsubst v%,%,$(shell [ -d .git ] && git describe --abbrev=0 --tags || grep ^\#\# CHANGELOG.md | awk '{print $$2; exit}'))
LDFLAGS:= -w
LDFLAGS := -w
build:
$(GO) build -gcflags="-e" -v -ldflags "$(LDFLAGS) -s" -o '$(BIN)' cmd/jira/main.go
PACKAGE=gopkg.in/Netflix-Skunkworks/go-jira.v1
# use 'make debug' and you can get a debuggable golang binary
# see https://golang.org/doc/gdb
# note on mac's you will need to codesign the gdb binary before you can use it:
# codesign -fs gdb-cert /usr/local/bin/gdb
ifneq ($(DEBUG),)
GOBUILD=go build -ldflags "-s" -gcflags "-N -l"
else
GOBUILD=go build -ldflags "$(LDFLAGS) -s"
endif
build: $(GOPATH)/src/$(PACKAGE)
cd $(GOPATH)/src/$(PACKAGE) && $(GOBUILD) -o $(BIN)
debug:
$(MAKE) DEBUG=1 build
$(GOPATH)/src/%:
mkdir -p $(@D)
test -L $@ || ln -sf ../../.. $@
glide install -v
vet:
@$(GO) vet .
@$(GO) vet ./jiracli
@$(GO) vet ./jiracmd
@$(GO) vet ./jiradata
@$(GO) vet ./cmd/jira
@go vet *.go lib/*.go data/*.go
lint:
@$(GO) get github.com/golang/lint/golint
@golint .
@golint ./jiracli
@golint ./jiracmd
@golint ./jiradata
@golint ./cmd/jira
@go get github.com/golang/lint/golint
@$(GOPATH)/bin/golint .
@$(GOPATH)/bin/golint ./data
@$(GOPATH)/bin/golint ./lib
all:
$(GO) get -u github.com/karalabe/xgo
test: $(GOPATH)/src/$(PACKAGE)
cd $(GOPATH)/src/$(SUBPACKAGE) && go test -v
cross-setup:
for p in $(PLATFORMS); do \
echo Building for $$p"; \
cd $(GOROOT)/src && sudo GOROOT_BOOTSTRAP=$(GOROOT) GOOS=$${p/-*/} GOARCH=$${p/*-/} bash ./make.bash --no-clean; \
done
all: $(GOPATH)/src/$(PACKAGE)
docker pull karalabe/xgo-latest
rm -rf dist
mkdir -p dist
xgo --targets="freebsd/amd64,linux/386,linux/amd64,windows/386,windows/amd64,darwin/amd64" -dest ./dist -ldflags="-w -s" ./cmd/jira
docker run --rm -e EXT_GOPATH=/gopath -v $(GOPATH):/gopath -e TARGETS="$(PLATFORMS)" -v $$(pwd)/dist:/build karalabe/xgo-latest $(PACKAGE)
cd $(DIST) && for x in go-jira-*; do mv $$x $$(echo $$x | cut -c 4-); done
fmt:
gofmt -s -w main.go lib/*.go data/*.go
install:
${MAKE} GOBIN=$$HOME/bin build
${MAKE} GOBIN=$(shell echo ~)/bin build
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 ^v$(CURVER) HEAD *.go jiracli/*.go jiradata/*.go jiracmd/*.go cmd/*/*.go *.lock | grep -vE 'gofmt|go fmt|version bump'
@git log --pretty=format:"* %s [%cn] [%h]" --no-merges ^v$(CURVER) HEAD *.go lib/*.go data/*.go | grep -vE 'gofmt|go fmt'
update-changelog:
@echo "# Changelog" > CHANGELOG.md.new; \
@@ -62,39 +100,14 @@ update-changelog:
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 -n +2 CHANGELOG.md >> CHANGELOG.md.new; \
perl -pi -e 's{VERSION = "$(CURVER)"}{VERSION = "$(NEWVER)"}' jira.go; \
mv CHANGELOG.md.new CHANGELOG.md; \
$(NULL)
update-usage:
@perl -pi -e 'undef $$/; s|\n```\nusage.*?```|"\n```\n".qx{./jira --help}."```"|esg' README.md
release:
make update-usage
git diff --exit-code --quiet README.md || git commit -m "Updated Usage" README.md
git commit -m "Updated Changelog" CHANGELOG.md
git commit -m "version bump" jira.go
git commit -m "Updated Changelog" CHANGELOG.md; \
perl -pi -e 's{VERSION = "$(CURVER)"}{VERSION = "$(NEWVER)"}' lib/cli.go; \
git commit -m "version bump" lib/cli.go; \
git tag v$(NEWVER)
git push --tags
version:
@echo $(CURVER)
clean: clean-password-store
rm -rf ./$(NAME)
clean-password-store:
rm -f "$(CWD)/_t/.password-store/GoJira/api-token:gojira@corybennett.org.gpg"
rm -f "$(CWD)/_t/.password-store/GoJira/api-token:mothra@corybennett.org.gpg"
test-password-store:
ln -s "$(CWD)/_t/.password-store/GoJira/api-token__gojira@corybennett.org.gpg" "$(CWD)/_t/.password-store/GoJira/api-token:gojira@corybennett.org.gpg"
ln -s "$(CWD)/_t/.password-store/GoJira/api-token__mothra@corybennett.org.gpg" "$(CWD)/_t/.password-store/GoJira/api-token:mothra@corybennett.org.gpg"
prove: test-password-store
chmod -R g-rwx,o-rwx $(CWD)/_t/.gnupg
OSHT_VERBOSE=1 prove -v _t/*.t
generate:
cd schemas && ./fetch-schemas.py
grep -h slipscheme jiradata/*.go | grep json | sort | uniq | awk -F\/\/ '{print $$2}' | while read cmd; do $$cmd; done
clean:
rm -rf pkg dist bin src ./$(NAME)
+146 -261
View File
@@ -1,49 +1,102 @@
[![Build Status](https://travis-ci.org/go-jira/jira/go-jira.svg?branch=master)](https://travis-ci.org/go-jira/jira)
[![GoDoc](https://godoc.org/github.com/go-jira/jira?status.svg)](https://godoc.org/github.com/go-jira/jira)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![Join the chat at https://gitter.im/go-jira-cli/help](https://badges.gitter.im/go-jira-cli/help.svg)](https://gitter.im/go-jira-cli/help?utm_source=badge&utm_medium=badge&utm_content=badge)
[![Build Status](https://travis-ci.org/Netflix-Skunkworks/go-jira.svg?branch=master)](https://travis-ci.org/Netflix-Skunkworks/go-jira)
[![GoDoc](https://godoc.org/gopkg.in/Netflix-Skunkworks/go-jira.v0?status.png)](https://godoc.org/gopkg.in/Netflix-Skunkworks/go-jira.v0)
# go-jira
simple command line client for Atlassian's Jira service written in Go
Simple command line client for Atlassian's Jira service written in Go.
## Synopsis
## Install
```bash
jira ls -p GOJIRA # list all unresolved issues for project GOJRIA
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
jira ls -t table -p GOJIRA # list all unresolved issues in pretty table output
### Download
jira view GOJIRA-321 # print Issue using "view" template
jira GOJIRA-321 # same as above
You can download one of the pre-built binaries for **go-jira** [here](https://github.com/go-jira/jira/releases).
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
### Build
# edit the issue, using the overirdes on the command line, skip the interactive editor:
jira edit GOJIRA-321 --noedit \
-o assignee=mothra \
-o comment="mothra, please take care of this." \
-o priority=Major
You can build and install the official repository with [Go](https://golang.org/dl/):
jira create -p GOJIRA # create new "Bug" type issue for project GOJIRA
jira create -p GOJIRA -i Task # create new Task type issue
go get github.com/go-jira/jira/cmd/jira
jira trans close GOJIRA-321 # close issue, with interactive editor to set fields
jira close GOJIRA-321 --edit # same as above
This will checkout this repository into `$GOPATH/src/github.com/go-jira/jira/`, build, and install it.
# close the issue, set the resolution, and skip interactive editor:
jira trans close GOJIRA-321 -o resolution="Won't Fix" --noedit
# same as above
jira close GOJIRA-321 -o resolution="Won't Fix"
It should then be available in $GOPATH/bin/jira
jira repopen GOJIRA-321 -m "reopening" # reopen issue
## Usage
jira watch GOJIRA-321 # add self as watcher to the issue
jira comment GOJIRA-321 -m "done yet?" # add comment to the issue
#### Setting up TAB completion
jira take GOJIRA-321 # assign issue to self
Since go-jira is built with the "kingpin" golang command line library we support bash/zsh shell completion automatically:
jira give GOJIRA-321 mothra # assign issue to user mothra
* <https://github.com/alecthomas/kingpin/tree/v2.2.5#bashzsh-shell-completion>
# create local project config to set defaults
mkdir .jira.d
echo "project: GOJIRA" > .jira.d/config.yml
For example, in bash, adding something along the lines of:
jira ls # list all unresolved issues for project GOJRIA
jira ls -a mothra # as above also assigned to user mothra
jira ls -w mothra # lists GOJIRA unresolved issues watched by user mothra
jira ls -r mothra # list GOJIRA unresolved issues reported by user mothra
jira ls -t table # list all unresolved issues in pretty table output
`eval "$(jira --completion-script-bash)"`
jira create # create new "Bug" type issue for project GOJIRA
jira create -i Task # create new Task type issue
to your bashrc, or .profile (assuming go-jira binary is already in your path) will cause jira to offer tab completion behavior.
# make the table template your default "list" template:
jira export-templates -t table
mv $HOME/.jira.d/templates/table $HOME/.jira.d/templates/list
```
## Download
You can download one of the pre-built binaries for **go-jira** [here](https://github.com/Netflix-Skunkworks/go-jira/releases).
## Build
* **NOTE** You will need **`go-1.4.1`** minimum
* To build the `jira` binary the current directory just run:
```bash
make
```
* To install the binary to you ~/bin directory you can run:
```bash
make install
```
## Configuration
**go-jira** uses a configuration hierarchy. When loading the configuration from disk it will recursively look through all parent directories in your current path looking for a **.jira.d** directory. If your current directory is not a child directory of your homedir, then your homedir will also be inspected for a **.jira.d** directory. From all of **.jira.d** directories discovered **go-jira** will load a **&lt;command&gt;.yml** file (ie for `jira list` it will load `.jira.d/list.yml`) then it will merge in any properties from the **config.yml** if found. The configuration properties found in a file closest to your current working directory will have precedence. Properties overridden with command line options will have final precedence.
**go-jira** uses a configuration hierarchy. When loading the configuration from disk it will recursively look through
all parent directories in your current path looking for a **.jira.d** directory. If your current directory is not
a child directory of your homedir, then your homedir will also be inspected for a **.jira.d** directory. From all of **.jira.d** directories
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 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:
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
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:
```yaml
```
project: foo
```
@@ -57,7 +110,7 @@ endpoint: https://jira.mycompany.com
EOM
```
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
Then use `jira login` to authenticate yourself.
### Dynamic Configuration
@@ -69,7 +122,7 @@ If the **.jira.d/config.yml** file is executable, then **go-jira** will attempt
echo "endpoint: https://jira.mycompany.com"
echo "editor: emacs -nw"
case $JIRA_OPERATION in
case $JIRA_OPERATION in
list)
echo "template: table";;
esac
@@ -89,159 +142,10 @@ case $JIRA_OPERATION in
esac
```
### Custom Commands
You can now create custom commands for `jira` just by editing your `.jira.d/config.yml` config file. These commands are effectively shell-scripts that can have documented options and arguments. The basic format is like:
```yaml
custom-commands:
- command1
- command2
```
##### Commands
Where the individual commands are maps with these keys:
* `name: string` [**required**] This is the command name, so for `jira foobar` you would have `name: foobar`
* `help: string` This is help message displayed in the usage for the command
* `hidden: bool` This command will be hidden from users, but still executable. Sometimes useful for constructing complex commands where one custom command might call another.
* `default: bool` Use this for compound command groups. If you wanted to have `jira foo bar` and `jira foo baz` you would have two commands with `name: foo bar` and `name: foo baz`. Then if you wanted `jira foo baz` to be called by default when you type `jira foo` you would set `default: true` for that custom command.
* `options: list` This is the list of possible option flags that the command will accept
* `args: list` This is the list of command arguments (like the ISSUE) that the command will accept.
* `aliases: string list`: This is a list of alternate names that the user can provide on the command line to run the same command. Typically used to shorten the command name or provide alternatives that users might expect.
* `script: string` [**required**] This is the script that will be executed as the action for this command. The value will be treated as a template and substitutions for options and arguments will be made before executing.
##### Options
These are possible keys under the command `options` property:
* `name: string` [**required**] Name of the option, so `name: foobar` will result in `--foobar` option.
* `help: string` The help message displayed in usage for the option.
* `type: string`: The type of the option, can be one of these values: `BOOL`, `COUNTER`, `ENUM`, `FLOAT32`, `FLOAT64`, `INT8`, `INT16`, `INT32`, `INT64`, `INT`, `STRING`, `STRINGMAP`, `UINT8`, `UINT16`, `UINT32`, `UINT64` and `UINT`. Most of these are primitive data types an should be self-explanatory. The default type is `STRING`. There are some special types:
* `COUNTER` will be an integer type that increments each time the option is used. So something like `--count --count` will results in `{{options.count}}` of `2`.
* `ENUM` type is used with the `enum` property. The raw type is a string and **must** be one of the values listed in the `enum` property.
* `STRINGMAP` is a `string => string` map with the format of `KEY=VALUE`. So `--override foo=bar --override bin=baz` will allow for `{{options.override.foo}}` to be `bar` and `{{options.override.bin}}` to be `baz`.
* `short: char` The single character option to be used so `short: c` will allow for `-c`.
* `required: bool` Indicate that this option must be provided on the command line. Conflicts with the `default` property.
* `default: any` Specify the default value for the option. Conflicts with the `required` property.
* `hidden: bool` Hide the option from the usage help message, but otherwise works fine. Sometimes useful for developer options that user should not play with.
* `repeat: bool` Indicate that this option can be repeated. Not applicable for `COUNTER` and `STRINGMAP` types. This will turn the option value into an array that you can iterate over. So `--day Monday --day Thursday` can be used like `{{range options.day}}Day: {{.}}{{end}}`
* `enum: string list` Used with the `type: ENUM` property, it is a list of strings values that represent the set of possible values the option accepts.
##### Arguments
These are possible keys under the command `args` property:
* `name: string` [**required**] Name of the option, so `name: ISSUE` will show in the usage as `jira <command> ISSUE`. This also represents the name of the argument to be used in the script template, so `{{args.ISSUE}}`.
* `help: string` The help message displayed in usage for the argument.
* `type: string`: The type of the argument, can be one of these values: `BOOL`, `COUNTER`, `ENUM`, `FLOAT32`, `FLOAT64`, `INT8`, `INT16`, `INT32`, `INT64`, `INT`, `STRING`, `STRINGMAP`, `UINT8`, `UINT16`, `UINT32`, `UINT64` and `UINT`. Most of these are primitive data types an should be self-explanatory. The default type is `STRING`. There are some special types:
* `COUNTER` will be an integer type that increments each the argument is provided So something like `jira <command> ISSUE-12 ISSUE-23` will results in `{{args.ISSUE}}` of `2`.
* `ENUM` type is used with the `enum` property. The raw type is a string and **must** be one of the values listed in the `enum` property.
* `STRINGMAP` is a `string => string` map with the format of `KEY=VALUE`. So `jira <command> foo=bar bin=baz` along with a `name: OVERRIDE` property will allow for `{{args.OVERRIDE.foo}}` to be `bar` and `{{args.OVERRIDE.bin}}` to be `baz`.
* `required: bool` Indicate that this argument must be provided on the command line. Conflicts with the `default` property.
* `default: any` Specify the default value for the argument. Conflicts with the `required` property.
* `repeat: bool` Indicate that this argument can be repeated. Not applicable for `COUNTER` and `STRINGMAP` types. This will turn the template value into an array that you can iterate over. So `jira <command> ISSUE-12 ISSUE-23` can be used like `{{range args.ISSUE}}Issue: {{.}}{{end}}`
* `enum: string list` Used with the `type: ENUM` property, it is a list of strings values that represent the set of possible values for the argument.
##### Script Template
The `script` property is a template that would produce `/bin/sh` compatible syntax after the template has been processed. There are 2 key template functions `{{args}}` and `{{options}}` that return the parsed arguments and option flags as a map.
To demonstrate how you might use args and options here is a `custom-test` command:
```yaml
custom-commands:
- name: custom-test
help: Testing the custom commands
options:
- name: abc
short: a
default: default
- name: day
type: ENUM
enum:
- Monday
- Tuesday
- Wednesday
- Thursday
- Friday
required: true
args:
- name: ARG
required: true
- name: MORE
repeat: true
script: |
echo COMMAND {{args.ARG}} --abc {{options.abc}} --day {{options.day}} {{range $more := args.MORE}}{{$more}} {{end}}
```
Then to run it:
```
$ jira custom-test
ERROR Invalid Usage: required flag --day not provided
$ jira custom-test --day Sunday
ERROR Invalid Usage: enum value must be one of Monday,Tuesday,Wednesday,Thursday,Friday, got 'Sunday'
$ jira custom-test --day Tuesday
ERROR Invalid Usage: required argument 'ARG' not provided
$ jira custom-test --day Tuesday arg1
COMMAND arg1 --abc default --day Tuesday
$ jira custom-test --day Tuesday arg1 more1 more2 more3
COMMAND arg1 --abc default --day Tuesday more1 more2 more3
$ jira custom-test --day Tuesday arg1 more1 more2 more3 --abc non-default
COMMAND arg1 --abc non-default --day Tuesday more1 more2 more3
$ jira custom-test --day Tuesday arg1 more1 more2 more3 -a short-non-default
COMMAND arg1 --abc short-non-default --day Tuesday more1 more2 more3
```
The script has access to all the environment variables that are in your current environment plus those that `jira` will set. `jira` sets environment variables for each config property it has parsed from `.jira.d/config.yml` or the command configs at `.jira.d/<command>.yml`. It might be useful to see all environment variables that `jira` is producing, so here is a simple custom command to list them:
```yaml
custom-commands:
- name: env
help: print the JIRA environment variables available to custom commands
script: |
env | grep JIRA
```
You could use the environment variables automatically, so if your `.jira.d/config.yml` looks something like this:
```yaml
project: PROJECT
custom-commands:
- name: print-project
help: print the name of the configured project
script: "echo $JIRA_PROJECT"
```
##### Examples
* `jira mine` for listing issues assigned to you
```yaml
custom-commands:
- name: mine
help: display issues assigned to me
script: |-
if [ -n "$JIRA_PROJECT" ]; then
# if `project: ...` configured just list the issues for current project
{{jira}} list --template table --query "resolution = unresolved and assignee=currentuser() and project = $JIRA_PROJECT ORDER BY priority asc, created"
else
# otherwise list issues for all project
{{jira}} list --template table --query "resolution = unresolved and assignee=currentuser() ORDER BY priority asc, created"
fi
```
* `jira sprint` for listing issues in your current sprint
```yaml
custom-commands:
- name: sprint
help: display issues for active sprint
script: |-
if [ -n "$JIRA_PROJECT" ]; then
# if `project: ...` configured just list the issues for current project
{{jira}} list --template table --query "sprint in openSprints() and type != epic and resolution = unresolved and project=$JIRA_PROJECT ORDER BY rank asc, created"
else
# otherwise list issues for all project
echo "\"project: ...\" configuration missing from .jira.d/config.yml"
fi
```
### Editing
When you run command like `jira edit` it will open up your favorite editor with the templatized output so you can quickly edit. When the editor
closes **go-jira** will submit the completed form. The order which **go-jira** attempts to determine your preferred editor is:
closes **go-jira** will submit the completed form. The order which **go-jira** attempts to determine your prefered editor is:
* **editor** property in any config.yml file
* **JIRA_EDITOR** environment variable
@@ -260,9 +164,9 @@ hard-coded templates with `jira export-templates` which will write them to **~/.
#### Writing/Editing Templates
First the basic templating functionality is defined by the Go language 'text/template' library. The library reference documentation can be found [here](https://golang.org/pkg/text/template/), and there is a good primer document [here](https://gohugo.io/templates/go-templates/). `go-jira` also provides a few extra helper functions to make it a bit easier to format the data, those functions are defined [here](https://github.com/go-jira/jira/blob/master/jiracli/templates.go#L64).
First the basic templating functionality is defined by the Go language 'text/template' library. The library reference documentation can be found [here](https://golang.org/pkg/text/template/), and there is a good primer document [here](https://gohugo.io/templates/go-templates/). `go-jira` also provides a few extra helper functions to make it a bit easlier to format the data, those functions are defined [here](https://github.com/Netflix-Skunkworks/go-jira/blob/master/util.go#L133).
Knowing what data and fields are available to any given template is not obvious. The easiest approach to determine what is available is to use the `debug` template on any given operation. For example to find out what is available to the "view" templates, you can use:
Knowing what data and fields are available to any given template is not obvious. The easiest approach to determine what is available is to use the `debug` template on any given operation. For eample to find out what is available to the "view" templates, you can use:
```
jira view GOJIRA-321 -t debug
```
@@ -272,94 +176,75 @@ This will print out the data in JSON format that is available to the template.
jira list -t debug
```
### Authentication
For Atlassian Cloud hosted Jira [API Tokens are now required](https://developer.atlassian.com/cloud/jira/platform/deprecation-notice-basic-auth-and-cookie-based-auth/). You will automatically be prompted for an API Token if your jira endpoint ends in `.atlassian.net`. If you are using a private Jira service, you can force `jira` to use an api-token by setting the `authentication-method: api-token` property in your `$HOME/.jira.d/config.yml` file. The API Token needs to be presented to the Jira service on every request, so it is recommended to store this API Token security within your OS's keyring, or using the `pass` service as documented below so that it can be programmatically accessed via `jira` and not prompt you every time. For a less-secure option you can also provide the API token via a `JIRA_API_TOKEN` environment variable. If you are unable to use an api-token for an Atlassian Cloud hosted Jira then you can still force `jira` to use the old session based authentication (until it the hosted system stops accepting it) by setting `authentication-method: session`.
The API Token authentication requires both the token and the email of the user. The email mut be set in the `user:` in your config.yml. Failure to provide the `user` will result in a 401 error.
If your Jira service still allows you to use the Session based authentication method then `jira` will prompt for a password automatically when get a response header from the Jira service that indicates you do not have an active session (ie the `X-Ausername` header is set to `anonymous`). Then after authentication we cache the `cloud.session.token` cookie returned by the service [session login api](https://docs.atlassian.com/jira/REST/cloud/#auth/1/session-login) and reuse that on subsequent requests. Typically this cookie will be valid for several hours (depending on the service configuration). To automatically securely store your password for easy reuse by jira You can enable a `password-source` via `.jira.d/config.yml` with possible values of `keyring` or `pass`.
#### User vs Login
The Jira service has sometimes differing opinions about how a user is identified. In other words the ID you login with might not be ID that the jira system recognized you as. This matters when trying to identify a user via various Jira REST APIs (like issue assignment). This is especially relevant when trying to authenticate with an API Token where the authentication user is usually an email address, but within the Jira system the user is identified by a user name. To accommodate this `jira` now supports two different properties in the config file. So when authentication using the API Tokens you will likely want something like this in your `$HOME/.jira.d/config.yml` file:
```yaml
user: person
login: person@example.com
Figuring out what is available to input templates (like for the `create` operation) is a bit more tricky, but similar. To find the data available for a `create` template you can run:
```
You can also override these values on the command line with `jira --user person --login person@example.com`. The `login` value will be used only for authentication purposes, the `user` value will be used when a user name is required for any Jira service API calls.
#### `keyring` password source
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
jira create --dryrun -t debug --editor /bin/cat
```
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 automatically login.
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.
#### `pass` password source
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
password-name: jira.example.com/myuser
```
This assumes you have already setup `pass` correctly on your system. Specifically you will need to have created a gpg key like this:
## Usage
```
$ 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>"
```
Now insert your password with the name you configured.
```
$ pass insert jira.example.com/myuser
```
You probably want to setup gpg-agent so that you don't 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
if [ ! -f $HOME/.gpg-agent.conf ]; then
cat <<EOM >$HOME/.gpg-agent.conf
default-cache-ttl 604800
max-cache-ttl 604800
default-cache-ttl-ssh 604800
max-cache-ttl-ssh 604800
EOM
fi
if [ -n "${GPG_AGENT_INFO}" ]; then
nc -U "${GPG_AGENT_INFO%%:*}" >/dev/null </dev/null
if [ ! -S "${GPG_AGENT_INFO%%:*}" -o $? != 0 ]; then
# set passphrase cache so I only have to type my passphrase once a day
eval $(gpg-agent --options $HOME/.gpg-agent.conf --daemon --write-env-file $HOME/.gpg-agent-info --use-standard-socket --log-file $HOME/tmp/gpg-agent.log --verbose)
fi
fi
export GPG_TTY=$(tty)
```
#### `stdin` password source
When `password-source` is set to `stdin`, the `jira login` command will read from stdin until EOF, and the bytes read will be the used as the password. This is useful if you have some other programmatic method for fetching passwords. For example, if `password-generator` creates a one-time password and prints it to stdout, you could use it like this.
```bash
$ ./password-generator | jira login --endpoint=https://my.jira.endpoint.com --user=USERNAME
Usage:
jira (ls|list) <Query Options>
jira view ISSUE
jira edit [--noedit] <Edit Options> [ISSUE | <Query Options>]
jira create [--noedit] [-p PROJECT] <Create Options>
jira DUPLICATE dups ISSUE
jira BLOCKER blocks ISSUE
jira watch ISSUE [-w WATCHER]
jira (trans|transition) TRANSITION ISSUE [--noedit] <Edit Options>
jira ack ISSUE [--edit] <Edit Options>
jira close ISSUE [--edit] <Edit Options>
jira resolve ISSUE [--edit] <Edit Options>
jira reopen ISSUE [--edit] <Edit Options>
jira start ISSUE [--edit] <Edit Options>
jira stop ISSUE [--edit] <Edit Options>
jira comment ISSUE [--noedit] <Edit Options>
jira take ISSUE
jira (assign|give) ISSUE ASSIGNEE
jira fields
jira issuelinktypes
jira transmeta ISSUE
jira editmeta ISSUE
jira issuetypes [-p PROJECT]
jira createmeta [-p PROJECT] [-i ISSUETYPE]
jira transitions ISSUE
jira export-templates [-d DIR] [-t template]
jira (b|browse) ISSUE
jira login
jira ISSUE
General Options:
-b --browse Open your browser to the Jira issue
-e --endpoint=URI URI to use for jira
-h --help Show this usage
-t --template=FILE Template file to use for output/editing
-u --user=USER Username to use for authentication (default: $USER)
-v --verbose Increase output logging
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,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
-q --query=JQL Jira Query Language expression for the search
-r --reporter=USER Reporter to search for
-s --sort=ORDER For list operations, sort issues (default: priority asc, created)
-w --watcher=USER Watcher to add to issue (default: $USER)
or Watcher to search for
Edit Options:
-m --comment=COMMENT Comment message for transition
-o --override=KEY=VAL Set custom key/value pairs
Create Options:
-i --issuetype=ISSUETYPE Jira Issue Type (default: Bug)
-m --comment=COMMENT Comment message for transition
-o --override=KEY=VAL Set custom key/value pairs
Command Options:
-d --directory=DIR Directory to export templates to (default: $HOME/.jira.d/templates)
```
-236
View File
@@ -1,236 +0,0 @@
# 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.
-49
View File
@@ -1,49 +0,0 @@
config:
stop: true
password-source: pass
endpoint: https://go-jira.atlassian.net
user: gojira
login: gojira@corybennett.org
project: BASIC
queries:
todo: >-
resolution = unresolved {{if .project}}AND project = '{{.project}}'{{end}} AND status = 'To Do'
custom-commands:
- name: env
help: print the JIRA environment variables available to custom commands
script: |-
env | sort | grep JIRA
- name: print-project
help: print the name of the configured project
script: "echo $JIRA_PROJECT"
- name: jira-path
help: print the path the jira command that is running this alias
script: |-
echo {{jira}}
- name: mine
help: display issues assigned to me
script: |-
if [ -n "$JIRA_PROJECT" ]; then
# if `project: ...` configured just list the issues for current project
{{jira}} list --template table --query "resolution = unresolved and assignee=currentuser() and project = $JIRA_PROJECT ORDER BY priority asc, created"
else
# otherwise list issues for all project
{{jira}} list --template table --query "resolution = unresolved and assignee=currentuser() ORDER BY priority asc, created"
fi
- name: argtest
help: testing passing args
script: |-
echo {{args.ARG}}
args:
- name: ARG
help: string to echo for testing
- name: opttest
help: testing passing option flags
script: |-
echo {{options.OPT}}
options:
- name: OPT
help: string to echo for testing
-1
View File
@@ -1 +0,0 @@
template: list
-1
View File
@@ -1 +0,0 @@
Go Jira <gojira@example.com>
-1
View File
@@ -1 +0,0 @@
 (ΡαΆω GΈώ20,ΧΎ„ι―’«$Ggu©y1_a-ΟI'ΥΈοΘ}Ν£4
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
 (Ñá¢ù G¸ü ©oCº™*šâj0OÊ! =ldÿ§ô~2%p7•>´·kbñ#d›‹'¥d|_à{±ºa¶ŠÔο=mÂm½ð°Lí&‹À',^‰ý$¡¨!HÞ>]Ð4WïôêÔi+q=†N 2¿1´K;_% ~Ø ™¶£Õ‡ØÄ~\¶'–
Binary file not shown.
-80
View File
@@ -1,80 +0,0 @@
#!/bin/bash
eval "$(curl -q -s https://raw.githubusercontent.com/coryb/osht/master/osht.sh)"
cd $(dirname $0)
jira="../jira"
. env.sh
PLAN 16
# reset login
RUNS $jira logout
RUNS $jira login
# cleanup from previous failed test executions
($jira ls --project BASIC | awk -F: '{print $1}' | while read issue; do ../jira done $issue; done) | sed 's/^/# CLEANUP: /g'
###############################################################################
## Create an issue
###############################################################################
RUNS $jira create --project BASIC -o summary=summary -o description=description --noedit --saveFile issue.props
issue=$(awk '/issue/{print $2}' issue.props)
DIFF <<EOF
OK $issue $ENDPOINT/browse/$issue
EOF
###############################################################################
## Testing the example custom commands, print-project
###############################################################################
RUNS $jira print-project
DIFF <<EOF
BASIC
EOF
###############################################################################
## Testing the example custom commands, jira-path
###############################################################################
RUNS $jira jira-path
DIFF <<EOF
../jira
EOF
###############################################################################
## Testing the example custom commands, env
###############################################################################
RUNS $jira env
GREP ^JIRA_PROJECT=BASIC
###############################################################################
## Testing the example custom commands, argtest
###############################################################################
RUNS $jira argtest TEST
DIFF <<EOF
TEST
EOF
###############################################################################
## Testing the example custom commands, opttest
###############################################################################
RUNS $jira opttest --OPT TEST
DIFF <<EOF
TEST
EOF
###############################################################################
## Use the "mine" alias to list issues assigned to self
###############################################################################
RUNS $jira mine
DIFF <<EOF
+----------------+------------------------------------------+--------------+--------------+--------------+------------+--------------+--------------+
| Issue | Summary | Type | Priority | Status | Age | Reporter | Assignee |
+----------------+------------------------------------------+--------------+--------------+--------------+------------+--------------+--------------+
| $(printf %-14s $issue) | summary | Bug | Medium | To Do | a minute | gojira | gojira |
+----------------+------------------------------------------+--------------+--------------+--------------+------------+--------------+--------------+
EOF
-121
View File
@@ -1,121 +0,0 @@
#!/bin/bash
eval "$(curl -q -s https://raw.githubusercontent.com/coryb/osht/master/osht.sh)"
cd $(dirname $0)
jira="../jira"
. env.sh
PLAN 22
# reset login
RUNS $jira logout
RUNS $jira login
# cleanup from previous failed test executions
($jira ls --project BASIC | awk -F: '{print $1}' | while read issue; do ../jira done $issue; done) | sed 's/^/# CLEANUP: /g'
###############################################################################
## Create an epic
###############################################################################
RUNS $jira epic create --project BASIC -o summary="Totally Epic" -o description=description --epic-name "Basic Epic" --noedit --saveFile issue.props
epic=$(awk '/issue/{print $2}' issue.props)
DIFF <<EOF
OK $epic $ENDPOINT/browse/$epic
EOF
###############################################################################
## Create issues we can assign to epic
###############################################################################
RUNS $jira create --project BASIC -o summary="summary" -o description=description --noedit --saveFile issue.props
issue1=$(awk '/issue/{print $2}' issue.props)
DIFF <<EOF
OK $issue1 $ENDPOINT/browse/$issue1
EOF
RUNS $jira create --project BASIC -o summary="summary" -o description=description --noedit --saveFile issue.props
issue2=$(awk '/issue/{print $2}' issue.props)
DIFF <<EOF
OK $issue2 $ENDPOINT/browse/$issue2
EOF
###############################################################################
## List the issues for the epic
###############################################################################
RUNS $jira epic list $epic
DIFF<<EOF
+----------------+------------------------------------------+--------------+--------------+--------------+------------+--------------+--------------+
| Issue | Summary | Type | Priority | Status | Age | Reporter | Assignee |
+----------------+------------------------------------------+--------------+--------------+--------------+------------+--------------+--------------+
+----------------+------------------------------------------+--------------+--------------+--------------+------------+--------------+--------------+
EOF
###############################################################################
## Add issues to an epic
###############################################################################
RUNS $jira epic add $epic $issue1 $issue2
DIFF<<EOF
OK $epic $ENDPOINT/browse/$epic
OK $issue1 $ENDPOINT/browse/$issue1
OK $issue2 $ENDPOINT/browse/$issue2
EOF
###############################################################################
## List the issues for the epic
###############################################################################
RUNS $jira epic list $epic
DIFF<<EOF
+----------------+------------------------------------------+--------------+--------------+--------------+------------+--------------+--------------+
| Issue | Summary | Type | Priority | Status | Age | Reporter | Assignee |
+----------------+------------------------------------------+--------------+--------------+--------------+------------+--------------+--------------+
| $(printf %-14s $issue1) | summary | Bug | Medium | To Do | a minute | gojira | gojira |
| $(printf %-14s $issue2) | summary | Bug | Medium | To Do | a minute | gojira | gojira |
+----------------+------------------------------------------+--------------+--------------+--------------+------------+--------------+--------------+
EOF
###############################################################################
## Remove an issue from an Epic
###############################################################################
RUNS $jira epic remove $issue1
DIFF<<EOF
OK $issue1 $ENDPOINT/browse/$issue1
EOF
###############################################################################
## List the issues for the epic
###############################################################################
RUNS $jira epic list $epic
DIFF<<EOF
+----------------+------------------------------------------+--------------+--------------+--------------+------------+--------------+--------------+
| Issue | Summary | Type | Priority | Status | Age | Reporter | Assignee |
+----------------+------------------------------------------+--------------+--------------+--------------+------------+--------------+--------------+
| $(printf %-14s $issue2) | summary | Bug | Medium | To Do | a minute | gojira | gojira |
+----------------+------------------------------------------+--------------+--------------+--------------+------------+--------------+--------------+
EOF
###############################################################################
## Remove last issue from an Epic
###############################################################################
RUNS $jira epic remove $issue2
DIFF<<EOF
OK $issue2 $ENDPOINT/browse/$issue2
EOF
###############################################################################
## List the issues for the epic
###############################################################################
RUNS $jira epic list $epic
DIFF<<EOF
+----------------+------------------------------------------+--------------+--------------+--------------+------------+--------------+--------------+
| Issue | Summary | Type | Priority | Status | Age | Reporter | Assignee |
+----------------+------------------------------------------+--------------+--------------+--------------+------------+--------------+--------------+
+----------------+------------------------------------------+--------------+--------------+--------------+------------+--------------+--------------+
EOF
-189
View File
@@ -1,189 +0,0 @@
#!/bin/bash
eval "$(curl -q -s https://raw.githubusercontent.com/coryb/osht/master/osht.sh)"
cd $(dirname $0)
jira="../jira"
. env.sh
PLAN 43
# reset login
RUNS $jira logout
RUNS $jira login
# cleanup from previous failed test executions
($jira ls --project BASIC | awk -F: '{print $1}' | while read issue; do ../jira done $issue; done) | sed 's/^/# CLEANUP: /g'
###############################################################################
## Create an issue
###############################################################################
RUNS $jira create --project BASIC -o summary="Attach To Me" -o description=description --noedit --saveFile issue.props
issue=$(awk '/issue/{print $2}' issue.props)
DIFF <<EOF
OK $issue $ENDPOINT/browse/$issue
EOF
###############################################################################
## Attach via stdin
###############################################################################
RUNS $jira attach create $issue --filename README.md --saveFile attach.props < ./README.md
attach1=$(awk '/^id:/{print $2}' attach.props)
DIFF <<EOF
OK $attach1 $ENDPOINT/secure/attachment/$attach1/README.md
EOF
###############################################################################
## Attach binary file
###############################################################################
RUNS dd of=garbage.bin if=/dev/urandom count=1k bs=1k
RUNS $jira attach create $issue garbage.bin --saveFile attach.props
attach2=$(awk '/^id:/{print $2}' attach.props)
DIFF <<EOF
OK $attach2 $ENDPOINT/secure/attachment/$attach2/garbage.bin
EOF
###############################################################################
## Attach binary file with different name
###############################################################################
RUNS $jira attach create $issue garbage.bin --filename foobar.bin --saveFile attach.props
attach3=$(awk '/^id:/{print $2}' attach.props)
DIFF <<EOF
OK $attach3 $ENDPOINT/secure/attachment/$attach3/foobar.bin
EOF
###############################################################################
## List attachments
###############################################################################
RUNS $jira attach list $issue
DIFF <<EOF
+------------+------------------------------+------------+--------------+--------------+
| id | filename | bytes | user | created |
+------------+------------------------------+------------+--------------+--------------+
| $(printf %10s $attach1) | README.md | 1239 | gojira | a minute |
| $(printf %10s $attach2) | garbage.bin | 1048576 | gojira | a minute |
| $(printf %10s $attach3) | foobar.bin | 1048576 | gojira | a minute |
+------------+------------------------------+------------+--------------+--------------+
EOF
###############################################################################
## Fetch text attachment
###############################################################################
RUNS $jira attach get $attach1 -o attach1.txt
DIFF <<EOF
OK Wrote attach1.txt
EOF
# verify no diffs
RUNS diff -q README.md attach1.txt
###############################################################################
## Fetch text attachment to stdout
###############################################################################
RUNS sh -c "$jira attach get $attach1 -o- > attach1.txt"
# verify no diffs
RUNS diff -q README.md attach1.txt
###############################################################################
## Fetch text attachment as same name
###############################################################################
RUNS $jira attach get $attach1
DIFF <<EOF
OK Wrote README.md
EOF
# verify no diffs
RUNS git diff README.md
###############################################################################
## Fetch binary attachment
###############################################################################
RUNS $jira attach get $attach2 --output binary.out
DIFF <<EOF
OK Wrote binary.out
EOF
# verify no diffs
RUNS diff -q garbage.bin binary.out
###############################################################################
## Fetch binary attachment to stdout
###############################################################################
RUNS sh -c "$jira attach get $attach2 -o- > binary.out"
# verify no diffs
RUNS diff -q garbage.bin binary.out
###############################################################################
## Fetch binary attachment
###############################################################################
RUNS $jira attach get $attach3
DIFF <<EOF
OK Wrote foobar.bin
EOF
# verify no diffs
RUNS diff -q garbage.bin foobar.bin
###############################################################################
## Fetch binary attachment to stdout
###############################################################################
RUNS sh -c "$jira attach get $attach3 --output=- > binary.out"
# verify no diffs
RUNS diff -q garbage.bin binary.out
###############################################################################
## Delete attachment
###############################################################################
RUNS $jira attach remove $attach1
DIFF <<EOF
OK Deleted Attachment $attach1
EOF
RUNS $jira attach list $issue
DIFF <<EOF
+------------+------------------------------+------------+--------------+--------------+
| id | filename | bytes | user | created |
+------------+------------------------------+------------+--------------+--------------+
| $(printf %10s $attach2) | garbage.bin | 1048576 | gojira | a minute |
| $(printf %10s $attach3) | foobar.bin | 1048576 | gojira | a minute |
+------------+------------------------------+------------+--------------+--------------+
EOF
###############################################################################
## Delete attachment
###############################################################################
RUNS $jira attach rm $attach2
DIFF <<EOF
OK Deleted Attachment $attach2
EOF
RUNS $jira attach list $issue
DIFF <<EOF
+------------+------------------------------+------------+--------------+--------------+
| id | filename | bytes | user | created |
+------------+------------------------------+------------+--------------+--------------+
| $(printf %10s $attach3) | foobar.bin | 1048576 | gojira | a minute |
+------------+------------------------------+------------+--------------+--------------+
EOF
###############################################################################
## Delete last
###############################################################################
RUNS $jira attach rm $attach3
DIFF <<EOF
OK Deleted Attachment $attach3
EOF
RUNS $jira attach list $issue
DIFF <<EOF
+------------+------------------------------+------------+--------------+--------------+
| id | filename | bytes | user | created |
+------------+------------------------------+------------+--------------+--------------+
+------------+------------------------------+------------+--------------+--------------+
EOF
-7
View File
@@ -1,7 +0,0 @@
export COLUMNS=149
export JIRA_LOG_FORMAT="%{level:-5s} %{message}"
export ENDPOINT="https://go-jira.atlassian.net"
export GNUPGHOME=$(pwd)/.gnupg
export PASSWORD_STORE_DIR=$(pwd)/.password-store
export JIRACLOUD=1
-46
View File
@@ -1,46 +0,0 @@
package jira
import (
"encoding/json"
"github.com/go-jira/jira/jiradata"
)
// https://docs.atlassian.com/jira/REST/cloud/#api/2/attachment-getAttachment
func (j *Jira) GetAttachment(id string) (*jiradata.Attachment, error) {
return GetAttachment(j.UA, j.Endpoint, id)
}
func GetAttachment(ua HttpClient, endpoint string, id string) (*jiradata.Attachment, error) {
uri := URLJoin(endpoint, "rest/api/2/attachment", id)
resp, err := ua.GetJSON(uri)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
results := &jiradata.Attachment{}
return results, json.NewDecoder(resp.Body).Decode(results)
}
return nil, responseError(resp)
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/attachment-removeAttachment
func (j *Jira) RemoveAttachment(id string) error {
return RemoveAttachment(j.UA, j.Endpoint, id)
}
func RemoveAttachment(ua HttpClient, endpoint string, id string) error {
uri := URLJoin(endpoint, "rest/api/2/attachment", id)
resp, err := ua.Delete(uri)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == 204 {
return nil
}
return responseError(resp)
}
+9
View File
@@ -0,0 +1,9 @@
package jiracli
import (
"gopkg.in/Netflix-Skunkworks/go-jira.v1/lib"
)
type JiraOptions struct {
jira.SearchOptions `yaml:",inline"`
}
+31
View File
@@ -0,0 +1,31 @@
package jiracli
import (
"os"
"gopkg.in/op/go-logging.v1"
)
var (
log = logging.MustGetLogger("jiracli")
defaultFormat = "%{color}%{time:2006-01-02T15:04:05.000Z07:00} %{level:-5s} [%{shortfile}]%{color:reset} %{message}"
)
func InitLogging() {
logBackend := logging.NewLogBackend(os.Stderr, "", 0)
format := os.Getenv("JIRA_LOG_FORMAT")
if format == "" {
format = defaultFormat
}
logging.SetBackend(
logging.NewBackendFormatter(
logBackend,
logging.MustStringFormatter(format),
),
)
logging.SetLevel(logging.NOTICE, "")
}
func VerboseLogging() {
logging.SetLevel(logging.GetLevel("")+1, "")
}
+52
View File
@@ -0,0 +1,52 @@
package jiracli
import (
"os"
"gopkg.in/Netflix-Skunkworks/go-jira.v1/lib"
"gopkg.in/alecthomas/kingpin.v2"
)
func usage() {
app := kingpin.New("jira", "Jira Command Line Client")
app.Writer(os.Stdout)
app.Version(jira.VERSION)
opts := &JiraOptions{}
// -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
listUsage(app, opts)
}
func listUsage(app *kingpin.Application, opts *JiraOptions) {
list := app.Command("list", "List Jira Issues")
list.Flag("assignee", "Username assigned the issue").Short('a').StringVar(&opts.Assignee)
list.Flag("component", "Component to use for query").Short('c').StringVar(&opts.Component)
list.Flag("queryfields", "Fields that are used for \"list\" template").Short('f').StringVar(&opts.QueryFields)
list.Flag("limit", "Maximum number of results to return in query").Short('l').IntVar(&opts.MaxResults)
list.Flag("project", "Project to use for query").Short('p').StringVar(&opts.Project)
list.Flag("query", "Jira Query Language expression for the search").Short('q').StringVar(&opts.Query)
list.Flag("reporter", "Reporter to use in query").Short('r').StringVar(&opts.Reporter)
list.Flag("sort", "Sort order used in query").Short('s').StringVar(&opts.Sort)
list.Flag("watcher", "Watcher to use in query").Short('w').StringVar(&opts.Watcher)
// -a --assignee=USER Username assigned the issue
// -c --component=COMPONENT Component to Search for
// -f --queryfields=FIELDS Fields that are used in "list" template: (default: %s)
// -i --issuetype=ISSUETYPE The Issue Type
// -l --limit=VAL Maximum number of results to return in query (default: %d)
// -p --project=PROJECT Project to Search for
// -q --query=JQL Jira Query Language expression for the search
// -r --reporter=USER Reporter to search for
// -s --sort=ORDER For list operations, sort issues (default: %s)
// -w --watcher=USER Watcher to add to issue (default: %s)
// or Watcher to search for
}
-48
View File
@@ -1,48 +0,0 @@
package main
import (
"os"
"path/filepath"
"github.com/coryb/figtree"
"github.com/coryb/oreo"
"github.com/go-jira/jira/jiracli"
"github.com/go-jira/jira/jiracmd"
"gopkg.in/op/go-logging.v1"
)
type oreoLogger struct {
logger *logging.Logger
}
var log = logging.MustGetLogger("jira")
func (ol *oreoLogger) Printf(format string, args ...interface{}) {
ol.logger.Debugf(format, args...)
}
func main() {
defer jiracli.HandleExit()
jiracli.InitLogging()
configDir := ".jira.d"
fig := figtree.NewFigTree(
figtree.WithHome(jiracli.Homedir()),
figtree.WithEnvPrefix("JIRA"),
figtree.WithConfigDir(configDir),
)
if err := os.MkdirAll(filepath.Join(jiracli.Homedir(), configDir), 0755); err != nil {
log.Errorf("%s", err)
panic(jiracli.Exit{Code: 1})
}
o := oreo.New().WithCookieFile(filepath.Join(jiracli.Homedir(), configDir, "cookies.js")).WithLogger(&oreoLogger{log})
jiracmd.RegisterAllCommands()
app := jiracli.CommandLine(fig, o)
jiracli.ParseCommandLine(app, os.Args[1:])
}
-37
View File
@@ -1,37 +0,0 @@
package jira
import (
"bytes"
"encoding/json"
"github.com/go-jira/jira/jiradata"
)
type ComponentProvider interface {
ProvideComponent() *jiradata.Component
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/component-createComponent
func (j *Jira) CreateComponent(cp ComponentProvider) (*jiradata.Component, error) {
return CreateComponent(j.UA, j.Endpoint, cp)
}
func CreateComponent(ua HttpClient, endpoint string, cp ComponentProvider) (*jiradata.Component, error) {
req := cp.ProvideComponent()
encoded, err := json.Marshal(req)
if err != nil {
return nil, err
}
uri := URLJoin(endpoint, "rest/api/2/component")
resp, err := ua.Post(uri, "application/json", bytes.NewBuffer(encoded))
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 201 {
results := &jiradata.Component{}
return results, json.NewDecoder(resp.Body).Decode(results)
}
return nil, responseError(resp)
}
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/TransitionsMeta.json
// slipscheme -pkg jiradata -overwrite ../schemas/TransitionsMeta.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -19,10 +19,6 @@ package jiradata
// "title": "User",
// "type": "object",
// "properties": {
// "accountId": {
// "title": "accountId",
// "type": "string"
// },
// "active": {
// "title": "active",
// "type": "boolean"
@@ -196,10 +192,6 @@ package jiradata
// "title": "field",
// "type": "string"
// },
// "fieldId": {
// "title": "fieldId",
// "type": "string"
// },
// "fieldtype": {
// "title": "fieldtype",
// "type": "string"
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -19,10 +19,6 @@ package jiradata
// "title": "field",
// "type": "string"
// },
// "fieldId": {
// "title": "fieldId",
// "type": "string"
// },
// "fieldtype": {
// "title": "fieldtype",
// "type": "string"
@@ -47,8 +43,7 @@ package jiradata
// }
type ChangeItem struct {
Field string `json:"field,omitempty" yaml:"field,omitempty"`
FieldID string `json:"fieldId,omitempty" yaml:"fieldId,omitempty"`
FieldType string `json:"fieldtype,omitempty" yaml:"fieldtype,omitempty"`
Fieldtype string `json:"fieldtype,omitempty" yaml:"fieldtype,omitempty"`
From string `json:"from,omitempty" yaml:"from,omitempty"`
FromString string `json:"fromString,omitempty" yaml:"fromString,omitempty"`
To string `json:"to,omitempty" yaml:"to,omitempty"`
+1 -9
View File
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -26,10 +26,6 @@ package jiradata
// "title": "User",
// "type": "object",
// "properties": {
// "accountId": {
// "title": "accountId",
// "type": "string"
// },
// "active": {
// "title": "active",
// "type": "boolean"
@@ -203,10 +199,6 @@ package jiradata
// "title": "field",
// "type": "string"
// },
// "fieldId": {
// "title": "fieldId",
// "type": "string"
// },
// "fieldtype": {
// "title": "fieldtype",
// "type": "string"
+1 -2
View File
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -30,7 +30,6 @@ package jiradata
// "autoCompleteUrl": {
// "type": "string"
// },
// "defaultValue": {},
// "hasDefaultValue": {
// "type": "boolean"
// },
+5 -39
View File
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/TransitionsMeta.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -16,76 +16,42 @@ package jiradata
// "type": "object",
// "properties": {
// "allowedValues": {
// "title": "allowedValues",
// "type": "array",
// "items": {}
// },
// "autoCompleteUrl": {
// "title": "autoCompleteUrl",
// "type": "string"
// },
// "defaultValue": {
// "title": "defaultValue"
// },
// "hasDefaultValue": {
// "title": "hasDefaultValue",
// "type": "boolean"
// },
// "key": {
// "title": "key",
// "type": "string"
// },
// "name": {
// "title": "name",
// "type": "string"
// },
// "operations": {
// "title": "operations",
// "type": "array",
// "items": {
// "type": "string"
// }
// },
// "required": {
// "title": "required",
// "type": "boolean"
// },
// "schema": {
// "title": "Json Type",
// "type": "object",
// "properties": {
// "custom": {
// "title": "custom",
// "type": "string"
// },
// "customId": {
// "title": "customId",
// "type": "integer"
// },
// "items": {
// "title": "items",
// "type": "string"
// },
// "system": {
// "title": "system",
// "type": "string"
// },
// "type": {
// "title": "type",
// "type": "string"
// }
// }
// "$ref": "#/definitions/json-type"
// }
// }
// }
type FieldMeta struct {
AllowedValues AllowedValues `json:"allowedValues,omitempty" yaml:"allowedValues,omitempty"`
AllowedValues []interface{} `json:"allowedValues,omitempty" yaml:"allowedValues,omitempty"`
AutoCompleteURL string `json:"autoCompleteUrl,omitempty" yaml:"autoCompleteUrl,omitempty"`
DefaultValue interface{} `json:"defaultValue,omitempty" yaml:"defaultValue,omitempty"`
HasDefaultValue bool `json:"hasDefaultValue,omitempty" yaml:"hasDefaultValue,omitempty"`
Key string `json:"key,omitempty" yaml:"key,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Operations Operations `json:"operations,omitempty" yaml:"operations,omitempty"`
Operations []string `json:"operations,omitempty" yaml:"operations,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
Schema *JSONType `json:"schema,omitempty" yaml:"schema,omitempty"`
Schema interface{} `json:"schema,omitempty" yaml:"schema,omitempty"`
}
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/TransitionsMeta.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -20,65 +20,32 @@ package jiradata
// "type": "object",
// "properties": {
// "allowedValues": {
// "title": "allowedValues",
// "type": "array",
// "items": {}
// },
// "autoCompleteUrl": {
// "title": "autoCompleteUrl",
// "type": "string"
// },
// "defaultValue": {
// "title": "defaultValue"
// },
// "hasDefaultValue": {
// "title": "hasDefaultValue",
// "type": "boolean"
// },
// "key": {
// "title": "key",
// "type": "string"
// },
// "name": {
// "title": "name",
// "type": "string"
// },
// "operations": {
// "title": "operations",
// "type": "array",
// "items": {
// "type": "string"
// }
// },
// "required": {
// "title": "required",
// "type": "boolean"
// },
// "schema": {
// "title": "Json Type",
// "type": "object",
// "properties": {
// "custom": {
// "title": "custom",
// "type": "string"
// },
// "customId": {
// "title": "customId",
// "type": "integer"
// },
// "items": {
// "title": "items",
// "type": "string"
// },
// "system": {
// "title": "system",
// "type": "string"
// },
// "type": {
// "title": "type",
// "type": "string"
// }
// }
// "$ref": "#/definitions/json-type"
// }
// }
// }
+1 -1
View File
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchRequest.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchRequest.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
+1 -9
View File
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -22,10 +22,6 @@ package jiradata
// "title": "User",
// "type": "object",
// "properties": {
// "accountId": {
// "title": "accountId",
// "type": "string"
// },
// "active": {
// "title": "active",
// "type": "boolean"
@@ -199,10 +195,6 @@ package jiradata
// "title": "field",
// "type": "string"
// },
// "fieldId": {
// "title": "fieldId",
// "type": "string"
// },
// "fieldtype": {
// "title": "fieldtype",
// "type": "string"
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -15,4 +15,4 @@ package jiradata
// "title": "Included Fields",
// "type": "object"
// }
type IncludedFields map[string]interface{}
type IncludedFields map[string]string
+1 -11
View File
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -30,10 +30,6 @@ package jiradata
// "title": "User",
// "type": "object",
// "properties": {
// "accountId": {
// "title": "accountId",
// "type": "string"
// },
// "active": {
// "title": "active",
// "type": "boolean"
@@ -207,10 +203,6 @@ package jiradata
// "title": "field",
// "type": "string"
// },
// "fieldId": {
// "title": "fieldId",
// "type": "string"
// },
// "fieldtype": {
// "title": "fieldtype",
// "type": "string"
@@ -270,7 +262,6 @@ package jiradata
// "autoCompleteUrl": {
// "type": "string"
// },
// "defaultValue": {},
// "hasDefaultValue": {
// "type": "boolean"
// },
@@ -449,7 +440,6 @@ package jiradata
// "autoCompleteUrl": {
// "type": "string"
// },
// "defaultValue": {},
// "hasDefaultValue": {
// "type": "boolean"
// },
+1 -11
View File
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -33,10 +33,6 @@ package jiradata
// "title": "User",
// "type": "object",
// "properties": {
// "accountId": {
// "title": "accountId",
// "type": "string"
// },
// "active": {
// "title": "active",
// "type": "boolean"
@@ -210,10 +206,6 @@ package jiradata
// "title": "field",
// "type": "string"
// },
// "fieldId": {
// "title": "fieldId",
// "type": "string"
// },
// "fieldtype": {
// "title": "fieldtype",
// "type": "string"
@@ -273,7 +265,6 @@ package jiradata
// "autoCompleteUrl": {
// "type": "string"
// },
// "defaultValue": {},
// "hasDefaultValue": {
// "type": "boolean"
// },
@@ -452,7 +443,6 @@ package jiradata
// "autoCompleteUrl": {
// "type": "string"
// },
// "defaultValue": {},
// "hasDefaultValue": {
// "type": "boolean"
// },
+1 -5
View File
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -22,10 +22,6 @@ package jiradata
// "title": "field",
// "type": "string"
// },
// "fieldId": {
// "title": "fieldId",
// "type": "string"
// },
// "fieldtype": {
// "title": "fieldtype",
// "type": "string"
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
+1 -6
View File
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/TransitionsMeta.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -16,23 +16,18 @@ package jiradata
// "type": "object",
// "properties": {
// "custom": {
// "title": "custom",
// "type": "string"
// },
// "customId": {
// "title": "customId",
// "type": "integer"
// },
// "items": {
// "title": "items",
// "type": "string"
// },
// "system": {
// "title": "system",
// "type": "string"
// },
// "type": {
// "title": "type",
// "type": "string"
// }
// }
+1 -1
View File
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/TransitionsMeta.json
// slipscheme -pkg jiradata -overwrite ../schemas/TransitionsMeta.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
+1 -1
View File
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
+13 -13
View File
@@ -5,27 +5,27 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/WorklogWithPagination.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
// Properties defined from schema:
// {
// "title": "properties",
// "type": "array",
// "items": {
// "title": "Entity Property",
// "type": "object",
// "title": "Properties",
// "type": "object",
// "properties": {
// "properties": {
// "key": {
// "title": "key",
// "type": "string"
// },
// "value": {
// "title": "value"
// "title": "properties",
// "type": "object",
// "patternProperties": {
// ".+": {
// "type": "string"
// }
// }
// }
// }
// }
type Properties []*EntityProperty
type Properties struct {
Properties map[string]string `json:"properties,omitempty" yaml:"properties,omitempty"`
}
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchRequest.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchRequest.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -35,29 +35,21 @@ package jiradata
// "title": "maxResults",
// "type": "integer"
// },
// "properties": {
// "title": "properties",
// "type": "array",
// "items": {
// "type": "string"
// }
// },
// "startAt": {
// "title": "startAt",
// "type": "integer"
// },
// "validateQuery": {
// "title": "validateQuery",
// "type": "string"
// "type": "boolean"
// }
// }
// }
type SearchRequest struct {
Fields Fields `json:"fields,omitempty" yaml:"fields,omitempty"`
FieldsByKeys bool `json:"fieldsByKeys,omitempty" yaml:"fieldsByKeys,omitempty"`
JQL string `json:"jql,omitempty" yaml:"jql,omitempty"`
MaxResults int `json:"maxResults,omitempty" yaml:"maxResults,omitempty"`
Properties Properties `json:"properties,omitempty" yaml:"properties,omitempty"`
StartAt int `json:"startAt,omitempty" yaml:"startAt,omitempty"`
ValidateQuery string `json:"validateQuery,omitempty" yaml:"validateQuery,omitempty"`
Fields Fields `json:"fields,omitempty" yaml:"fields,omitempty"`
FieldsByKeys bool `json:"fieldsByKeys,omitempty" yaml:"fieldsByKeys,omitempty"`
JQL string `json:"jql,omitempty" yaml:"jql,omitempty"`
MaxResults int `json:"maxResults,omitempty" yaml:"maxResults,omitempty"`
StartAt int `json:"startAt,omitempty" yaml:"startAt,omitempty"`
ValidateQuery bool `json:"validateQuery,omitempty" yaml:"validateQuery,omitempty"`
}
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -27,7 +27,6 @@ package jiradata
// "autoCompleteUrl": {
// "type": "string"
// },
// "defaultValue": {},
// "hasDefaultValue": {
// "type": "boolean"
// },
@@ -181,10 +180,6 @@ package jiradata
// "title": "User",
// "type": "object",
// "properties": {
// "accountId": {
// "title": "accountId",
// "type": "string"
// },
// "active": {
// "title": "active",
// "type": "boolean"
@@ -358,10 +353,6 @@ package jiradata
// "title": "field",
// "type": "string"
// },
// "fieldId": {
// "title": "fieldId",
// "type": "string"
// },
// "fieldtype": {
// "title": "fieldtype",
// "type": "string"
@@ -421,7 +412,6 @@ package jiradata
// "autoCompleteUrl": {
// "type": "string"
// },
// "defaultValue": {},
// "hasDefaultValue": {
// "type": "boolean"
// },
@@ -600,7 +590,6 @@ package jiradata
// "autoCompleteUrl": {
// "type": "string"
// },
// "defaultValue": {},
// "hasDefaultValue": {
// "type": "boolean"
// },
+1 -1
View File
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/TransitionsMeta.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/TransitionsMeta.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
+2 -35
View File
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/TransitionsMeta.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -28,65 +28,32 @@ package jiradata
// "type": "object",
// "properties": {
// "allowedValues": {
// "title": "allowedValues",
// "type": "array",
// "items": {}
// },
// "autoCompleteUrl": {
// "title": "autoCompleteUrl",
// "type": "string"
// },
// "defaultValue": {
// "title": "defaultValue"
// },
// "hasDefaultValue": {
// "title": "hasDefaultValue",
// "type": "boolean"
// },
// "key": {
// "title": "key",
// "type": "string"
// },
// "name": {
// "title": "name",
// "type": "string"
// },
// "operations": {
// "title": "operations",
// "type": "array",
// "items": {
// "type": "string"
// }
// },
// "required": {
// "title": "required",
// "type": "boolean"
// },
// "schema": {
// "title": "Json Type",
// "type": "object",
// "properties": {
// "custom": {
// "title": "custom",
// "type": "string"
// },
// "customId": {
// "title": "customId",
// "type": "integer"
// },
// "items": {
// "title": "items",
// "type": "string"
// },
// "system": {
// "title": "system",
// "type": "string"
// },
// "type": {
// "title": "type",
// "type": "string"
// }
// }
// "$ref": "#/definitions/json-type"
// }
// }
// }
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/TransitionsMeta.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -31,65 +31,32 @@ package jiradata
// "type": "object",
// "properties": {
// "allowedValues": {
// "title": "allowedValues",
// "type": "array",
// "items": {}
// },
// "autoCompleteUrl": {
// "title": "autoCompleteUrl",
// "type": "string"
// },
// "defaultValue": {
// "title": "defaultValue"
// },
// "hasDefaultValue": {
// "title": "hasDefaultValue",
// "type": "boolean"
// },
// "key": {
// "title": "key",
// "type": "string"
// },
// "name": {
// "title": "name",
// "type": "string"
// },
// "operations": {
// "title": "operations",
// "type": "array",
// "items": {
// "type": "string"
// }
// },
// "required": {
// "title": "required",
// "type": "boolean"
// },
// "schema": {
// "title": "Json Type",
// "type": "object",
// "properties": {
// "custom": {
// "title": "custom",
// "type": "string"
// },
// "customId": {
// "title": "customId",
// "type": "integer"
// },
// "items": {
// "title": "items",
// "type": "string"
// },
// "system": {
// "title": "system",
// "type": "string"
// },
// "type": {
// "title": "type",
// "type": "string"
// }
// }
// "$ref": "#/definitions/json-type"
// }
// }
// }
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/TransitionsMeta.json
// slipscheme -pkg jiradata -overwrite ../schemas/TransitionsMeta.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
@@ -48,9 +48,6 @@ package jiradata
// "title": "autoCompleteUrl",
// "type": "string"
// },
// "defaultValue": {
// "title": "defaultValue"
// },
// "hasDefaultValue": {
// "title": "hasDefaultValue",
// "type": "boolean"
+66
View File
@@ -0,0 +1,66 @@
package jiradata
/////////////////////////////////////////////////////////////////////////
// This Code is Generated by SlipScheme Project:
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
// User defined from schema:
// {
// "title": "User",
// "type": "object",
// "properties": {
// "active": {
// "title": "active",
// "type": "boolean"
// },
// "avatarUrls": {
// "title": "avatarUrls",
// "type": "object",
// "patternProperties": {
// ".+": {
// "type": "string"
// }
// }
// },
// "displayName": {
// "title": "displayName",
// "type": "string"
// },
// "emailAddress": {
// "title": "emailAddress",
// "type": "string"
// },
// "key": {
// "title": "key",
// "type": "string"
// },
// "name": {
// "title": "name",
// "type": "string"
// },
// "self": {
// "title": "self",
// "type": "string"
// },
// "timeZone": {
// "title": "timeZone",
// "type": "string"
// }
// }
// }
type User struct {
Active bool `json:"active,omitempty" yaml:"active,omitempty"`
AvatarUrls map[string]string `json:"avatarUrls,omitempty" yaml:"avatarUrls,omitempty"`
DisplayName string `json:"displayName,omitempty" yaml:"displayName,omitempty"`
EmailAddress string `json:"emailAddress,omitempty" yaml:"emailAddress,omitempty"`
Key string `json:"key,omitempty" yaml:"key,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Self string `json:"self,omitempty" yaml:"self,omitempty"`
TimeZone string `json:"timeZone,omitempty" yaml:"timeZone,omitempty"`
}
@@ -5,7 +5,7 @@ package jiradata
// https://github.com/coryb/slipscheme
//
// Generated with command:
// slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json
// slipscheme -pkg jiradata -overwrite ../schemas/SearchResults.json
/////////////////////////////////////////////////////////////////////////
// DO NOT EDIT //
/////////////////////////////////////////////////////////////////////////
-113
View File
@@ -1,113 +0,0 @@
package jira
import (
"bytes"
"encoding/json"
"fmt"
"net/url"
"strings"
"github.com/coryb/oreo"
"github.com/go-jira/jira/jiradata"
)
// https://docs.atlassian.com/jira-software/REST/latest/#agile/1.0/epic-getIssuesForEpic
func (j *Jira) EpicSearch(epic string, sp SearchProvider) (*jiradata.SearchResults, error) {
return EpicSearch(j.UA, j.Endpoint, epic, sp)
}
func EpicSearch(ua HttpClient, endpoint string, epic string, sp SearchProvider) (*jiradata.SearchResults, error) {
req := sp.ProvideSearchRequest()
// encoded, err := json.Marshal(req)
// if err != nil {
// return nil, err
// }
uri, err := url.Parse(URLJoin(endpoint, "rest/agile/1.0/epic", epic, "issue"))
if err != nil {
return nil, err
}
params := url.Values{}
if len(req.Fields) > 0 {
params.Add("fields", strings.Join(req.Fields, ","))
}
if req.JQL != "" {
params.Add("jql", req.JQL)
}
if req.MaxResults != 0 {
params.Add("maxResults", fmt.Sprintf("%d", req.MaxResults))
}
if req.StartAt != 0 {
params.Add("startAt", fmt.Sprintf("%d", req.StartAt))
}
if req.ValidateQuery != "" {
params.Add("validateQuery", req.ValidateQuery)
}
uri.RawQuery = params.Encode()
resp, err := ua.Do(oreo.RequestBuilder(uri).WithHeader("Accept", "application/json").Build())
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
results := &jiradata.SearchResults{}
return results, json.NewDecoder(resp.Body).Decode(results)
}
return nil, responseError(resp)
}
type EpicIssuesProvider interface {
ProvideEpicIssues() *jiradata.EpicIssues
}
// https://docs.atlassian.com/jira-software/REST/latest/#agile/1.0/epic-moveIssuesToEpic
func (j *Jira) EpicAddIssues(epic string, eip EpicIssuesProvider) error {
return EpicAddIssues(j.UA, j.Endpoint, epic, eip)
}
func EpicAddIssues(ua HttpClient, endpoint string, epic string, eip EpicIssuesProvider) error {
req := eip.ProvideEpicIssues()
encoded, err := json.Marshal(req)
if err != nil {
return err
}
uri := URLJoin(endpoint, "rest/agile/1.0/epic", epic, "issue")
resp, err := ua.Post(uri, "application/json", bytes.NewBuffer(encoded))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == 204 {
return nil
}
return responseError(resp)
}
// https://docs.atlassian.com/jira-software/REST/latest/#agile/1.0/epic-removeIssuesFromEpic
func (j *Jira) EpicRemoveIssues(eip EpicIssuesProvider) error {
return EpicRemoveIssues(j.UA, j.Endpoint, eip)
}
func EpicRemoveIssues(ua HttpClient, endpoint string, eip EpicIssuesProvider) error {
req := eip.ProvideEpicIssues()
encoded, err := json.Marshal(req)
if err != nil {
return err
}
uri := URLJoin(endpoint, "rest/agile/1.0/epic/none/issue")
resp, err := ua.Post(uri, "application/json", bytes.NewBuffer(encoded))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == 204 {
return nil
}
return responseError(resp)
}
-21
View File
@@ -1,21 +0,0 @@
package jira
import (
"encoding/json"
"net/http"
"github.com/go-jira/jira/jiradata"
)
func responseError(resp *http.Response) error {
results := &jiradata.ErrorCollection{}
if err := json.NewDecoder(resp.Body).Decode(results); err != nil {
results.Status = resp.StatusCode
results.ErrorMessages = append(results.ErrorMessages, err.Error())
}
if len(results.ErrorMessages) == 0 && len(results.Errors) == 0 {
results.Status = resp.StatusCode
results.ErrorMessages = append(results.ErrorMessages, resp.Status)
}
return results
}
-26
View File
@@ -1,26 +0,0 @@
package jira
import (
"encoding/json"
"github.com/go-jira/jira/jiradata"
)
// https://docs.atlassian.com/jira/REST/cloud/#api/2/field-getFields
func (j *Jira) GetFields() ([]jiradata.Field, error) {
return GetFields(j.UA, j.Endpoint)
}
func GetFields(ua HttpClient, endpoint string) ([]jiradata.Field, error) {
uri := URLJoin(endpoint, "rest/api/2/field")
resp, err := ua.GetJSON(uri)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
results := []jiradata.Field{}
return results, json.NewDecoder(resp.Body).Decode(&results)
}
return nil, responseError(resp)
}
Generated
+71
View File
@@ -0,0 +1,71 @@
hash: c912ef7d849008c13494df9deb6ae3f55b7b7472704ea15253bea32d2f8a219a
updated: 2016-09-25T12:49:13.264637268-07:00
imports:
- name: github.com/alecthomas/template
version: a0175ee3bccc567396460bf5acd36800cb10c49c
subpackages:
- parse
- name: github.com/alecthomas/units
version: 2efee857e7cfd4f3d0138cc3cbb1b4966962b93a
- name: github.com/coryb/optigo
version: 1b53172e5b4a00b528ea9fde88a4b76b596e293e
- name: github.com/howeyc/gopass
version: 3ca23474a7c7203e0a0a070fd33508f6efdb9b3d
- name: github.com/kballard/go-shellquote
version: d8ec1a69a250a17bb0e419c386eac1f3711dc142
- name: github.com/mattn/go-colorable
version: ed8eb9e318d7a84ce5915b495b7d35e0cfe7b5a8
- name: github.com/mattn/go-isatty
version: 3a115632dcd687f9c8cd01679c83a06a0e21c1f3
- name: github.com/mgutz/ansi
version: c286dcecd19ff979eeb73ea444e479b903f2cfcb
- name: github.com/op/go-logging
version: 970db520ece77730c7e4724c61121037378659d9
- name: golang.org/x/crypto
version: b13fc1fd382d01861b16b2e6474487d3d4d27f20
subpackages:
- acme
- blowfish
- cast5
- curve25519
- ed25519
- ed25519/internal/edwards25519
- hkdf
- nacl/secretbox
- openpgp
- openpgp/armor
- openpgp/elgamal
- openpgp/errors
- openpgp/packet
- openpgp/s2k
- pbkdf2
- pkcs12/internal/rc2
- poly1305
- ripemd160
- salsa20/salsa
- ssh
- ssh/agent
- ssh/terminal
- ssh/testdata
- name: golang.org/x/net
version: 6250b412798208e6c90b03b7c4f226de5aa299e2
subpackages:
- context
- context/ctxhttp
- name: golang.org/x/sys
version: a646d33e2ee3172a661fc09bca23bb4889a41bc8
subpackages:
- unix
- name: golang.org/x/tools
version: c2ef61f450233d3c629ade8eb2083e43fa75c97a
subpackages:
- cmd/gorename
- name: gopkg.in/alecthomas/kingpin.v2
version: e9044be3ab2a8e11d4e1f418d12f0790d57e8d70
- name: gopkg.in/coryb/yaml.v2
version: bd17d4d1209d8af092496a57db259d2d472676eb
- name: gopkg.in/op/go-logging.v1
version: b2cb9fa56473e98db8caba80237377e83fe44db5
testImports:
- name: gopkg.in/check.v1
version: 4f90aeace3a26ad7021961c297b22c42160c7b25
+15
View File
@@ -0,0 +1,15 @@
package: .
import:
- package: github.com/coryb/optigo
version: ^0.0.4
- package: github.com/howeyc/gopass
- package: github.com/kballard/go-shellquote
- package: github.com/mgutz/ansi
- package: gopkg.in/coryb/yaml.v2
- package: gopkg.in/op/go-logging.v1
version: ^1.0.0
- package: gopkg.in/alecthomas/kingpin.v2
version: ^2.2.3
- package: golang.org/x/tools
subpackages:
- cmd/gorename
-41
View File
@@ -1,41 +0,0 @@
module github.com/go-jira/jira
go 1.12
require (
github.com/Netflix/go-expect v0.0.0-20180928190340-9d1f4485533b // indirect
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect
github.com/cheekybits/genny v1.0.0 // indirect
github.com/coryb/figtree v0.0.0-20180728224503-071d1ef303df
github.com/coryb/kingpeon v0.0.0-20180107011214-9a669f143f2e
github.com/coryb/oreo v0.0.0-20180804211640-3e1b88fc08f1
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/fatih/camelcase v1.0.0 // indirect
github.com/guelfey/go.dbus v0.0.0-20131113121618-f6a3a2366cc3 // indirect
github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c // indirect
github.com/jinzhu/copier v0.0.0-20180308034124-7e38e58719c3
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/kr/pretty v0.1.0 // indirect
github.com/kr/pty v1.1.4 // indirect
github.com/mattn/go-colorable v0.0.9 // indirect
github.com/mattn/go-isatty v0.0.3 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
github.com/pkg/browser v0.0.0-20170505125900-c90ca0c84f15
github.com/pkg/errors v0.8.0
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.2.2
github.com/theckman/go-flock v0.4.0 // indirect
github.com/tidwall/gjson v0.0.0-20180711011033-ba784d767ac7
github.com/tidwall/match v1.0.0 // indirect
github.com/tmc/keyring v0.0.0-20171121202319-839169085ae1
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb
golang.org/x/net v0.0.0-20171102191033-01c190206fbd
golang.org/x/sys v0.0.0-20180727230415-bd9dbc187b6e // indirect
gopkg.in/AlecAivazis/survey.v1 v1.6.1
gopkg.in/alecthomas/kingpin.v2 v2.2.6
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/coryb/yaml.v2 v2.0.0-20180616071044-0e40e46f7153
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
gopkg.in/yaml.v2 v2.2.2 // indirect
)
-74
View File
@@ -1,74 +0,0 @@
github.com/Netflix/go-expect v0.0.0-20180928190340-9d1f4485533b h1:sSQK05nvxs4UkgCJaxihteu+r+6ela3dNMm7NVmsS3c=
github.com/Netflix/go-expect v0.0.0-20180928190340-9d1f4485533b/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/coryb/figtree v0.0.0-20180728224503-071d1ef303df h1:cS4Z9Nlv8J4UqFbLp9ltZypgenm2p3Jeg0yqLfpH2pc=
github.com/coryb/figtree v0.0.0-20180728224503-071d1ef303df/go.mod h1:uAkZUEGm6dROpxfy+8vXLs7JrLCI4O+gQyKAuISxI/g=
github.com/coryb/kingpeon v0.0.0-20180107011214-9a669f143f2e h1:tGmk9Tuyz7fKuBq/d3nFJvVWRvc48MEBKQC4uYV3wb0=
github.com/coryb/kingpeon v0.0.0-20180107011214-9a669f143f2e/go.mod h1:gBc0uEH6swbOMoR7VkVuW7w5fGvZu/KHeSgxBR4Ta7Q=
github.com/coryb/oreo v0.0.0-20180804211640-3e1b88fc08f1 h1:Hh0qSvmvoAGL8VxvEoUv9UuUf9XlKcQtSxAMTz1kqfE=
github.com/coryb/oreo v0.0.0-20180804211640-3e1b88fc08f1/go.mod h1:l/wuS2rM8ostk0aApWje8tsZNWJPOc2TVr85B0n3e6M=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/guelfey/go.dbus v0.0.0-20131113121618-f6a3a2366cc3 h1:fngCxKbvZdctIsWj2hYijhAt4iK0JXSSA78B36xP0yI=
github.com/guelfey/go.dbus v0.0.0-20131113121618-f6a3a2366cc3/go.mod h1:0CNX5Cvi77WEH8llpfZ/ieuqyceb1cnO5//b5zzsnF8=
github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c h1:kp3AxgXgDOmIJFR7bIwqFhwJ2qWar8tEQSE5XXhCfVk=
github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
github.com/jinzhu/copier v0.0.0-20180308034124-7e38e58719c3 h1:sHsPfNMAG70QAvKbddQ0uScZCHQoZsT5NykGRCeeeIs=
github.com/jinzhu/copier v0.0.0-20180308034124-7e38e58719c3/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ=
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/pkg/browser v0.0.0-20170505125900-c90ca0c84f15 h1:mrI+6Ae64Wjt+uahGe5we/sPS1sXjvfT3YjtawAVgps=
github.com/pkg/browser v0.0.0-20170505125900-c90ca0c84f15/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/theckman/go-flock v0.4.0 h1:bcqNkS4RTQBGWybG7IBimUMxnLz53Qes1+D4QaOhzJc=
github.com/theckman/go-flock v0.4.0/go.mod h1:kjuth3y9VJ2aNlkNEO99G/8lp9fMIKaGyBmh84IBheM=
github.com/tidwall/gjson v0.0.0-20180711011033-ba784d767ac7 h1:PW7TzL8BOpYMcUYSv4qWDoH1Y5iRzVABteynvfF7pwE=
github.com/tidwall/gjson v0.0.0-20180711011033-ba784d767ac7/go.mod h1:c/nTNbUr0E0OrXEhq1pwa8iEgc2DOt4ZZqAt1HtCkPA=
github.com/tidwall/match v1.0.0 h1:Ym1EcFkp+UQ4ptxfWlW+iMdq5cPH5nEuGzdf/Pb7VmI=
github.com/tidwall/match v1.0.0/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tmc/keyring v0.0.0-20171121202319-839169085ae1 h1:+gXfyhy0t28Guz+vFztBg45yIquB2bNtiFvbItzJtUc=
github.com/tmc/keyring v0.0.0-20171121202319-839169085ae1/go.mod h1:gsa3jftQ3xia55nzIN4lXLYzDcWdxjojdKoz+N0St2Y=
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb h1:Ah9YqXLj6fEgeKqcmBuLCbAsrF3ScD7dJ/bYM0C6tXI=
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20171102191033-01c190206fbd h1:CLQSRrSDQMOMkogMxky7XOkERftMegAnxjT2re4E66M=
golang.org/x/net v0.0.0-20171102191033-01c190206fbd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.0.0-20180727230415-bd9dbc187b6e h1:3dQ4fR8k5KugjVKO0oqSd1odxuk2yaE2CIfxWP2WarQ=
golang.org/x/sys v0.0.0-20180727230415-bd9dbc187b6e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
gopkg.in/AlecAivazis/survey.v1 v1.6.1 h1:HyWkjKGBpzhNxrpaKRLDqoa4L1f4cMVBNU4bnVmU8Mw=
gopkg.in/AlecAivazis/survey.v1 v1.6.1/go.mod h1:2Ehl7OqkBl3Xb8VmC4oFW2bItAhnUfzIjrOzwRxCrOU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/coryb/yaml.v2 v2.0.0-20180616071044-0e40e46f7153 h1:3KfEubBNUdXqlEXuMz13dXy4cYK2AvuPhp8fKTYuPdU=
gopkg.in/coryb/yaml.v2 v2.0.0-20180616071044-0e40e46f7153/go.mod h1:Vth2iKfSejHZ3p6akgWO0iSjuuiu6mNCEgzcYUCnumw=
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW8s2qTSe3wGBtvo0MbVQG/c5k8RE=
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-14
View File
@@ -1,14 +0,0 @@
package jira
import (
"io"
"net/http"
)
type HttpClient interface {
Delete(url string) (*http.Response, error)
Do(*http.Request) (*http.Response, error)
GetJSON(url string) (*http.Response, error)
Post(url, bodyType string, body io.Reader) (*http.Response, error)
Put(url, bodyType string, body io.Reader) (*http.Response, error)
}
-585
View File
@@ -1,585 +0,0 @@
package jira
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/url"
"strings"
"github.com/coryb/oreo"
"github.com/go-jira/jira/jiradata"
)
type IssueQueryProvider interface {
ProvideIssueQueryString() string
}
type IssueOptions struct {
Fields []string `json:"fields,omitempty" yaml:"fields,omitempty"`
Expand []string `json:"expand,omitempty" yaml:"expand,omitempty"`
Properties []string `json:"properties,omitempty" yaml:"properties,omitempty"`
FieldsByKeys bool `json:"fieldsByKeys,omitempty" yaml:"fieldsByKeys,omitempty"`
UpdateHistory bool `json:"updateHistory,omitempty" yaml:"updateHistory,omitempty"`
}
func (o *IssueOptions) ProvideIssueQueryString() string {
params := []string{}
if len(o.Fields) > 0 {
params = append(params, "fields="+strings.Join(o.Fields, ","))
}
if len(o.Expand) > 0 {
params = append(params, "expand="+strings.Join(o.Expand, ","))
}
if len(o.Properties) > 0 {
params = append(params, "properties="+strings.Join(o.Properties, ","))
}
if o.FieldsByKeys {
params = append(params, "fieldsByKeys=true")
}
if o.UpdateHistory {
params = append(params, "updateHistory=true")
}
if len(params) > 0 {
return "?" + strings.Join(params, "&")
}
return ""
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-getIssue
func (j *Jira) GetIssue(issue string, iqg IssueQueryProvider) (*jiradata.Issue, error) {
return GetIssue(j.UA, j.Endpoint, issue, iqg)
}
func GetIssue(ua HttpClient, endpoint string, issue string, iqg IssueQueryProvider) (*jiradata.Issue, error) {
query := ""
if iqg != nil {
query = iqg.ProvideIssueQueryString()
}
uri := URLJoin(endpoint, "rest/api/2/issue", issue)
uri += query
resp, err := ua.GetJSON(uri)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
results := &jiradata.Issue{}
return results, json.NewDecoder(resp.Body).Decode(results)
}
return nil, responseError(resp)
}
func (j *Jira) GetIssueWorklog(issue string) (*jiradata.Worklogs, error) {
return GetIssueWorklog(j.UA, j.Endpoint, issue)
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issue/{issueIdOrKey}/worklog-getIssueWorklog
func GetIssueWorklog(ua HttpClient, endpoint string, issue string) (*jiradata.Worklogs, error) {
startAt := 0
total := 1
maxResults := 100
worklogs := jiradata.Worklogs{}
for startAt < total {
uri := URLJoin(endpoint, "rest/api/2/issue", issue, "worklog")
uri += fmt.Sprintf("?startAt=%d&maxResults=%d", startAt, maxResults)
resp, err := ua.GetJSON(uri)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
results := &jiradata.WorklogWithPagination{}
err := json.NewDecoder(resp.Body).Decode(results)
if err != nil {
return nil, err
}
startAt = startAt + maxResults
total = results.Total
worklogs = append(worklogs, results.Worklogs...)
} else {
return nil, responseError(resp)
}
}
return &worklogs, nil
}
type WorklogProvider interface {
ProvideWorklog() *jiradata.Worklog
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issue/{issueIdOrKey}/worklog-addWorklog
func (j *Jira) AddIssueWorklog(issue string, wp WorklogProvider) (*jiradata.Worklog, error) {
return AddIssueWorklog(j.UA, j.Endpoint, issue, wp)
}
func AddIssueWorklog(ua HttpClient, endpoint string, issue string, wp WorklogProvider) (*jiradata.Worklog, error) {
req := wp.ProvideWorklog()
encoded, err := json.Marshal(req)
if err != nil {
return nil, err
}
uri := URLJoin(endpoint, "rest/api/2/issue", issue, "worklog")
resp, err := ua.Post(uri, "application/json", bytes.NewBuffer(encoded))
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 201 {
results := &jiradata.Worklog{}
return results, json.NewDecoder(resp.Body).Decode(results)
}
return nil, responseError(resp)
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-getEditIssueMeta
func (j *Jira) GetIssueEditMeta(issue string) (*jiradata.EditMeta, error) {
return GetIssueEditMeta(j.UA, j.Endpoint, issue)
}
func GetIssueEditMeta(ua HttpClient, endpoint string, issue string) (*jiradata.EditMeta, error) {
uri := URLJoin(endpoint, "rest/api/2/issue", issue, "editmeta")
resp, err := ua.GetJSON(uri)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
results := &jiradata.EditMeta{}
return results, json.NewDecoder(resp.Body).Decode(results)
}
return nil, responseError(resp)
}
type IssueUpdateProvider interface {
ProvideIssueUpdate() *jiradata.IssueUpdate
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-editIssue
func (j *Jira) EditIssue(issue string, iup IssueUpdateProvider) error {
return EditIssue(j.UA, j.Endpoint, issue, iup)
}
func EditIssue(ua HttpClient, endpoint string, issue string, iup IssueUpdateProvider) error {
req := iup.ProvideIssueUpdate()
encoded, err := json.Marshal(req)
if err != nil {
return err
}
uri := URLJoin(endpoint, "rest/api/2/issue", issue)
resp, err := ua.Put(uri, "application/json", bytes.NewBuffer(encoded))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == 204 {
return nil
}
return responseError(resp)
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-createIssue
func (j *Jira) CreateIssue(iup IssueUpdateProvider) (*jiradata.IssueCreateResponse, error) {
return CreateIssue(j.UA, j.Endpoint, iup)
}
func CreateIssue(ua HttpClient, endpoint string, iup IssueUpdateProvider) (*jiradata.IssueCreateResponse, error) {
req := iup.ProvideIssueUpdate()
encoded, err := json.Marshal(req)
if err != nil {
return nil, err
}
uri := URLJoin(endpoint, "rest/api/2/issue")
resp, err := ua.Post(uri, "application/json", bytes.NewBuffer(encoded))
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 201 {
results := &jiradata.IssueCreateResponse{}
return results, json.NewDecoder(resp.Body).Decode(results)
}
return nil, responseError(resp)
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-getCreateIssueMeta
func (j *Jira) GetIssueCreateMetaProject(projectKey string) (*jiradata.CreateMetaProject, error) {
return GetIssueCreateMetaProject(j.UA, j.Endpoint, projectKey)
}
func GetIssueCreateMetaProject(ua HttpClient, endpoint string, projectKey string) (*jiradata.CreateMetaProject, error) {
uri := URLJoin(endpoint, "rest/api/2/issue/createmeta")
uri += fmt.Sprintf("?projectKeys=%s&expand=projects.issuetypes.fields", projectKey)
resp, err := ua.GetJSON(uri)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
results := &jiradata.CreateMeta{}
err = json.NewDecoder(resp.Body).Decode(results)
if err != nil {
return nil, err
}
for _, project := range results.Projects {
if project.Key == projectKey {
return project, nil
}
}
return nil, fmt.Errorf("project %s not found", projectKey)
}
return nil, responseError(resp)
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-getCreateIssueMeta
func (j *Jira) GetIssueCreateMetaIssueType(projectKey, issueTypeName string) (*jiradata.IssueType, error) {
return GetIssueCreateMetaIssueType(j.UA, j.Endpoint, projectKey, issueTypeName)
}
func GetIssueCreateMetaIssueType(ua HttpClient, endpoint string, projectKey, issueTypeName string) (*jiradata.IssueType, error) {
uri := URLJoin(endpoint, "rest/api/2/issue/createmeta")
uri += fmt.Sprintf("?projectKeys=%s&issuetypeNames=%s&expand=projects.issuetypes.fields", projectKey, url.QueryEscape(issueTypeName))
resp, err := ua.GetJSON(uri)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, responseError(resp)
}
results := &jiradata.CreateMeta{}
if err := json.NewDecoder(resp.Body).Decode(results); err != nil {
return nil, err
}
for _, project := range results.Projects {
if project.Key != projectKey {
continue
}
for _, issueType := range project.IssueTypes {
if issueType.Name == issueTypeName {
return issueType, nil
}
}
}
return nil, fmt.Errorf("project %s and IssueType %s not found", projectKey, issueTypeName)
}
type LinkIssueProvider interface {
ProvideLinkIssueRequest() *jiradata.LinkIssueRequest
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issueLink-linkIssues
func (j *Jira) LinkIssues(lip LinkIssueProvider) error {
return LinkIssues(j.UA, j.Endpoint, lip)
}
func LinkIssues(ua HttpClient, endpoint string, lip LinkIssueProvider) error {
req := lip.ProvideLinkIssueRequest()
encoded, err := json.Marshal(req)
if err != nil {
return err
}
uri := URLJoin(endpoint, "rest/api/2/issueLink")
resp, err := ua.Post(uri, "application/json", bytes.NewBuffer(encoded))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == 201 {
return nil
}
return responseError(resp)
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-getTransitions
func (j *Jira) GetIssueTransitions(issue string) (*jiradata.TransitionsMeta, error) {
return GetIssueTransitions(j.UA, j.Endpoint, issue)
}
func GetIssueTransitions(ua HttpClient, endpoint string, issue string) (*jiradata.TransitionsMeta, error) {
uri := URLJoin(endpoint, "rest/api/2/issue", issue, "transitions")
uri += "?expand=transitions.fields"
resp, err := ua.GetJSON(uri)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
results := &jiradata.TransitionsMeta{}
return results, json.NewDecoder(resp.Body).Decode(results)
}
return nil, responseError(resp)
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-doTransition
func (j *Jira) TransitionIssue(issue string, iup IssueUpdateProvider) error {
return TransitionIssue(j.UA, j.Endpoint, issue, iup)
}
func TransitionIssue(ua HttpClient, endpoint string, issue string, iup IssueUpdateProvider) error {
req := iup.ProvideIssueUpdate()
encoded, err := json.Marshal(req)
if err != nil {
return err
}
uri := URLJoin(endpoint, "rest/api/2/issue", issue, "transitions")
resp, err := ua.Post(uri, "application/json", bytes.NewBuffer(encoded))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == 204 {
return nil
}
return responseError(resp)
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issueLinkType-getIssueLinkTypes
func (j *Jira) GetIssueLinkTypes() (*jiradata.IssueLinkTypes, error) {
return GetIssueLinkTypes(j.UA, j.Endpoint)
}
func GetIssueLinkTypes(ua HttpClient, endpoint string) (*jiradata.IssueLinkTypes, error) {
uri := URLJoin(endpoint, "rest/api/2/issueLinkType")
resp, err := ua.GetJSON(uri)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
results := struct {
IssueLinkTypes jiradata.IssueLinkTypes
}{
IssueLinkTypes: jiradata.IssueLinkTypes{},
}
return &results.IssueLinkTypes, json.NewDecoder(resp.Body).Decode(&results)
}
return nil, responseError(resp)
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-addVote
func (j *Jira) IssueAddVote(issue string) error {
return IssueAddVote(j.UA, j.Endpoint, issue)
}
func IssueAddVote(ua HttpClient, endpoint string, issue string) error {
uri := URLJoin(endpoint, "rest/api/2/issue", issue, "votes")
resp, err := ua.Post(uri, "application/json", strings.NewReader("{}"))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == 204 {
return nil
}
return responseError(resp)
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-removeVote
func (j *Jira) IssueRemoveVote(issue string) error {
return IssueRemoveVote(j.UA, j.Endpoint, issue)
}
func IssueRemoveVote(ua HttpClient, endpoint string, issue string) error {
uri := URLJoin(endpoint, "rest/api/2/issue", issue, "votes")
resp, err := ua.Delete(uri)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == 204 {
return nil
}
return responseError(resp)
}
type RankRequestProvider interface {
ProvideRankRequest() *jiradata.RankRequest
}
// https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/issue-rankIssues
func (j *Jira) RankIssues(rrp RankRequestProvider) error {
return RankIssues(j.UA, j.Endpoint, rrp)
}
func RankIssues(ua HttpClient, endpoint string, rrp RankRequestProvider) error {
req := rrp.ProvideRankRequest()
encoded, err := json.Marshal(req)
if err != nil {
return err
}
uri := URLJoin(endpoint, "rest/agile/1.0/issue/rank")
resp, err := ua.Put(uri, "application/json", bytes.NewBuffer(encoded))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == 204 {
return nil
}
return responseError(resp)
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-addWatcher
func (j *Jira) IssueAddWatcher(issue, user string) error {
return IssueAddWatcher(j.UA, j.Endpoint, issue, user)
}
func IssueAddWatcher(ua HttpClient, endpoint string, issue, user string) error {
uri := URLJoin(endpoint, "rest/api/2/issue", issue, "watchers")
resp, err := ua.Post(uri, "application/json", strings.NewReader(fmt.Sprintf("%q", user)))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == 204 {
return nil
}
return responseError(resp)
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-addWatcher
func (j *Jira) IssueRemoveWatcher(issue, user string) error {
return IssueRemoveWatcher(j.UA, j.Endpoint, issue, user)
}
func IssueRemoveWatcher(ua HttpClient, endpoint string, issue, user string) error {
uri := URLJoin(endpoint, "rest/api/2/issue", issue, "watchers")
uri += fmt.Sprintf("?username=%s", user)
resp, err := ua.Delete(uri)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == 204 {
return nil
}
return responseError(resp)
}
type CommentProvider interface {
ProvideComment() *jiradata.Comment
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issue/{issueIdOrKey}/comment-addComment
func (j *Jira) IssueAddComment(issue string, cp CommentProvider) (*jiradata.Comment, error) {
return IssueAddComment(j.UA, j.Endpoint, issue, cp)
}
func IssueAddComment(ua HttpClient, endpoint string, issue string, cp CommentProvider) (*jiradata.Comment, error) {
req := cp.ProvideComment()
encoded, err := json.Marshal(req)
if err != nil {
return nil, err
}
uri := URLJoin(endpoint, "rest/api/2/issue", issue, "comment")
resp, err := ua.Post(uri, "application/json", bytes.NewBuffer(encoded))
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 201 {
results := jiradata.Comment{}
return &results, json.NewDecoder(resp.Body).Decode(&results)
}
return nil, responseError(resp)
}
type UserProvider interface {
ProvideUser() *jiradata.User
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issue-assign
func (j *Jira) IssueAssign(issue, name string) error {
return IssueAssign(j.UA, j.Endpoint, issue, name)
}
func IssueAssign(ua HttpClient, endpoint string, issue, name string) error {
// this is special, not using the jiradata.User structure
// because we need to be able to send `null` as the name param
// when we want to un-assign the issue
req := struct {
Name *string `json:"name"`
}{&name}
if name == "" {
req.Name = nil
}
encoded, err := json.Marshal(req)
if err != nil {
return err
}
uri := URLJoin(endpoint, "rest/api/2/issue", issue, "assignee")
resp, err := ua.Put(uri, "application/json", bytes.NewBuffer(encoded))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == 204 {
return nil
}
return responseError(resp)
}
// https://docs.atlassian.com/jira/REST/cloud/#api/2/issue/{issueIdOrKey}/attachments-addAttachment
func (j *Jira) IssueAttachFile(issue, filename string, contents io.Reader) (*jiradata.ListOfAttachment, error) {
return IssueAttachFile(j.UA, j.Endpoint, issue, filename, contents)
}
func IssueAttachFile(ua HttpClient, endpoint string, issue, filename string, contents io.Reader) (*jiradata.ListOfAttachment, error) {
var buf bytes.Buffer
w := multipart.NewWriter(&buf)
formFile, err := w.CreateFormFile("file", filename)
if err != nil {
return nil, err
}
_, err = io.Copy(formFile, contents)
if err != nil {
return nil, err
}
uri, err := url.Parse(URLJoin(endpoint, "rest/api/2/issue", issue, "attachments"))
if err != nil {
return nil, err
}
req := oreo.RequestBuilder(uri).WithMethod("POST").WithHeader(
"X-Atlassian-Token", "no-check",
).WithHeader(
"Accept", "application/json",
).WithContentType(w.FormDataContentType()).WithBody(&buf).Build()
w.Close()
resp, err := ua.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
results := jiradata.ListOfAttachment{}
return &results, json.NewDecoder(resp.Body).Decode(&results)
}
return nil, responseError(resp)
}
-19
View File
@@ -1,19 +0,0 @@
package jira
import (
"github.com/coryb/oreo"
)
const VERSION = "1.0.20"
type Jira struct {
Endpoint string `json:"endpoint,omitempty" yaml:"endpoint,omitempty"`
UA HttpClient `json:"-" yaml:"-"`
}
func NewJira(endpoint string) *Jira {
return &Jira{
Endpoint: endpoint,
UA: oreo.New(),
}
}
-460
View File
@@ -1,460 +0,0 @@
package jiracli
import (
"bytes"
"crypto/tls"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"reflect"
"runtime/debug"
"strings"
"github.com/coryb/figtree"
"github.com/coryb/oreo"
"github.com/jinzhu/copier"
shellquote "github.com/kballard/go-shellquote"
"github.com/tidwall/gjson"
"gopkg.in/AlecAivazis/survey.v1"
kingpin "gopkg.in/alecthomas/kingpin.v2"
yaml "gopkg.in/coryb/yaml.v2"
logging "gopkg.in/op/go-logging.v1"
)
type Exit struct {
Code int
}
// HandleExit will unwind any panics and check to see if they are jiracli.Exit
// and exit accordingly.
//
// Example:
// func main() {
// defer jiracli.HandleExit()
// ...
// }
func HandleExit() {
if e := recover(); e != nil {
if exit, ok := e.(Exit); ok {
os.Exit(exit.Code)
} else {
fmt.Fprintf(os.Stderr, "%s\n%s", e, debug.Stack())
os.Exit(1)
}
}
}
type GlobalOptions struct {
// AuthenticationMethod is the method we use to authenticate with the jira serivce. Possible values are "api-token" or "session".
// The default is "api-token" when the service endpoint ends with "atlassian.net", otherwise it "session". Session authentication
// will promt for user password and use the /auth/1/session-login endpoint.
AuthenticationMethod figtree.StringOption `yaml:"authentication-method,omitempty" json:"authentication-method,omitempty"`
// Endpoint is the URL for the Jira service. Something like: https://go-jira.atlassian.net
Endpoint figtree.StringOption `yaml:"endpoint,omitempty" json:"endpoint,omitempty"`
// Insecure will allow you to connect to an https endpoint with a self-signed SSL certificate
Insecure figtree.BoolOption `yaml:"insecure,omitempty" json:"insecure,omitempty"`
// Login is the id used for authenticating with the Jira service. For "api-token" AuthenticationMethod this is usually a
// full email address, something like "user@example.com". For "session" AuthenticationMethod this will be something
// like "user", which by default will use the same value in the `User` field.
Login figtree.StringOption `yaml:"login,omitempty" json:"login,omitempty"`
// PasswordSource specificies the method that we fetch the password. Possible values are "keyring" or "pass".
// If this is unset we will just prompt the user. For "keyring" this will look in the OS keychain, if missing
// then prompt the user and store the password in the OS keychain. For "pass" this will look in the PasswordDirectory
// location using the `pass` tool, if missing prompt the user and store in the PasswordDirectory
PasswordSource figtree.StringOption `yaml:"password-source,omitempty" json:"password-source,omitempty"`
// PasswordDirectory is only used for the "pass" PasswordSource. It is the location for the encrypted password
// files used by `pass`. Effectively this overrides the "PASSWORD_STORE_DIR" environment variable
PasswordDirectory figtree.StringOption `yaml:"password-directory,omitempty" json:"password-directory,omitempty"`
// PasswordName is the the name of the password key entry stored used with PasswordSource `pass`.
PasswordName figtree.StringOption `yaml:"password-name,omitempty" json:"password-name,omitempty"`
// Quiet will lower the defalt log level to suppress the standard output for commands
Quiet figtree.BoolOption `yaml:"quiet,omitempty" json:"quiet,omitempty"`
// SocksProxy is used to configure the http client to access the Endpoint via a socks proxy. The value
// should be a ip address and port string, something like "127.0.0.1:1080"
SocksProxy figtree.StringOption `yaml:"socksproxy,omitempty" json:"socksproxy,omitempty"`
// UnixProxy is use to configure the http client to access the Endpoint via a local unix domain socket used
// to proxy requests
UnixProxy figtree.StringOption `yaml:"unixproxy,omitempty" json:"unixproxy,omitempty"`
// User is use to represent the user on the Jira service. This can be different from the username used to
// authenticate with the service. For example when using AuthenticationMethod `api-token` the Login is
// typically an email address like `username@example.com` and the User property would be someting like
// `username` The User property is used on Jira service API calls that require a user to associate with
// an Issue (like assigning a Issue to yourself)
User figtree.StringOption `yaml:"user,omitempty" json:"user,omitempty"`
}
type CommonOptions struct {
Browse figtree.BoolOption `yaml:"browse,omitempty" json:"browse,omitempty"`
Editor figtree.StringOption `yaml:"editor,omitempty" json:"editor,omitempty"`
GJsonQuery figtree.StringOption `yaml:"gjq,omitempty" json:"gjq,omitempty"`
SkipEditing figtree.BoolOption `yaml:"noedit,omitempty" json:"noedit,omitempty"`
Template figtree.StringOption `yaml:"template,omitempty" json:"template,omitempty"`
}
type CommandRegistryEntry struct {
Help string
UsageFunc func(*figtree.FigTree, *kingpin.CmdClause) error
ExecuteFunc func(*oreo.Client, *GlobalOptions) error
}
type CommandRegistry struct {
Command string
Aliases []string
Entry *CommandRegistryEntry
Default bool
}
// either kingpin.Application or kingpin.CmdClause fit this interface
type kingpinAppOrCommand interface {
Command(string, string) *kingpin.CmdClause
GetCommand(string) *kingpin.CmdClause
}
var globalCommandRegistry = []CommandRegistry{}
func RegisterCommand(regEntry CommandRegistry) {
globalCommandRegistry = append(globalCommandRegistry, regEntry)
}
func (o *GlobalOptions) AuthMethod() string {
if strings.Contains(o.Endpoint.Value, ".atlassian.net") && o.AuthenticationMethod.Source == "default" {
return "api-token"
}
return o.AuthenticationMethod.Value
}
func register(app *kingpin.Application, o *oreo.Client, fig *figtree.FigTree) {
globals := GlobalOptions{
User: figtree.NewStringOption(os.Getenv("USER")),
AuthenticationMethod: figtree.NewStringOption("session"),
}
app.Flag("endpoint", "Base URI to use for Jira").Short('e').SetValue(&globals.Endpoint)
app.Flag("insecure", "Disable TLS certificate verification").Short('k').SetValue(&globals.Insecure)
app.Flag("quiet", "Suppress output to console").Short('Q').SetValue(&globals.Quiet)
app.Flag("unixproxy", "Path for a unix-socket proxy").SetValue(&globals.UnixProxy)
app.Flag("socksproxy", "Address for a socks proxy").SetValue(&globals.SocksProxy)
app.Flag("user", "user name used within the Jira service").Short('u').SetValue(&globals.User)
app.Flag("login", "login name that corresponds to the user used for authentication").SetValue(&globals.Login)
o = o.WithPreCallback(func(req *http.Request) (*http.Request, error) {
if globals.AuthMethod() == "api-token" {
// need to set basic auth header with user@domain:api-token
token := globals.GetPass()
authHeader := fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", globals.Login.Value, token))))
req.Header.Add("Authorization", authHeader)
}
return req, nil
})
o = o.WithPostCallback(func(req *http.Request, resp *http.Response) (*http.Response, error) {
if globals.AuthMethod() == "session" {
authUser := resp.Header.Get("X-Ausername")
if authUser == "" || authUser == "anonymous" {
// preserve the --quiet value, we need to temporarily disable it so
// the normal login output is surpressed
defer func(quiet bool) {
globals.Quiet.Value = quiet
}(globals.Quiet.Value)
globals.Quiet.Value = true
// we are not logged in, so force login now by running the "login" command
app.Parse([]string{"login"})
// rerun the original request
return o.Do(req)
}
} else if globals.AuthMethod() == "api-token" && resp.StatusCode == 401 {
globals.SetPass("")
return o.Do(req)
}
return resp, nil
})
for _, command := range globalCommandRegistry {
copy := command
commandFields := strings.Fields(copy.Command)
var appOrCmd kingpinAppOrCommand = app
if len(commandFields) > 1 {
for _, name := range commandFields[0 : len(commandFields)-1] {
tmp := appOrCmd.GetCommand(name)
if tmp == nil {
tmp = appOrCmd.Command(name, "")
}
appOrCmd = tmp
}
}
cmd := appOrCmd.Command(commandFields[len(commandFields)-1], copy.Entry.Help)
LoadConfigs(cmd, fig, &globals)
cmd.PreAction(func(_ *kingpin.ParseContext) error {
if globals.Insecure.Value {
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
o = o.WithTransport(transport)
}
if globals.UnixProxy.Value != "" {
o = o.WithTransport(unixProxy(globals.UnixProxy.Value))
} else if globals.SocksProxy.Value != "" {
o = o.WithTransport(socksProxy(globals.SocksProxy.Value))
}
if globals.AuthMethod() == "api-token" {
o = o.WithCookieFile("")
}
if globals.Login.Value == "" {
globals.Login = globals.User
}
return nil
})
for _, alias := range copy.Aliases {
cmd = cmd.Alias(alias)
}
if copy.Default {
cmd = cmd.Default()
}
if copy.Entry.UsageFunc != nil {
copy.Entry.UsageFunc(fig, cmd)
}
cmd.Action(func(_ *kingpin.ParseContext) error {
if logging.GetLevel("") > logging.DEBUG {
o = o.WithTrace(true)
}
return copy.Entry.ExecuteFunc(o, &globals)
})
}
}
func LoadConfigs(cmd *kingpin.CmdClause, fig *figtree.FigTree, opts interface{}) {
cmd.PreAction(func(_ *kingpin.ParseContext) error {
os.Setenv("JIRA_OPERATION", cmd.FullCommand())
// load command specific configs first
if err := fig.LoadAllConfigs(strings.Join(strings.Fields(cmd.FullCommand()), "_")+".yml", opts); err != nil {
return err
}
// then load generic configs if not already populated above
return fig.LoadAllConfigs("config.yml", opts)
})
}
func BrowseUsage(cmd *kingpin.CmdClause, opts *CommonOptions) {
cmd.Flag("browse", "Open issue(s) in browser after operation").Short('b').SetValue(&opts.Browse)
}
func EditorUsage(cmd *kingpin.CmdClause, opts *CommonOptions) {
cmd.Flag("editor", "Editor to use").SetValue(&opts.Editor)
}
func TemplateUsage(cmd *kingpin.CmdClause, opts *CommonOptions) {
cmd.Flag("template", "Template to use for output").Short('t').SetValue(&opts.Template)
}
func GJsonQueryUsage(cmd *kingpin.CmdClause, opts *CommonOptions) {
cmd.Flag("gjq", "GJSON Query to filter output, see https://goo.gl/iaYwJ5").SetValue(&opts.GJsonQuery)
}
func (o *CommonOptions) PrintTemplate(data interface{}) error {
if o.GJsonQuery.Value != "" {
buf := bytes.NewBufferString("")
RunTemplate("json", data, buf)
results := gjson.GetBytes(buf.Bytes(), o.GJsonQuery.Value)
_, err := os.Stdout.Write([]byte(results.String()))
os.Stdout.Write([]byte{'\n'})
return err
}
return RunTemplate(o.Template.Value, data, nil)
}
func (o *CommonOptions) editFile(fileName string) (changes bool, err error) {
var editor string
for _, ed := range []string{o.Editor.Value, os.Getenv("JIRA_EDITOR"), os.Getenv("EDITOR"), "vim"} {
if ed != "" {
editor = ed
break
}
}
if o.SkipEditing.Value {
return false, nil
}
tmpFileNameOrig := fmt.Sprintf("%s.orig", fileName)
if err := copyFile(fileName, tmpFileNameOrig); err != nil {
return false, err
}
defer func() {
os.Remove(tmpFileNameOrig)
}()
shell, _ := shellquote.Split(editor)
shell = append(shell, fileName)
log.Debugf("Running: %#v", shell)
cmd := exec.Command(shell[0], shell[1:]...)
cmd.Stdout, cmd.Stderr, cmd.Stdin = os.Stdout, os.Stderr, os.Stdin
if err := cmd.Run(); err != nil {
return false, err
}
// now we just need to diff the files to see if there are any changes
f1, err := os.Open(tmpFileNameOrig)
if err != nil {
return false, err
}
f2, err := os.Open(fileName)
if err != nil {
return false, err
}
stat1, err := f1.Stat()
if err != nil {
return false, err
}
stat2, err := f2.Stat()
if err != nil {
return false, err
}
// different sizes, so must have changes
if stat1.Size() != stat2.Size() {
return true, nil
}
p1, p2 := make([]byte, 1024), make([]byte, 1024)
var n1, n2 int
// loop though 1024 bytes at a time comparing the buffers for changes
for err != io.EOF {
n1, _ = f1.Read(p1)
n2, err = f2.Read(p2)
if n1 != n2 {
return true, nil
}
if !bytes.Equal(p1[:n1], p2[:n2]) {
return true, nil
}
}
return false, nil
}
var EditLoopAbort = fmt.Errorf("edit Loop aborted by request")
func EditLoop(opts *CommonOptions, input interface{}, output interface{}, submit func() error) error {
tmpFile, err := tmpTemplate(opts.Template.Value, input)
if err != nil {
return err
}
confirm := func(dflt bool, msg string) (answer bool) {
survey.AskOne(
&survey.Confirm{Message: msg, Default: dflt},
&answer,
nil,
)
return
}
// we need to copy the original output so that we can restore
// it on retries in case we try to populate bogus fields that
// are rejected by the jira service.
dup := reflect.New(reflect.ValueOf(output).Elem().Type())
err = copier.Copy(dup.Interface(), output)
if err != nil {
return err
}
for {
if !opts.SkipEditing.Value {
changes, err := opts.editFile(tmpFile)
if err != nil {
log.Error(err.Error())
if confirm(true, "Editor reported an error, edit again?") {
continue
}
return EditLoopAbort
}
if !changes {
if !confirm(false, "No changes detected, submit anyway?") {
return EditLoopAbort
}
}
}
// parse template
data, err := ioutil.ReadFile(tmpFile)
if err != nil {
return err
}
defer func(mapType, iface reflect.Type) {
yaml.DefaultMapType = mapType
yaml.IfaceType = iface
}(yaml.DefaultMapType, yaml.IfaceType)
yaml.DefaultMapType = reflect.TypeOf(map[string]interface{}{})
yaml.IfaceType = yaml.DefaultMapType.Elem()
// restore output incase of retry loop
err = copier.Copy(output, dup.Interface())
if err != nil {
return err
}
// HACK HACK HACK we want to trim out all the yaml garbage that is not
// poplulated, like empty arrays, string values with only a newline,
// etc. We need to do this because jira will reject json documents
// with empty arrays, or empty strings typically. So here we process
// the data to a raw interface{} then we fixup the yaml parsed
// interface, then we serialize to a new yaml document ... then is
// parsed as the original document to populate the output struct. Phew.
var raw interface{}
if err := yaml.Unmarshal(data, &raw); err != nil {
log.Error(err.Error())
if confirm(true, "Invalid YAML syntax, edit again?") {
continue
}
return EditLoopAbort
}
yamlFixup(&raw)
fixedYAML, err := yaml.Marshal(&raw)
if err != nil {
log.Error(err.Error())
if confirm(true, "Invalid YAML syntax, edit again?") {
continue
}
return EditLoopAbort
}
if err := yaml.Unmarshal(fixedYAML, output); err != nil {
log.Error(err.Error())
if confirm(true, "Invalid YAML syntax, edit again?") {
continue
}
return EditLoopAbort
}
// submit template
if err := submit(); err != nil {
log.Error(err.Error())
if confirm(true, "Jira reported an error, edit again?") {
continue
}
return EditLoopAbort
}
break
}
return nil
}
-13
View File
@@ -1,13 +0,0 @@
package jiracli
import "github.com/pkg/errors"
type Error struct {
error
}
func CliError(cause error) error {
return &Error{
errors.WithStack(cause),
}
}
-17
View File
@@ -1,17 +0,0 @@
// +build !windows
package jiracli
import "github.com/tmc/keyring"
func keyringGet(user string) (string, error) {
password, err := keyring.Get("go-jira", user)
if err != nil && err != keyring.ErrNotFound {
return password, err
}
return password, nil
}
func keyringSet(user, passwd string) error {
return keyring.Set("go-jira", user, passwd)
}
-11
View File
@@ -1,11 +0,0 @@
package jiracli
import "fmt"
func keyringGet(user string) (string, error) {
return "", fmt.Errorf("Keyring is not supported for Windows, see: https://github.com/tmc/keyring")
}
func keyringSet(user, passwd string) error {
return fmt.Errorf("Keyring is not supported for Windows, see: https://github.com/tmc/keyring")
}
-38
View File
@@ -1,38 +0,0 @@
package jiracli
import (
"os"
"strconv"
logging "gopkg.in/op/go-logging.v1"
)
var (
log = logging.MustGetLogger("jira")
)
func IncreaseLogLevel(verbosity int) {
logging.SetLevel(logging.GetLevel("")+logging.Level(verbosity), "")
}
func InitLogging() {
logBackend := logging.NewLogBackend(os.Stderr, "", 0)
format := os.Getenv("JIRA_LOG_FORMAT")
if format == "" {
format = "%{color}%{level:-5s}%{color:reset} %{message}"
}
logging.SetBackend(
logging.NewBackendFormatter(
logBackend,
logging.MustStringFormatter(format),
),
)
if os.Getenv("JIRA_DEBUG") == "" {
logging.SetLevel(logging.NOTICE, "")
} else {
logging.SetLevel(logging.DEBUG, "")
if verbosity, err := strconv.Atoi(os.Getenv("JIRA_DEBUG")); err == nil {
IncreaseLogLevel(verbosity)
}
}
}
-142
View File
@@ -1,142 +0,0 @@
package jiracli
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"
"github.com/go-jira/jira/jiradata"
"gopkg.in/AlecAivazis/survey.v1"
)
func (o *GlobalOptions) ProvideAuthParams() *jiradata.AuthParams {
return &jiradata.AuthParams{
Username: o.Login.Value,
Password: o.GetPass(),
}
}
func (o *GlobalOptions) keyName() string {
user := o.Login.Value
if o.AuthMethod() == "api-token" {
user = "api-token:" + user
}
if o.PasswordSource.Value == "pass" {
if o.PasswordName.Value != "" {
return o.PasswordName.Value
}
return fmt.Sprintf("GoJira/%s", user)
}
return user
}
func (o *GlobalOptions) GetPass() string {
passwd := ""
if o.PasswordSource.Value != "" {
if o.PasswordSource.Value == "keyring" {
var err error
passwd, err = keyringGet(o.keyName())
if err != nil {
panic(err)
}
} else if o.PasswordSource.Value == "pass" {
if o.PasswordDirectory.Value != "" {
orig := os.Getenv("PASSWORD_STORE_DIR")
os.Setenv("PASSWORD_STORE_DIR", o.PasswordDirectory.Value)
defer os.Setenv("PASSWORD_STORE_DIR", orig)
}
if bin, err := exec.LookPath("pass"); err == nil {
buf := bytes.NewBufferString("")
cmd := exec.Command(bin, o.keyName())
cmd.Stdout = buf
cmd.Stderr = buf
if err := cmd.Run(); err == nil {
passwd = strings.TrimSpace(buf.String())
}
}
} else if o.PasswordSource.Value == "stdin" {
allBytes, err := ioutil.ReadAll(os.Stdin)
if err != nil {
panic(fmt.Sprintf("unable to read bytes from stdin: %s", err))
}
passwd = string(allBytes)
} else {
log.Warningf("Unknown password-source: %s", o.PasswordSource)
}
}
if passwd != "" {
return passwd
}
if passwd = os.Getenv("JIRA_API_TOKEN"); passwd != "" && o.AuthMethod() == "api-token" {
return passwd
}
prompt := fmt.Sprintf("Jira Password [%s]: ", o.Login)
help := ""
if o.AuthMethod() == "api-token" {
prompt = fmt.Sprintf("Jira API-Token [%s]: ", o.Login)
help = "API Tokens may be required by your Jira service endpoint: https://developer.atlassian.com/cloud/jira/platform/deprecation-notice-basic-auth-and-cookie-based-auth/"
}
err := survey.AskOne(
&survey.Password{
Message: prompt,
Help: help,
},
&passwd,
nil,
)
if err != nil {
log.Errorf("%s", err)
panic(Exit{Code: 1})
}
o.SetPass(passwd)
return passwd
}
func (o *GlobalOptions) SetPass(passwd string) error {
if o.PasswordSource.Value == "keyring" {
// save password in keychain so that it can be used for subsequent http requests
err := keyringSet(o.keyName(), passwd)
if err != nil {
log.Errorf("Failed to set password in keyring: %s", err)
return err
}
} else if o.PasswordSource.Value == "pass" {
if o.PasswordDirectory.Value != "" {
orig := os.Getenv("PASSWORD_STORE_DIR")
os.Setenv("PASSWORD_STORE_DIR", o.PasswordDirectory.Value)
defer os.Setenv("PASSWORD_STORE_DIR", orig)
}
if bin, err := exec.LookPath("pass"); err == nil {
log.Debugf("using %s", bin)
passName := o.keyName()
if passwd != "" {
in := bytes.NewBufferString(fmt.Sprintf("%s\n%s\n", passwd, passwd))
out := bytes.NewBufferString("")
cmd := exec.Command(bin, "insert", "--force", passName)
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 {
// clear the `pass` entry on empty password
if err := exec.Command(bin, "rm", "--force", passName).Run(); err != nil {
return fmt.Errorf("Failed to clear password for %s", passName)
}
}
}
} else if o.PasswordSource.Value != "" {
return fmt.Errorf("Unknown password-source: %s", o.PasswordSource)
}
return nil
}
-31
View File
@@ -1,31 +0,0 @@
package jiracli
import (
"net"
"net/http"
"time"
"golang.org/x/net/proxy"
)
func socksProxy(address string) *http.Transport {
return newSocksProxyTransport(address)
}
func newSocksProxyTransport(address string) *http.Transport {
dialer, err := proxy.SOCKS5("tcp", address, nil, proxy.Direct)
if err != nil {
// TODO: whoops, return error?
panic(err)
}
dial := func(network, addr string) (net.Conn, error) {
return dialer.Dial(network, addr)
}
return &http.Transport{
Dial: dial,
DisableKeepAlives: true,
ResponseHeaderTimeout: 30 * time.Second,
ExpectContinueTimeout: 10 * time.Second,
}
}
-561
View File
@@ -1,561 +0,0 @@
package jiracli
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"regexp"
"strconv"
"strings"
"text/template"
yaml "gopkg.in/coryb/yaml.v2"
"github.com/coryb/figtree"
shellquote "github.com/kballard/go-shellquote"
"github.com/mgutz/ansi"
"golang.org/x/crypto/ssh/terminal"
)
func findTemplate(name string) ([]byte, error) {
if file, err := findClosestParentPath(filepath.Join(".jira.d", "templates", name)); err == nil {
b, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
return b, nil
}
return nil, nil
}
func getTemplate(name string) (string, error) {
if _, err := os.Stat(name); err == nil {
b, err := ioutil.ReadFile(name)
if err != nil {
return "", err
}
return string(b), nil
}
b, err := findTemplate(name)
if err != nil {
return "", err
}
if b != nil {
return string(b), nil
}
if s, ok := AllTemplates[name]; ok {
return s, nil
}
return "", fmt.Errorf("No Template found for %q", name)
}
func tmpTemplate(templateName string, data interface{}) (string, error) {
tmpFile, err := tmpYml(templateName)
if err != nil {
return "", err
}
defer tmpFile.Close()
return tmpFile.Name(), RunTemplate(templateName, data, tmpFile)
}
func TemplateProcessor() *template.Template {
funcs := map[string]interface{}{
"jira": func() string {
return os.Args[0]
},
"env": func() map[string]string {
out := map[string]string{}
for _, env := range os.Environ() {
kv := strings.SplitN(env, "=", 2)
out[kv[0]] = kv[1]
}
return out
},
"shellquote": func(content string) string {
return shellquote.Join(content)
},
"toMinJson": func(content interface{}) (string, error) {
bytes, err := json.Marshal(content)
if err != nil {
return "", err
}
return string(bytes), nil
},
"toJson": func(content interface{}) (string, error) {
bytes, err := json.MarshalIndent(content, "", " ")
if err != nil {
return "", err
}
return string(bytes), nil
},
"termWidth": func() int {
w, _, err := terminal.GetSize(int(os.Stdout.Fd()))
if err == nil {
return w
}
if os.Getenv("COLUMNS") != "" {
w, err = strconv.Atoi(os.Getenv("COLUMNS"))
}
if err == nil {
return w
}
return 120
},
"sub": func(a, b int) int {
return a - b
},
"append": func(more string, content interface{}) (string, error) {
switch value := content.(type) {
case string:
return string(append([]byte(content.(string)), []byte(more)...)), nil
case []byte:
return string(append(content.([]byte), []byte(more)...)), nil
default:
return "", fmt.Errorf("Unknown type: %s", value)
}
},
"indent": func(spaces int, content string) string {
indent := make([]rune, spaces+1)
indent[0] = '\n'
for i := 1; i < spaces+1; i++ {
indent[i] = ' '
}
lineSeps := []rune{'\n', '\u0085', '\u2028', '\u2029'}
for _, sep := range lineSeps {
indent[0] = sep
content = strings.Replace(content, string(sep), string(indent), -1)
}
return content
},
"comment": func(content string) string {
lineSeps := []rune{'\n', '\u0085', '\u2028', '\u2029'}
for _, sep := range lineSeps {
content = strings.Replace(content, string(sep), string([]rune{sep, '#', ' '}), -1)
}
return content
},
"color": func(color string) string {
return ansi.ColorCode(color)
},
"regReplace": func(search string, replace string, content string) string {
re := regexp.MustCompile(search)
return re.ReplaceAllString(content, replace)
},
"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 && max > 2 {
var buffer bytes.Buffer
buffer.WriteString(content[:max-3])
buffer.WriteString("...")
return buffer.String()
}
return content
},
"rep": func(count int, content string) string {
var buffer bytes.Buffer
for i := 0; i < count; i++ {
buffer.WriteString(content)
}
return buffer.String()
},
"age": func(content string) (string, error) {
return fuzzyAge(content)
},
"dateFormat": func(format string, content string) (string, error) {
return dateFormat(format, content)
},
}
return template.New("gojira").Funcs(funcs)
}
func ConfigTemplate(fig *figtree.FigTree, template, command string, opts interface{}) (string, error) {
var tmp map[string]interface{}
err := ConvertType(opts, &tmp)
if err != nil {
return "", err
}
fig.LoadAllConfigs(command+".yml", &tmp)
fig.LoadAllConfigs("config.yml", &tmp)
tmpl, err := TemplateProcessor().Parse(template)
if err != nil {
return "", err
}
buf := bytes.NewBufferString("")
if err := tmpl.Execute(buf, &tmp); err != nil {
return "", err
}
return buf.String(), nil
}
func ConvertType(input interface{}, output interface{}) error {
// HACK HACK HACK: convert data formats to json for backwards compatibilty with templates
jsonData, err := json.Marshal(input)
if err != nil {
return err
}
defer func(mapType, iface reflect.Type) {
yaml.DefaultMapType = mapType
yaml.IfaceType = iface
}(yaml.DefaultMapType, yaml.IfaceType)
yaml.DefaultMapType = reflect.TypeOf(map[string]interface{}{})
yaml.IfaceType = yaml.DefaultMapType.Elem()
if err := yaml.Unmarshal(jsonData, output); err != nil {
return err
}
return nil
}
func RunTemplate(templateName string, data interface{}, out io.Writer) error {
templateContent, err := getTemplate(templateName)
if err != nil {
return err
}
if out == nil {
out = os.Stdout
}
var rawData interface{}
err = ConvertType(data, &rawData)
if err != nil {
return err
}
tmpl, err := TemplateProcessor().Parse(templateContent)
if err != nil {
return err
}
if err := tmpl.Execute(out, rawData); err != nil {
return err
}
return nil
}
var AllTemplates = map[string]string{
"attach-list": defaultAttachListTemplate,
"comment": defaultCommentTemplate,
"component-add": defaultComponentAddTemplate,
"components": defaultComponentsTemplate,
"create": defaultCreateTemplate,
"createmeta": defaultDebugTemplate,
"debug": defaultDebugTemplate,
"edit": defaultEditTemplate,
"editmeta": defaultDebugTemplate,
"epic-create": defaultEpicCreateTemplate,
"epic-list": defaultTableTemplate,
"fields": defaultDebugTemplate,
"issuelinktypes": defaultDebugTemplate,
"issuetypes": defaultIssuetypesTemplate,
"json": defaultDebugTemplate,
"list": defaultListTemplate,
"request": defaultDebugTemplate,
"subtask": defaultSubtaskTemplate,
"table": defaultTableTemplate,
"transition": defaultTransitionTemplate,
"transitions": defaultTransitionsTemplate,
"transmeta": defaultDebugTemplate,
"view": defaultViewTemplate,
"worklog": defaultWorklogTemplate,
"worklogs": defaultWorklogsTemplate,
}
const defaultDebugTemplate = "{{ . | toJson}}\n"
const defaultListTemplate = "{{ range .issues }}{{ .key | append \":\" | printf \"%-12s\"}} {{ .fields.summary }}\n{{ end }}"
const defaultTableTemplate = `{{/* table template */ -}}
{{$w := sub termWidth 107 -}}
+{{ "-" | rep 16 }}+{{ "-" | rep $w }}+{{ "-" | rep 14 }}+{{ "-" | rep 14 }}+{{ "-" | rep 14 }}+{{ "-" | rep 12 }}+{{ "-" | rep 14 }}+{{ "-" | rep 14 }}+
| {{ "Issue" | printf "%-14s" }} | {{ "Summary" | printf (printf "%%-%ds" (sub $w 2)) }} | {{ "Type" | printf "%-12s"}} | {{ "Priority" | printf "%-12s" }} | {{ "Status" | printf "%-12s" }} | {{ "Age" | printf "%-10s" }} | {{ "Reporter" | printf "%-12s" }} | {{ "Assignee" | printf "%-12s" }} |
+{{ "-" | rep 16 }}+{{ "-" | rep $w }}+{{ "-" | rep 14 }}+{{ "-" | rep 14 }}+{{ "-" | rep 14 }}+{{ "-" | rep 12 }}+{{ "-" | rep 14 }}+{{ "-" | rep 14 }}+
{{ range .issues -}}
| {{ .key | printf "%-14s"}} | {{ .fields.summary | abbrev (sub $w 2) | printf (printf "%%-%ds" (sub $w 2)) }} | {{.fields.issuetype.name | printf "%-12s" }} | {{if .fields.priority}}{{.fields.priority.name | printf "%-12s" }}{{else}}<unassigned>{{end}} | {{.fields.status.name | printf "%-12s" }} | {{.fields.created | age | printf "%-10s" }} | {{if .fields.reporter}}{{ .fields.reporter.name | printf "%-12s"}}{{else}}<unassigned>{{end}} | {{if .fields.assignee }}{{.fields.assignee.name | printf "%-12s" }}{{else}}<unassigned>{{end}} |
{{ end -}}
+{{ "-" | rep 16 }}+{{ "-" | rep $w }}+{{ "-" | rep 14 }}+{{ "-" | rep 14 }}+{{ "-" | rep 14 }}+{{ "-" | rep 12 }}+{{ "-" | rep 14 }}+{{ "-" | rep 14 }}+
`
const defaultAttachListTemplate = `{{/* table template */ -}}
+{{ "-" | rep 12 }}+{{ "-" | rep 30 }}+{{ "-" | rep 12 }}+{{ "-" | rep 14 }}+{{ "-" | rep 14 }}+
| {{printf "%-10s" "id"}} | {{printf "%-28s" "filename"}} | {{printf "%-10s" "bytes"}} | {{printf "%-12s" "user"}} | {{printf "%-12s" "created"}} |
+{{ "-" | rep 12 }}+{{ "-" | rep 30 }}+{{ "-" | rep 12 }}+{{ "-" | rep 14 }}+{{ "-" | rep 14 }}+
{{range . -}}
| {{.id | printf "%10d" }} | {{.filename | printf "%-28s"}} | {{.size | printf "%10d"}} | {{.author.name | printf "%-12s"}} | {{.created | age | printf "%-12s"}} |
{{end -}}
+{{ "-" | rep 12 }}+{{ "-" | rep 30 }}+{{ "-" | rep 12 }}+{{ "-" | rep 14 }}+{{ "-" | rep 14 }}+
`
const defaultViewTemplate = `{{/* view template */ -}}
issue: {{ .key }}
{{if .fields.created -}}
created: {{ .fields.created | age }} ago
{{end -}}
{{if .fields.status -}}
status: {{ .fields.status.name }}
{{end -}}
summary: {{ .fields.summary }}
project: {{ .fields.project.key }}
{{if .fields.components -}}
components: {{ range .fields.components }}{{ .name }} {{end}}
{{end -}}
{{if .fields.issuetype -}}
issuetype: {{ .fields.issuetype.name }}
{{end -}}
{{if .fields.assignee -}}
assignee: {{ .fields.assignee.name }}
{{end -}}
reporter: {{ if .fields.reporter }}{{ .fields.reporter.name }}{{end}}
{{if .fields.customfield_10110 -}}
watchers: {{ range .fields.customfield_10110 }}{{ .name }} {{end}}
{{end -}}
{{if .fields.issuelinks -}}
blockers: {{ range .fields.issuelinks }}{{if .outwardIssue}}{{ .outwardIssue.key }}[{{.outwardIssue.fields.status.name}}]{{end}}{{end}}
depends: {{ range .fields.issuelinks }}{{if .inwardIssue}}{{ .inwardIssue.key }}[{{.inwardIssue.fields.status.name}}]{{end}}{{end}}
{{end -}}
{{if .fields.priority -}}
priority: {{ .fields.priority.name }}
{{end -}}
{{if .fields.votes -}}
votes: {{ .fields.votes.votes}}
{{end -}}
{{if .fields.labels -}}
labels: {{ join ", " .fields.labels }}
{{end -}}
description: |
{{ or .fields.description "" | indent 2 }}
{{if .fields.comment.comments}}
comments:
{{ range .fields.comment.comments }} - | # {{.author.name}}, {{.created | age}} ago
{{ or .body "" | indent 4}}
{{end}}
{{end -}}
`
const defaultEditTemplate = `{{/* edit template */ -}}
# issue: {{ .key }} - created: {{ .fields.created | age}} ago
update:
comment:
- add:
body: |~
{{ or .overrides.comment "" | indent 10 }}
fields:
summary: >-
{{ or .overrides.summary .fields.summary }}
{{- if and .meta.fields.components .meta.fields.components.allowedValues }}
components: # Values: {{ range .meta.fields.components.allowedValues }}{{.name}}, {{end}}{{if .overrides.components }}{{ range (split "," .overrides.components)}}
- name: {{.}}{{end}}{{else}}{{ range .fields.components }}
- name: {{ .name }}{{end}}{{end}}{{end}}
{{- if .meta.fields.assignee}}
assignee:
name: {{ if .overrides.assignee }}{{.overrides.assignee}}{{else}}{{if .fields.assignee }}{{ .fields.assignee.name }}{{end}}{{end}}{{end}}
{{- if .meta.fields.reporter}}
reporter:
name: {{ if .overrides.reporter }}{{ .overrides.reporter }}{{else if .fields.reporter}}{{ .fields.reporter.name }}{{end}}{{end}}
{{- if .meta.fields.customfield_10110}}
# watchers
customfield_10110: {{ range .fields.customfield_10110 }}
- name: {{ .name }}{{end}}{{if .overrides.watcher}}
- name: {{ .overrides.watcher}}{{end}}{{end}}
{{- if .meta.fields.priority }}
priority: # Values: {{ range .meta.fields.priority.allowedValues }}{{.name}}, {{end}}
name: {{ or .overrides.priority .fields.priority.name "" }}{{end}}
description: |~
{{ or .overrides.description .fields.description "" | indent 4 }}
# votes: {{ .fields.votes.votes }}
# comments:
# {{ range .fields.comment.comments }} - | # {{.author.name}}, {{.created | age}} ago
# {{ or .body "" | indent 4 | comment}}
# {{end}}
`
const defaultTransitionsTemplate = `{{ range .transitions }}{{.id }}: {{.name}}
{{end}}`
const defaultComponentsTemplate = `{{ range . }}{{.id }}: {{.name}}
{{end}}`
const defaultComponentAddTemplate = `{{/* compoinent add template */ -}}
project: {{or .project ""}}
name: {{or .name ""}}
description: {{or .description ""}}
leadUserName: {{or .leadUserName ""}}
`
const defaultIssuetypesTemplate = `{{/* issuetypes template */ -}}
{{ range .issuetypes }}{{color "+bh"}}{{.name | append ":" | printf "%-13s" }}{{color "reset"}} {{.description}}
{{end}}`
const defaultCreateTemplate = `{{/* create template */ -}}
fields:
project:
key: {{ or .overrides.project "" }}
issuetype:
name: {{ or .overrides.issuetype "" }}
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}}`
const defaultEpicCreateTemplate = `{{/* epic create template */ -}}
fields:
project:
key: {{ or .overrides.project "" }}
# Epic Name
customfield_10120: {{ or (index .overrides "epic-name") "" }}
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: Epic`
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 }}
`
const defaultTransitionTemplate = `{{/* transition template */ -}}
{{- if .meta.fields.comment }}
update:
comment:
- add:
body: |~
{{ or .overrides.comment "" | indent 10 }}
{{- end -}}
fields:
{{- if .meta.fields.assignee}}
assignee:
name: {{if .overrides.assignee}}{{.overrides.assignee}}{{else}}{{if .fields.assignee}}{{.fields.assignee.name}}{{end}}{{end}}
{{- end -}}
{{if .meta.fields.components}}
components: # Values: {{ range .meta.fields.components.allowedValues }}{{.name}}, {{end}}{{if .overrides.components }}{{ range (split "," .overrides.components)}}
- name: {{.}}{{end}}{{else}}{{ range .fields.components }}
- name: {{ .name }}{{end}}{{end}}
{{- end -}}
{{if .meta.fields.description}}
description: |~
{{ or .fields.description "" | indent 4 }}
{{- end -}}
{{if .meta.fields.fixVersions -}}
{{if .meta.fields.fixVersions.allowedValues}}
fixVersions: # Values: {{ range .meta.fields.fixVersions.allowedValues }}{{.name}}, {{end}}{{if .overrides.fixVersions}}{{ range (split "," .overrides.fixVersions)}}
- name: {{.}}{{end}}{{else}}{{range .fields.fixVersions}}
- name: {{.name}}{{end}}{{end}}
{{- end -}}
{{- end -}}
{{if .meta.fields.issuetype}}
issuetype: # Values: {{ range .meta.fields.issuetype.allowedValues }}{{.name}}, {{end}}
name: {{if .overrides.issuetype}}{{.overrides.issuetype}}{{else}}{{if .fields.issuetype}}{{.fields.issuetype.name}}{{end}}{{end}}
{{- end -}}
{{if .meta.fields.labels}}
labels: {{range .fields.labels}}
- {{.}}{{end}}{{if .overrides.labels}}{{range (split "," .overrides.labels)}}
- {{.}}{{end}}{{end}}
{{- end -}}
{{if .meta.fields.priority}}
priority: # Values: {{ range .meta.fields.priority.allowedValues }}{{.name}}, {{end}}
name: {{ or .overrides.priority "unassigned" }}
{{- end -}}
{{if .meta.fields.reporter}}
reporter:
name: {{if .overrides.reporter}}{{.overrides.reporter}}{{else}}{{if .fields.reporter}}{{.fields.reporter.name}}{{end}}{{end}}
{{- end -}}
{{if .meta.fields.resolution}}
resolution: # Values: {{ range .meta.fields.resolution.allowedValues }}{{.name}}, {{end}}
name: {{if .overrides.resolution}}{{.overrides.resolution}}{{else if .fields.resolution}}{{.fields.resolution.name}}{{else}}{{or .overrides.defaultResolution "Fixed"}}{{end}}
{{- end -}}
{{if .meta.fields.summary}}
summary: >-
{{or .overrides.summary .fields.summary}}
{{- end -}}
{{if .meta.fields.versions.allowedValues}}
versions: # Values: {{ range .meta.fields.versions.allowedValues }}{{.name}}, {{end}}{{if .overrides.versions}}{{ range (split "," .overrides.versions)}}
- name: {{.}}{{end}}{{else}}{{range .fields.versions}}
- name: {{.}}{{end}}{{end}}
{{- end}}
transition:
id: {{ .transition.id }}
name: {{ .transition.name }}
`
const defaultWorklogTemplate = `{{/* worklog template */ -}}
# issue: {{ .issue }}
comment: |~
{{ or .comment "" | indent 2 }}
timeSpent: {{ or .timeSpent "" }}
started: {{ or .started "" }}
`
const defaultWorklogsTemplate = `{{/* worklogs template */ -}}
{{ range .worklogs }}- # {{.author.name}}, {{.created | age}} ago
comment: {{ or .comment "" }}
started: {{ .started }}
timeSpent: {{ .timeSpent }}
{{end}}`
-41
View File
@@ -1,41 +0,0 @@
package jiracli
import (
"fmt"
"net"
"net/http"
"os"
"time"
)
type transport struct {
shadow *http.Transport
}
func newUnixProxyTransport(path string) *transport {
dial := func(network, addr string) (net.Conn, error) {
return net.Dial("unix", path)
}
shadow := &http.Transport{
Dial: dial,
DialTLS: dial,
DisableKeepAlives: true,
ResponseHeaderTimeout: 30 * time.Second,
ExpectContinueTimeout: 10 * time.Second,
}
return &transport{shadow}
}
func unixProxy(path string) *transport {
return newUnixProxyTransport(os.ExpandEnv(path))
}
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
req2 := *req
url2 := *req.URL
req2.URL = &url2
req2.URL.Opaque = fmt.Sprintf("//%s%s", req.URL.Host, req.URL.EscapedPath())
return t.shadow.RoundTrip(&req2)
}
-198
View File
@@ -1,198 +0,0 @@
package jiracli
import (
"fmt"
"os"
"os/exec"
"regexp"
"runtime"
"syscall"
"github.com/coryb/figtree"
"github.com/coryb/kingpeon"
"github.com/coryb/oreo"
jira "github.com/go-jira/jira"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
var usage = `{{define "FormatCommand"}}\
{{if .FlagSummary}} {{.FlagSummary}}{{end}}\
{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\
{{end}}\
{{define "FormatBriefCommands"}}\
{{range .FlattenedCommands}}\
{{if not .Hidden}}\
{{ print .FullCommand ":" | printf "%-20s"}} {{.Help}}
{{end}}\
{{end}}\
{{end}}\
{{define "FormatCommands"}}\
{{range .FlattenedCommands}}\
{{if not .Hidden}}\
{{.FullCommand}}{{if .Default}}*{{end}}{{template "FormatCommand" .}}
{{.Help|Wrap 4}}
{{with .Flags|FlagsToTwoColumns}}{{FormatTwoColumnsWithIndent . 4 2}}{{end}}
{{end}}\
{{end}}\
{{end}}\
{{define "FormatUsage"}}\
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}
{{if .Help}}
{{.Help|Wrap 0}}\
{{end}}\
{{end}}\
{{if .Context.SelectedCommand}}\
usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatCommand" .Context.SelectedCommand}}
{{if .Context.SelectedCommand.Aliases }}\
{{range $top := .App.Commands}}\
{{if eq $top.FullCommand $.Context.SelectedCommand.FullCommand}}\
{{range $alias := $.Context.SelectedCommand.Aliases}}\
alias: {{$.App.Name}} {{$alias}}{{template "FormatCommand" $.Context.SelectedCommand}}
{{end}}\
{{else}}\
{{range $sub := $top.Commands}}\
{{if eq $sub.FullCommand $.Context.SelectedCommand.FullCommand}}\
{{range $alias := $.Context.SelectedCommand.Aliases}}\
alias: {{$.App.Name}} {{$top.Name}} {{$alias}}{{template "FormatCommand" $.Context.SelectedCommand}}
{{end}}\
{{end}}\
{{end}}\
{{end}}\
{{end}}\
{{end}}
{{if .Context.SelectedCommand.Help}}\
{{.Context.SelectedCommand.Help|Wrap 0}}
{{end}}\
{{else}}\
usage: {{.App.Name}}{{template "FormatUsage" .App}}
{{end}}\
{{if .App.Flags}}\
Global flags:
{{.App.Flags|FlagsToTwoColumns|FormatTwoColumns}}
{{end}}\
{{if .Context.SelectedCommand}}\
{{if and .Context.SelectedCommand.Flags|RequiredFlags}}\
Required flags:
{{.Context.SelectedCommand.Flags|RequiredFlags|FlagsToTwoColumns|FormatTwoColumns}}
{{end}}\
{{if .Context.SelectedCommand.Flags|OptionalFlags}}\
Optional flags:
{{.Context.SelectedCommand.Flags|OptionalFlags|FlagsToTwoColumns|FormatTwoColumns}}
{{end}}\
{{end}}\
{{if .Context.Args}}\
Args:
{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}
{{end}}\
{{if .Context.SelectedCommand}}\
{{if .Context.SelectedCommand.Commands}}\
Subcommands:
{{template "FormatCommands" .Context.SelectedCommand}}
{{end}}\
{{else if .App.Commands}}\
Commands:
{{template "FormatBriefCommands" .App}}
{{end}}\
`
func CommandLine(fig *figtree.FigTree, o *oreo.Client) *kingpin.Application {
app := kingpin.New("jira", "Jira Command Line Interface")
app.UsageWriter(os.Stdout)
app.ErrorWriter(os.Stderr)
app.Command("version", "Prints version").PreAction(func(*kingpin.ParseContext) error {
fmt.Println(jira.VERSION)
panic(Exit{Code: 0})
})
app.UsageTemplate(usage)
var verbosity int
app.Flag("verbose", "Increase verbosity for debugging").Short('v').PreAction(func(_ *kingpin.ParseContext) error {
os.Setenv("JIRA_DEBUG", fmt.Sprintf("%d", verbosity))
IncreaseLogLevel(1)
return nil
}).CounterVar(&verbosity)
app.Terminate(func(status int) {
for _, arg := range os.Args {
if arg == "-h" || arg == "--help" || len(os.Args) == 1 {
panic(Exit{Code: 0})
}
}
panic(Exit{Code: 1})
})
register(app, o, fig)
// register custom commands
data := struct {
CustomCommands kingpeon.DynamicCommands `yaml:"custom-commands" json:"custom-commands"`
}{}
if err := fig.LoadAllConfigs("config.yml", &data); err != nil {
log.Errorf("%s", err)
panic(Exit{Code: 1})
}
if len(data.CustomCommands) > 0 {
runner := syscall.Exec
if runtime.GOOS == "windows" {
runner = func(binary string, cmd []string, env []string) error {
command := exec.Command(binary, cmd[1:]...)
command.Stdin = os.Stdin
command.Stdout = os.Stdout
command.Stderr = os.Stderr
command.Env = env
return command.Run()
}
}
tmp := map[string]interface{}{}
fig.LoadAllConfigs("config.yml", &tmp)
kingpeon.RegisterDynamicCommandsWithRunner(runner, app, data.CustomCommands, TemplateProcessor())
}
return app
}
func ParseCommandLine(app *kingpin.Application, args []string) {
// checking for default usage of `jira ISSUE-123` but need to allow
// for global options first like: `jira --user mothra ISSUE-123`
ctx, err := app.ParseContext(args)
if err != nil && ctx == nil {
// This is an internal kingpin usage error, duplicate options/commands
log.Fatalf("error: %s, ctx: %v", err, ctx)
}
if ctx != nil {
if ctx.SelectedCommand == nil {
next := ctx.Next()
if next != nil {
if ok, err := regexp.MatchString("^[A-Z]+-[0-9]+$", next.Value); err != nil {
log.Errorf("Invalid Regex: %s", err)
} else if ok {
// insert "view" at i=1 (2nd position)
os.Args = append(os.Args[:1], append([]string{"view"}, os.Args[1:]...)...)
}
}
}
}
if _, err := app.Parse(os.Args[1:]); err != nil {
if _, ok := err.(*Error); ok {
log.Errorf("%s", err)
panic(Exit{Code: 1})
}
ctx, _ := app.ParseContext(os.Args[1:])
if ctx != nil {
app.UsageForContext(ctx)
}
log.Errorf("Invalid Usage: %s", err)
panic(Exit{Code: 1})
}
}
-180
View File
@@ -1,180 +0,0 @@
package jiracli
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"time"
kingpin "gopkg.in/alecthomas/kingpin.v2"
"github.com/coryb/figtree"
)
func Homedir() string {
if runtime.GOOS == "windows" {
return os.Getenv("USERPROFILE")
}
return os.Getenv("HOME")
}
func findClosestParentPath(fileName string) (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", err
}
paths := figtree.FindParentPaths(Homedir(), cwd, fileName)
if len(paths) > 0 {
return paths[len(paths)-1], nil
}
return "", fmt.Errorf("%s not found in parent directory hierarchy", fileName)
}
func tmpYml(tmpFilePrefix string) (*os.File, error) {
fh, err := ioutil.TempFile("", filepath.Base(tmpFilePrefix))
if err != nil {
return nil, err
}
// now we need to rename the file since we dont control the file extensions
// ... it has to be `.yml` so that vim/emacs etc know what edit mode to apply
// for easier editing
oldFileName := fh.Name()
newFileName := oldFileName + ".yml"
// close tmpfile so we can rename on windows
fh.Close()
if err := os.Rename(oldFileName, newFileName); err != nil {
return nil, err
}
return os.OpenFile(newFileName, os.O_RDWR|os.O_EXCL, 0600)
}
func FlagValue(ctx *kingpin.ParseContext, name string) string {
for _, elem := range ctx.Elements {
if flag, ok := elem.Clause.(*kingpin.FlagClause); ok {
if flag.Model().Name == name {
return *elem.Value
}
}
}
return ""
}
func copyFile(src, dst string) (err error) {
var s, d *os.File
if s, err = os.Open(src); err == nil {
defer s.Close()
if d, err = os.Create(dst); err == nil {
if _, err = io.Copy(d, s); err != nil {
d.Close()
return
}
return d.Close()
}
}
return
}
func fuzzyAge(start string) (string, error) {
t, err := time.Parse("2006-01-02T15:04:05.000-0700", start)
if err != nil {
return "", err
}
delta := time.Since(t)
if delta.Minutes() < 2 {
return "a minute", nil
} else if dm := delta.Minutes(); dm < 45 {
return fmt.Sprintf("%d minutes", int(dm)), nil
} else if dm := delta.Minutes(); dm < 90 {
return "an hour", nil
} else if dh := delta.Hours(); dh < 24 {
return fmt.Sprintf("%d hours", int(dh)), nil
} else if dh := delta.Hours(); dh < 48 {
return "a day", nil
}
return fmt.Sprintf("%d days", int(delta.Hours()/24)), nil
}
func dateFormat(format string, content string) (string, error) {
t, err := time.Parse("2006-01-02T15:04:05.000-0700", content)
if err != nil {
return "", err
}
return t.Format(format), nil
}
// this is a HACK to make yaml parsed documents to be serializable
// to json, so prevent this:
// json: unsupported type: map[interface {}]interface {}
// Also we want to clean up common input errors for the edit
// templates, like dangling "\n"
func yamlFixup(data interface{}) (interface{}, error) {
switch d := data.(type) {
case map[interface{}]interface{}:
// need to copy this map into a string map so json can encode it
copy := make(map[string]interface{})
for key, val := range d {
switch k := key.(type) {
case string:
if fixed, err := yamlFixup(val); err != nil {
return nil, err
} else if fixed != nil {
copy[k] = fixed
}
default:
err := fmt.Errorf("YAML: key %s is type '%T', require 'string'", key, k)
log.Errorf("%s", err)
return nil, err
}
}
if len(copy) == 0 {
return nil, nil
}
return copy, nil
case map[string]interface{}:
copy := make(map[string]interface{})
for k, v := range d {
if fixed, err := yamlFixup(v); err != nil {
return nil, err
} else if fixed != nil {
copy[k] = fixed
}
}
if len(copy) == 0 {
return nil, nil
}
return copy, nil
case []interface{}:
copy := make([]interface{}, 0, len(d))
for _, val := range d {
if fixed, err := yamlFixup(val); err != nil {
return nil, err
} else if fixed != nil {
copy = append(copy, fixed)
}
}
if len(copy) == 0 {
return nil, nil
}
return copy, nil
case *interface{}:
if fixed, err := yamlFixup(*d); err != nil {
return nil, err
} else if fixed != nil {
*d = fixed
}
return d, nil
case string:
if d == "" || d == "\n" {
return nil, nil
}
return d, nil
default:
return d, nil
}
}
-71
View File
@@ -1,71 +0,0 @@
package jiracli
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func realPath(path string) string {
cpath, err := filepath.EvalSymlinks(path)
if err != nil {
log.Fatal(err)
}
return cpath
}
func comparePaths(p1 string, p2 string) bool {
if realPath(p1) == realPath(p2) {
return true
}
return false
}
func TestFindClosestParentPath(t *testing.T) {
dir, err := ioutil.TempDir("", "testFindParentPath")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
origDir, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
defer os.Chdir(origDir)
t1 := filepath.Join(dir, "/.test1")
t2 := filepath.Join(t1, "/.test2")
t3 := filepath.Join(t2, "/.test1")
err = os.MkdirAll(t3, os.ModePerm)
if err != nil {
log.Fatal(err)
}
os.Chdir(t3)
path1, err := findClosestParentPath(".test1")
if err != nil {
t.Errorf("findClosestParentPath should not have errored: %s", err)
}
if ok := comparePaths(path1, t3); !ok {
t.Errorf("%s != %s", path1, t3)
}
path2, err := findClosestParentPath(".test2")
if err != nil {
t.Errorf("findClosestParentPath should not have errored: %s", err)
}
if ok := comparePaths(path2, t2); !ok {
t.Errorf("%s != %s", path2, t2)
}
path3, err := findClosestParentPath(".test3")
if err.Error() != ".test3 not found in parent directory hierarchy" {
t.Errorf("incorrect error from findClosestParentPath: %s", err)
}
if path3 != "" {
t.Errorf("path3 should be empty, but is not: %s", path3)
}
}
-63
View File
@@ -1,63 +0,0 @@
package jiracmd
import (
"fmt"
"github.com/coryb/figtree"
"github.com/coryb/oreo"
"github.com/go-jira/jira"
"github.com/go-jira/jira/jiracli"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
type AssignOptions struct {
jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"`
Issue string `yaml:"issue,omitempty" json:"issue,omitempty"`
Assignee string `yaml:"assignee,omitempty" json:"assignee,omitempty"`
}
func CmdAssignRegistry() *jiracli.CommandRegistryEntry {
opts := AssignOptions{}
return &jiracli.CommandRegistryEntry{
"Assign user to issue",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
return CmdAssignUsage(cmd, &opts)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdAssign(o, globals, &opts)
},
}
}
func CmdAssignUsage(cmd *kingpin.CmdClause, opts *AssignOptions) error {
jiracli.BrowseUsage(cmd, &opts.CommonOptions)
cmd.Flag("default", "use default user for assignee").PreAction(func(ctx *kingpin.ParseContext) error {
if jiracli.FlagValue(ctx, "default") == "true" {
opts.Assignee = "-1"
}
return nil
}).Bool()
cmd.Arg("ISSUE", "issue to assign").Required().StringVar(&opts.Issue)
cmd.Arg("ASSIGNEE", "user to assign to issue").StringVar(&opts.Assignee)
return nil
}
// CmdAssign will assign an issue to a user
func CmdAssign(o *oreo.Client, globals *jiracli.GlobalOptions, opts *AssignOptions) error {
err := jira.IssueAssign(o, globals.Endpoint.Value, opts.Issue, opts.Assignee)
if err != nil {
return err
}
if !globals.Quiet.Value {
fmt.Printf("OK %s %s\n", opts.Issue, jira.URLJoin(globals.Endpoint.Value, "browse", opts.Issue))
}
if opts.Browse.Value {
return CmdBrowse(globals, opts.Issue)
}
return nil
}
-99
View File
@@ -1,99 +0,0 @@
package jiracmd
import (
"fmt"
"os"
"sort"
"golang.org/x/crypto/ssh/terminal"
"github.com/coryb/figtree"
"github.com/coryb/oreo"
jira "github.com/go-jira/jira"
"github.com/go-jira/jira/jiracli"
kingpin "gopkg.in/alecthomas/kingpin.v2"
yaml "gopkg.in/coryb/yaml.v2"
)
type AttachCreateOptions struct {
jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"`
Issue string `yaml:"issue,omitempty" json:"issue,omitempty"`
Attachment string `yaml:"attachment,omitempty" json:"attachment,omitempty"`
Filename string `yaml:"filename,omitempty" json:"filename,omitempty"`
SaveFile string `yaml:"savefile,omitempty" json:"savefile,omitempty"`
}
func CmdAttachCreateRegistry() *jiracli.CommandRegistryEntry {
opts := AttachCreateOptions{}
return &jiracli.CommandRegistryEntry{
"Attach file to issue",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
return CmdAttachCreateUsage(cmd, &opts)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdAttachCreate(o, globals, &opts)
},
}
}
func CmdAttachCreateUsage(cmd *kingpin.CmdClause, opts *AttachCreateOptions) error {
jiracli.BrowseUsage(cmd, &opts.CommonOptions)
cmd.Flag("saveFile", "Write attachment information as yaml to file").StringVar(&opts.SaveFile)
cmd.Flag("filename", "Filename to use for attachment").Short('f').StringVar(&opts.Filename)
cmd.Arg("ISSUE", "issue to assign").Required().StringVar(&opts.Issue)
cmd.Arg("ATTACHMENT", "File to attach to issue, if not provided read from stdin").StringVar(&opts.Attachment)
return nil
}
func CmdAttachCreate(o *oreo.Client, globals *jiracli.GlobalOptions, opts *AttachCreateOptions) error {
var contents *os.File
var err error
if opts.Attachment == "" {
if terminal.IsTerminal(int(os.Stdin.Fd())) {
return fmt.Errorf("ATTACHMENT argument required or redirect from STDIN")
}
contents = os.Stdin
if opts.Filename == "" {
return fmt.Errorf("--filename required when reading from stdin")
}
} else {
contents, err = os.Open(opts.Attachment)
if err != nil {
return err
}
if opts.Filename == "" {
opts.Filename = opts.Attachment
}
}
attachments, err := jira.IssueAttachFile(o, globals.Endpoint.Value, opts.Issue, opts.Filename, contents)
if err != nil {
return err
}
sort.Sort(sort.Reverse(attachments))
if opts.SaveFile != "" {
fh, err := os.Create(opts.SaveFile)
if err != nil {
return err
}
defer fh.Close()
out, err := yaml.Marshal((*attachments)[0])
if err != nil {
return err
}
fh.Write(out)
}
if !globals.Quiet.Value {
fmt.Printf("OK %d %s\n", (*attachments)[0].ID, (*attachments)[0].Content)
}
if opts.Browse.Value {
return CmdBrowse(globals, opts.Issue)
}
return nil
}
-79
View File
@@ -1,79 +0,0 @@
package jiracmd
import (
"fmt"
"io"
"os"
"github.com/coryb/figtree"
"github.com/coryb/oreo"
jira "github.com/go-jira/jira"
"github.com/go-jira/jira/jiracli"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
type AttachGetOptions struct {
AttachmentID string `yaml:"attachment-id,omitempty" json:"attachment-id,omitempty"`
OutputFile string `yaml:"output-file,omitempty" json:"output-file,omitempty"`
}
func CmdAttachGetRegistry() *jiracli.CommandRegistryEntry {
opts := AttachGetOptions{}
return &jiracli.CommandRegistryEntry{
"Fetch attachment",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
return CmdAttachGetUsage(cmd, &opts)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdAttachGet(o, globals, &opts)
},
}
}
func CmdAttachGetUsage(cmd *kingpin.CmdClause, opts *AttachGetOptions) error {
cmd.Flag("output", "Write attachment to specified file name, '-' for stdout").Short('o').StringVar(&opts.OutputFile)
cmd.Arg("ATTACHMENT-ID", "Attachment id to fetch").StringVar(&opts.AttachmentID)
return nil
}
func CmdAttachGet(o *oreo.Client, globals *jiracli.GlobalOptions, opts *AttachGetOptions) error {
attachment, err := jira.GetAttachment(o, globals.Endpoint.Value, opts.AttachmentID)
if err != nil {
return err
}
resp, err := o.Get(attachment.Content)
if err != nil {
return err
}
defer resp.Body.Close()
var output *os.File
if opts.OutputFile == "-" {
output = os.Stdout
} else if opts.OutputFile != "" {
output, err = os.Create(opts.OutputFile)
if err != nil {
return err
}
defer output.Close()
} else {
output, err = os.Create(attachment.Filename)
if err != nil {
return err
}
defer output.Close()
}
_, err = io.Copy(output, resp.Body)
if err != nil {
return err
}
output.Close()
if opts.OutputFile != "-" && !globals.Quiet.Value {
fmt.Printf("OK Wrote %s\n", output.Name())
}
return nil
}
-67
View File
@@ -1,67 +0,0 @@
package jiracmd
import (
"sort"
"github.com/coryb/figtree"
"github.com/coryb/oreo"
"github.com/go-jira/jira"
"github.com/go-jira/jira/jiracli"
"github.com/go-jira/jira/jiradata"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
type AttachListOptions struct {
jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"`
Issue string `yaml:"issue,omitempty" json:"issue,omitempty"`
}
func CmdAttachListRegistry() *jiracli.CommandRegistryEntry {
opts := AttachListOptions{
CommonOptions: jiracli.CommonOptions{
Template: figtree.NewStringOption("attach-list"),
},
}
return &jiracli.CommandRegistryEntry{
"Prints attachment details for issue",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
return CmdAttachListUsage(cmd, &opts)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdAttachList(o, globals, &opts)
},
}
}
func CmdAttachListUsage(cmd *kingpin.CmdClause, opts *AttachListOptions) error {
jiracli.BrowseUsage(cmd, &opts.CommonOptions)
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
cmd.Arg("ISSUE", "Issue id to lookup attachments").Required().StringVar(&opts.Issue)
return nil
}
func CmdAttachList(o *oreo.Client, globals *jiracli.GlobalOptions, opts *AttachListOptions) error {
data, err := jira.GetIssue(o, globals.Endpoint.Value, opts.Issue, nil)
if err != nil {
return err
}
// need to conver the interface{} "attachment" field to an actual
// ListOfAttachment object so we can sort it
var attachments jiradata.ListOfAttachment
err = jiracli.ConvertType(data.Fields["attachment"], &attachments)
if err != nil {
return err
}
sort.Sort(&attachments)
if err := opts.PrintTemplate(attachments); err != nil {
return err
}
if opts.Browse.Value {
return CmdBrowse(globals, opts.Issue)
}
return nil
}
-46
View File
@@ -1,46 +0,0 @@
package jiracmd
import (
"fmt"
"github.com/coryb/figtree"
"github.com/coryb/oreo"
jira "github.com/go-jira/jira"
"github.com/go-jira/jira/jiracli"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
type AttachRemoveOptions struct {
AttachmentID string `yaml:"attachment-id,omitempty" json:"attachment-id,omitempty"`
}
func CmdAttachRemoveRegistry() *jiracli.CommandRegistryEntry {
opts := AttachRemoveOptions{}
return &jiracli.CommandRegistryEntry{
"Delete attachment",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
return CmdAttachRemoveUsage(cmd, &opts)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdAttachRemove(o, globals, &opts)
},
}
}
func CmdAttachRemoveUsage(cmd *kingpin.CmdClause, opts *AttachRemoveOptions) error {
cmd.Arg("ATTACHMENT-ID", "Attachment id to fetch").StringVar(&opts.AttachmentID)
return nil
}
func CmdAttachRemove(o *oreo.Client, globals *jiracli.GlobalOptions, opts *AttachRemoveOptions) error {
if err := jira.RemoveAttachment(o, globals.Endpoint.Value, opts.AttachmentID); err != nil {
return err
}
if !globals.Quiet.Value {
fmt.Printf("OK Deleted Attachment %s\n", opts.AttachmentID)
}
return nil
}
-80
View File
@@ -1,80 +0,0 @@
package jiracmd
import (
"fmt"
"github.com/coryb/figtree"
"github.com/coryb/oreo"
"github.com/go-jira/jira"
"github.com/go-jira/jira/jiracli"
"github.com/go-jira/jira/jiradata"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
type BlockOptions struct {
jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"`
jiradata.LinkIssueRequest `yaml:",inline" json:",inline" figtree:",inline"`
}
func CmdBlockRegistry() *jiracli.CommandRegistryEntry {
opts := BlockOptions{
CommonOptions: jiracli.CommonOptions{
Template: figtree.NewStringOption("edit"),
},
LinkIssueRequest: jiradata.LinkIssueRequest{
Type: &jiradata.IssueLinkType{
Name: "Blocks",
},
InwardIssue: &jiradata.IssueRef{},
OutwardIssue: &jiradata.IssueRef{},
},
}
return &jiracli.CommandRegistryEntry{
"Mark issues as blocker",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
return CmdBlockUsage(cmd, &opts)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdBlock(o, globals, &opts)
},
}
}
func CmdBlockUsage(cmd *kingpin.CmdClause, opts *BlockOptions) error {
jiracli.BrowseUsage(cmd, &opts.CommonOptions)
jiracli.EditorUsage(cmd, &opts.CommonOptions)
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
cmd.Flag("comment", "Comment message when marking issue as blocker").Short('m').PreAction(func(ctx *kingpin.ParseContext) error {
opts.Comment = &jiradata.Comment{
Body: jiracli.FlagValue(ctx, "comment"),
}
return nil
}).String()
cmd.Arg("BLOCKER", "blocker issue").Required().StringVar(&opts.OutwardIssue.Key)
cmd.Arg("ISSUE", "issue that is blocked").Required().StringVar(&opts.InwardIssue.Key)
return nil
}
// CmdBlock will update the given issue as being a duplicate by the given dup issue
// and will attempt to resolve the dup issue
func CmdBlock(o *oreo.Client, globals *jiracli.GlobalOptions, opts *BlockOptions) error {
if err := jira.LinkIssues(o, globals.Endpoint.Value, &opts.LinkIssueRequest); err != nil {
return err
}
if !globals.Quiet.Value {
fmt.Printf("OK %s %s\n", opts.InwardIssue.Key, jira.URLJoin(globals.Endpoint.Value, "browse", opts.InwardIssue.Key))
fmt.Printf("OK %s %s\n", opts.OutwardIssue.Key, jira.URLJoin(globals.Endpoint.Value, "browse", opts.OutwardIssue.Key))
}
if opts.Browse.Value {
if err := CmdBrowse(globals, opts.InwardIssue.Key); err != nil {
return CmdBrowse(globals, opts.OutwardIssue.Key)
}
}
return nil
}
-30
View File
@@ -1,30 +0,0 @@
package jiracmd
import (
"github.com/coryb/figtree"
"github.com/coryb/oreo"
jira "github.com/go-jira/jira"
"github.com/go-jira/jira/jiracli"
"github.com/pkg/browser"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
func CmdBrowseRegistry() *jiracli.CommandRegistryEntry {
issue := ""
return &jiracli.CommandRegistryEntry{
"Open issue in browser",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
cmd.Arg("ISSUE", "Issue to browse to").Required().StringVar(&issue)
return nil
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdBrowse(globals, issue)
},
}
}
// CmdBrowse open the default system browser to the provided issue
func CmdBrowse(globals *jiracli.GlobalOptions, issue string) error {
return browser.OpenURL(jira.URLJoin(globals.Endpoint.Value, "browse", issue))
}
-79
View File
@@ -1,79 +0,0 @@
package jiracmd
import (
"fmt"
"github.com/coryb/figtree"
"github.com/coryb/oreo"
"github.com/go-jira/jira"
"github.com/go-jira/jira/jiracli"
"github.com/go-jira/jira/jiradata"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
type CommentOptions struct {
jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"`
Overrides map[string]string `yaml:"overrides,omitempty" json:"overrides,omitempty"`
Issue string `yaml:"issue,omitempty" json:"issue,omitempty"`
}
func CmdCommentRegistry() *jiracli.CommandRegistryEntry {
opts := CommentOptions{
CommonOptions: jiracli.CommonOptions{
Template: figtree.NewStringOption("comment"),
},
Overrides: map[string]string{},
}
return &jiracli.CommandRegistryEntry{
"Add comment to issue",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
return CmdCommentUsage(cmd, &opts)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdComment(o, globals, &opts)
},
}
}
func CmdCommentUsage(cmd *kingpin.CmdClause, opts *CommentOptions) error {
jiracli.BrowseUsage(cmd, &opts.CommonOptions)
jiracli.EditorUsage(cmd, &opts.CommonOptions)
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
cmd.Flag("noedit", "Disable opening the editor").SetValue(&opts.SkipEditing)
cmd.Flag("comment", "Comment message for issue").Short('m').PreAction(func(ctx *kingpin.ParseContext) error {
opts.Overrides["comment"] = jiracli.FlagValue(ctx, "comment")
return nil
}).String()
cmd.Arg("ISSUE", "issue id to update").StringVar(&opts.Issue)
return nil
}
// CmdComment will update issue with comment
func CmdComment(o *oreo.Client, globals *jiracli.GlobalOptions, opts *CommentOptions) error {
comment := jiradata.Comment{}
input := struct {
Overrides map[string]string `yaml:"overrides,omitempty" json:"overrides,omitempty"`
}{
opts.Overrides,
}
err := jiracli.EditLoop(&opts.CommonOptions, &input, &comment, func() error {
_, err := jira.IssueAddComment(o, globals.Endpoint.Value, opts.Issue, &comment)
return err
})
if err != nil {
return err
}
if !globals.Quiet.Value {
fmt.Printf("OK %s %s\n", opts.Issue, jira.URLJoin(globals.Endpoint.Value, "browse", opts.Issue))
}
if opts.Browse.Value {
return CmdBrowse(globals, opts.Issue)
}
return nil
}
-67
View File
@@ -1,67 +0,0 @@
package jiracmd
import (
"fmt"
"github.com/coryb/figtree"
"github.com/coryb/oreo"
"github.com/go-jira/jira"
"github.com/go-jira/jira/jiracli"
"github.com/go-jira/jira/jiradata"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
type ComponentAddOptions struct {
jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"`
jiradata.Component `yaml:",inline" json:",inline" figtree:",inline"`
}
func CmdComponentAddRegistry() *jiracli.CommandRegistryEntry {
opts := ComponentAddOptions{
CommonOptions: jiracli.CommonOptions{
Template: figtree.NewStringOption("component-add"),
},
}
return &jiracli.CommandRegistryEntry{
"Add component",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
return CmdComponentAddUsage(cmd, &opts)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdComponentAdd(o, globals, &opts)
},
}
}
func CmdComponentAddUsage(cmd *kingpin.CmdClause, opts *ComponentAddOptions) error {
jiracli.EditorUsage(cmd, &opts.CommonOptions)
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
cmd.Flag("noedit", "Disable opening the editor").SetValue(&opts.SkipEditing)
cmd.Flag("project", "project to create component in").Short('p').StringVar(&opts.Project)
cmd.Flag("name", "name of component").Short('n').StringVar(&opts.Name)
cmd.Flag("description", "description of component").Short('d').StringVar(&opts.Description)
cmd.Flag("lead", "person that acts as lead for component").Short('l').StringVar(&opts.LeadUserName)
return nil
}
// CmdComponentAdd sends the provided overrides to the "component-add" template for editing, then
// will parse the edited document as YAML and submit the document to jira.
func CmdComponentAdd(o *oreo.Client, globals *jiracli.GlobalOptions, opts *ComponentAddOptions) error {
var err error
component := &jiradata.Component{}
err = jiracli.EditLoop(&opts.CommonOptions, &opts.Component, component, func() error {
_, err = jira.CreateComponent(o, globals.Endpoint.Value, component)
return err
})
if err != nil {
return err
}
if !globals.Quiet.Value {
fmt.Printf("OK %s %s\n", component.Project, component.Name)
}
return nil
}
-56
View File
@@ -1,56 +0,0 @@
package jiracmd
import (
"fmt"
"github.com/coryb/figtree"
"github.com/coryb/oreo"
"github.com/go-jira/jira"
"github.com/go-jira/jira/jiracli"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
type ComponentsOptions struct {
jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"`
Project string `yaml:"project,omitempty" json:"project,omitempty"`
}
func CmdComponentsRegistry() *jiracli.CommandRegistryEntry {
opts := ComponentsOptions{
CommonOptions: jiracli.CommonOptions{
Template: figtree.NewStringOption("components"),
},
}
return &jiracli.CommandRegistryEntry{
"Show components for a project",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
return CmdComponentsUsage(cmd, &opts)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdComponents(o, globals, &opts)
},
}
}
func CmdComponentsUsage(cmd *kingpin.CmdClause, opts *ComponentsOptions) error {
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions)
cmd.Flag("project", "project to list components").Short('p').StringVar(&opts.Project)
return nil
}
// CmdComponents will get available components for project and send to the "components" template
func CmdComponents(o *oreo.Client, globals *jiracli.GlobalOptions, opts *ComponentsOptions) error {
if opts.Project == "" {
return fmt.Errorf("Project Required.")
}
data, err := jira.GetProjectComponents(o, globals.Endpoint.Value, opts.Project)
if err != nil {
return err
}
return opts.PrintTemplate(data)
}
-153
View File
@@ -1,153 +0,0 @@
package jiracmd
import (
"fmt"
"os"
"github.com/coryb/figtree"
"github.com/coryb/oreo"
"github.com/go-jira/jira"
"github.com/go-jira/jira/jiracli"
"github.com/go-jira/jira/jiradata"
kingpin "gopkg.in/alecthomas/kingpin.v2"
yaml "gopkg.in/coryb/yaml.v2"
)
type CreateOptions struct {
jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"`
jiradata.IssueUpdate `yaml:",inline" json:",inline" figtree:",inline"`
Project string `yaml:"project,omitempty" json:"project,omitempty"`
IssueType string `yaml:"issuetype,omitempty" json:"issuetype,omitempty"`
Overrides map[string]string `yaml:"overrides,omitempty" json:"overrides,omitempty"`
SaveFile string `yaml:"savefile,omitempty" json:"savefile,omitempty"`
}
func CmdCreateRegistry() *jiracli.CommandRegistryEntry {
opts := CreateOptions{
CommonOptions: jiracli.CommonOptions{
Template: figtree.NewStringOption("create"),
},
Overrides: map[string]string{},
}
return &jiracli.CommandRegistryEntry{
"Create issue",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
return CmdCreateUsage(cmd, &opts)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdCreate(o, globals, &opts)
},
}
}
func CmdCreateUsage(cmd *kingpin.CmdClause, opts *CreateOptions) error {
jiracli.BrowseUsage(cmd, &opts.CommonOptions)
jiracli.EditorUsage(cmd, &opts.CommonOptions)
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
cmd.Flag("noedit", "Disable opening the editor").SetValue(&opts.SkipEditing)
cmd.Flag("project", "project to create issue in").Short('p').StringVar(&opts.Project)
cmd.Flag("issuetype", "issuetype in to create").Short('i').StringVar(&opts.IssueType)
cmd.Flag("comment", "Comment message for issue").Short('m').PreAction(func(ctx *kingpin.ParseContext) error {
opts.Overrides["comment"] = jiracli.FlagValue(ctx, "comment")
return nil
}).String()
cmd.Flag("override", "Set issue property").Short('o').StringMapVar(&opts.Overrides)
cmd.Flag("saveFile", "Write issue as yaml to file").StringVar(&opts.SaveFile)
return nil
}
// CmdCreate sends the create-metadata to the "create" template for editing, then
// will parse the edited document as YAML and submit the document to jira.
func CmdCreate(o *oreo.Client, globals *jiracli.GlobalOptions, opts *CreateOptions) error {
type templateInput struct {
Meta *jiradata.IssueType `yaml:"meta" json:"meta"`
Overrides map[string]string `yaml:"overrides" json:"overrides"`
}
if err := defaultIssueType(o, globals.Endpoint.Value, &opts.Project, &opts.IssueType); err != nil {
return err
}
createMeta, err := jira.GetIssueCreateMetaIssueType(o, globals.Endpoint.Value, opts.Project, opts.IssueType)
if err != nil {
return err
}
issueUpdate := jiradata.IssueUpdate{}
input := templateInput{
Meta: createMeta,
Overrides: opts.Overrides,
}
input.Overrides["project"] = opts.Project
input.Overrides["issuetype"] = opts.IssueType
input.Overrides["user"] = globals.User.Value
var issueResp *jiradata.IssueCreateResponse
err = jiracli.EditLoop(&opts.CommonOptions, &input, &issueUpdate, func() error {
issueResp, err = jira.CreateIssue(o, globals.Endpoint.Value, &issueUpdate)
return err
})
if err != nil {
return err
}
browseLink := jira.URLJoin(globals.Endpoint.Value, "browse", issueResp.Key)
if !globals.Quiet.Value {
fmt.Printf("OK %s %s\n", issueResp.Key, browseLink)
}
if opts.SaveFile != "" {
fh, err := os.Create(opts.SaveFile)
if err != nil {
return err
}
defer fh.Close()
out, err := yaml.Marshal(map[string]string{
"issue": issueResp.Key,
"link": browseLink,
})
if err != nil {
return err
}
fh.Write(out)
}
if opts.Browse.Value {
return CmdBrowse(globals, issueResp.Key)
}
return nil
}
func defaultIssueType(o *oreo.Client, endpoint string, project, issuetype *string) error {
if project == nil || *project == "" {
return fmt.Errorf("Project undefined, please use --project argument or set the `project` config property")
}
if issuetype != nil && *issuetype != "" {
return nil
}
projectMeta, err := jira.GetIssueCreateMetaProject(o, endpoint, *project)
if err != nil {
return err
}
issueTypes := map[string]bool{}
for _, issuetype := range projectMeta.IssueTypes {
issueTypes[issuetype.Name] = true
}
// prefer "Bug" type
if _, ok := issueTypes["Bug"]; ok {
*issuetype = "Bug"
return nil
}
// next best default it "Task"
if _, ok := issueTypes["Task"]; ok {
*issuetype = "Task"
return nil
}
return fmt.Errorf("Unable to find default issueType of Bug or Task, please set --issuetype argument or set the `issuetype` config property")
}
-54
View File
@@ -1,54 +0,0 @@
package jiracmd
import (
"github.com/coryb/figtree"
"github.com/coryb/oreo"
"github.com/go-jira/jira"
"github.com/go-jira/jira/jiracli"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
type CreateMetaOptions struct {
jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"`
Project string `yaml:"project,omitempty" json:"project,omitempty"`
IssueType string `yaml:"issuetype,omitempty" json:"issuetype,omitempty"`
}
func CmdCreateMetaRegistry() *jiracli.CommandRegistryEntry {
opts := CreateMetaOptions{
CommonOptions: jiracli.CommonOptions{
Template: figtree.NewStringOption("createmeta"),
},
}
return &jiracli.CommandRegistryEntry{
"View 'create' metadata",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
return CmdCreateMetaUsage(cmd, &opts)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdCreateMeta(o, globals, &opts)
},
}
}
func CmdCreateMetaUsage(cmd *kingpin.CmdClause, opts *CreateMetaOptions) error {
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions)
cmd.Flag("project", "project to fetch create metadata").Short('p').StringVar(&opts.Project)
cmd.Flag("issuetype", "issuetype in project to fetch create metadata").Short('i').StringVar(&opts.IssueType)
return nil
}
// Create will get issue create metadata and send to "createmeta" template
func CmdCreateMeta(o *oreo.Client, globals *jiracli.GlobalOptions, opts *CreateMetaOptions) error {
if err := defaultIssueType(o, globals.Endpoint.Value, &opts.Project, &opts.IssueType); err != nil {
return err
}
createMeta, err := jira.GetIssueCreateMetaIssueType(o, globals.Endpoint.Value, opts.Project, opts.IssueType)
if err != nil {
return err
}
return opts.PrintTemplate(createMeta)
}
-117
View File
@@ -1,117 +0,0 @@
package jiracmd
import (
"fmt"
"github.com/coryb/figtree"
"github.com/coryb/oreo"
"github.com/go-jira/jira"
"github.com/go-jira/jira/jiracli"
"github.com/go-jira/jira/jiradata"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
type DupOptions struct {
jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"`
jiradata.LinkIssueRequest `yaml:",inline" json:",inline" figtree:",inline"`
Duplicate string `yaml:"duplicate,omitempty" json:"duplicate,omitempty"`
Issue string `yaml:"issue,omitempty" json:"issue,omitempty"`
}
func CmdDupRegistry() *jiracli.CommandRegistryEntry {
opts := DupOptions{
CommonOptions: jiracli.CommonOptions{
Template: figtree.NewStringOption("edit"),
},
LinkIssueRequest: jiradata.LinkIssueRequest{
Type: &jiradata.IssueLinkType{
Name: "Duplicate",
},
InwardIssue: &jiradata.IssueRef{},
OutwardIssue: &jiradata.IssueRef{},
},
}
return &jiracli.CommandRegistryEntry{
"Mark issues as duplicate",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
return CmdDupUsage(cmd, &opts)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdDup(o, globals, &opts)
},
}
}
func CmdDupUsage(cmd *kingpin.CmdClause, opts *DupOptions) error {
jiracli.BrowseUsage(cmd, &opts.CommonOptions)
jiracli.EditorUsage(cmd, &opts.CommonOptions)
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
cmd.Flag("comment", "Comment message when marking issue as duplicate").Short('m').PreAction(func(ctx *kingpin.ParseContext) error {
opts.Comment = &jiradata.Comment{
Body: jiracli.FlagValue(ctx, "comment"),
}
return nil
}).String()
cmd.Arg("DUPLICATE", "duplicate issue to mark closed").Required().StringVar(&opts.InwardIssue.Key)
cmd.Arg("ISSUE", "duplicate issue to leave open").Required().StringVar(&opts.OutwardIssue.Key)
return nil
}
// CmdDup will update the given issue as being a duplicate by the given dup issue
// and will attempt to resolve the dup issue
func CmdDup(o *oreo.Client, globals *jiracli.GlobalOptions, opts *DupOptions) error {
if err := jira.LinkIssues(o, globals.Endpoint.Value, &opts.LinkIssueRequest); err != nil {
return err
}
if !globals.Quiet.Value {
fmt.Printf("OK %s %s\n", opts.OutwardIssue.Key, jira.URLJoin(globals.Endpoint.Value, "browse", opts.OutwardIssue.Key))
}
meta, err := jira.GetIssueTransitions(o, globals.Endpoint.Value, opts.InwardIssue.Key)
if err != nil {
return err
}
for _, trans := range []string{"close", "done", "cancel", "start", "stop"} {
transMeta := meta.Transitions.Find(trans)
if transMeta == nil {
continue
}
issueUpdate := jiradata.IssueUpdate{
Transition: transMeta,
}
resolution := defaultResolution(transMeta)
if resolution != "" {
issueUpdate.Fields = map[string]interface{}{
"resolution": map[string]interface{}{
"name": resolution,
},
}
}
if err = jira.TransitionIssue(o, globals.Endpoint.Value, opts.InwardIssue.Key, &issueUpdate); err != nil {
return err
}
if trans != "start" {
break
}
// if we are here then we must be stopping, so need to reset the meta
meta, err = jira.GetIssueTransitions(o, globals.Endpoint.Value, opts.InwardIssue.Key)
if err != nil {
return err
}
}
if !globals.Quiet.Value {
fmt.Printf("OK %s %s\n", opts.InwardIssue.Key, jira.URLJoin(globals.Endpoint.Value, "browse", opts.InwardIssue.Key))
}
if opts.Browse.Value {
if err := CmdBrowse(globals, opts.OutwardIssue.Key); err != nil {
return CmdBrowse(globals, opts.InwardIssue.Key)
}
}
return nil
}
-153
View File
@@ -1,153 +0,0 @@
package jiracmd
import (
"fmt"
"github.com/coryb/figtree"
"github.com/coryb/oreo"
"github.com/go-jira/jira"
"github.com/go-jira/jira/jiracli"
"github.com/go-jira/jira/jiradata"
"gopkg.in/AlecAivazis/survey.v1"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
type EditOptions struct {
jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"`
jiradata.IssueUpdate `yaml:",inline" json:",inline" figtree:",inline"`
jira.SearchOptions `yaml:",inline" json:",inline" figtree:",inline"`
Overrides map[string]string `yaml:"overrides,omitempty" json:"overrides,omitempty"`
Issue string `yaml:"issue,omitempty" json:"issue,omitempty"`
Queries map[string]string `yaml:"queries,omitempty" json:"queries,omitempty"`
}
func CmdEditRegistry() *jiracli.CommandRegistryEntry {
opts := EditOptions{
CommonOptions: jiracli.CommonOptions{
Template: figtree.NewStringOption("edit"),
},
Overrides: map[string]string{},
}
return &jiracli.CommandRegistryEntry{
"Edit issue details",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
return CmdEditUsage(cmd, &opts, fig)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
if opts.QueryFields == "" {
opts.QueryFields = "assignee,created,priority,reporter,status,summary,updated,issuetype,comment,description,votes,created,customfield_10110,components"
}
return CmdEdit(o, globals, &opts)
},
}
}
func CmdEditUsage(cmd *kingpin.CmdClause, opts *EditOptions, fig *figtree.FigTree) error {
jiracli.BrowseUsage(cmd, &opts.CommonOptions)
jiracli.EditorUsage(cmd, &opts.CommonOptions)
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
cmd.Flag("noedit", "Disable opening the editor").SetValue(&opts.SkipEditing)
cmd.Flag("named-query", "The name of a query in the `queries` configuration").Short('n').PreAction(func(ctx *kingpin.ParseContext) error {
name := jiracli.FlagValue(ctx, "named-query")
if query, ok := opts.Queries[name]; ok && query != "" {
var err error
opts.Query, err = jiracli.ConfigTemplate(fig, query, cmd.FullCommand(), opts)
return err
}
return fmt.Errorf("A valid named-query %q not found in `queries` configuration", name)
}).String()
cmd.Flag("query", "Jira Query Language (JQL) expression for the search to edit multiple issues").Short('q').StringVar(&opts.Query)
cmd.Flag("comment", "Comment message for issue").Short('m').PreAction(func(ctx *kingpin.ParseContext) error {
opts.Overrides["comment"] = jiracli.FlagValue(ctx, "comment")
return nil
}).String()
cmd.Flag("override", "Set issue property").Short('o').StringMapVar(&opts.Overrides)
cmd.Arg("ISSUE", "issue id to edit").StringVar(&opts.Issue)
return nil
}
// Edit will get issue data and send to "edit" template
func CmdEdit(o *oreo.Client, globals *jiracli.GlobalOptions, opts *EditOptions) error {
type templateInput struct {
*jiradata.Issue `yaml:",inline"`
Meta *jiradata.EditMeta `yaml:"meta" json:"meta"`
Overrides map[string]string `yaml:"overrides" json:"overrides"`
}
if opts.Issue != "" {
issueData, err := jira.GetIssue(o, globals.Endpoint.Value, opts.Issue, nil)
if err != nil {
return err
}
editMeta, err := jira.GetIssueEditMeta(o, globals.Endpoint.Value, opts.Issue)
if err != nil {
return err
}
issueUpdate := jiradata.IssueUpdate{}
input := templateInput{
Issue: issueData,
Meta: editMeta,
Overrides: opts.Overrides,
}
err = jiracli.EditLoop(&opts.CommonOptions, &input, &issueUpdate, func() error {
return jira.EditIssue(o, globals.Endpoint.Value, opts.Issue, &issueUpdate)
})
if err != nil {
return err
}
if !globals.Quiet.Value {
fmt.Printf("OK %s %s\n", opts.Issue, jira.URLJoin(globals.Endpoint.Value, "browse", opts.Issue))
}
if opts.Browse.Value {
return CmdBrowse(globals, opts.Issue)
}
return nil
}
results, err := jira.Search(o, globals.Endpoint.Value, opts)
if err != nil {
return err
}
for i, issueData := range results.Issues {
editMeta, err := jira.GetIssueEditMeta(o, globals.Endpoint.Value, issueData.Key)
if err != nil {
return err
}
issueUpdate := jiradata.IssueUpdate{}
input := templateInput{
Issue: issueData,
Meta: editMeta,
Overrides: opts.Overrides,
}
err = jiracli.EditLoop(&opts.CommonOptions, &input, &issueUpdate, func() error {
return jira.EditIssue(o, globals.Endpoint.Value, issueData.Key, &issueUpdate)
})
if err == jiracli.EditLoopAbort && len(results.Issues) > i+1 {
var answer bool
survey.AskOne(
&survey.Confirm{
Message: fmt.Sprintf("Continue to edit next issue %s?", results.Issues[i+1].Key),
Default: true,
},
&answer,
nil,
)
if answer {
continue
}
panic(jiracli.Exit{1})
}
if err != nil {
return err
}
if !globals.Quiet.Value {
fmt.Printf("OK %s %s\n", issueData.Key, jira.URLJoin(globals.Endpoint.Value, "browse", issueData.Key))
}
if opts.Browse.Value {
return CmdBrowse(globals, issueData.Key)
}
}
return nil
}
-57
View File
@@ -1,57 +0,0 @@
package jiracmd
import (
"github.com/coryb/figtree"
"github.com/coryb/oreo"
"github.com/go-jira/jira"
"github.com/go-jira/jira/jiracli"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
type EditMetaOptions struct {
jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"`
Issue string `yaml:"issue,omitempty" json:"issue,omitempty"`
}
func CmdEditMetaRegistry() *jiracli.CommandRegistryEntry {
opts := EditMetaOptions{
CommonOptions: jiracli.CommonOptions{
Template: figtree.NewStringOption("editmeta"),
},
}
return &jiracli.CommandRegistryEntry{
"View 'edit' metadata",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
return CmdEditMetaUsage(cmd, &opts)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdEditMeta(o, globals, &opts)
},
}
}
func CmdEditMetaUsage(cmd *kingpin.CmdClause, opts *EditMetaOptions) error {
jiracli.BrowseUsage(cmd, &opts.CommonOptions)
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions)
cmd.Arg("ISSUE", "edit metadata for issue id").Required().StringVar(&opts.Issue)
return nil
}
// CmdEditMeta will get issue edit metadata and send to "editmeta" template
func CmdEditMeta(o *oreo.Client, globals *jiracli.GlobalOptions, opts *EditMetaOptions) error {
editMeta, err := jira.GetIssueEditMeta(o, globals.Endpoint.Value, opts.Issue)
if err != nil {
return err
}
if err := opts.PrintTemplate(editMeta); err != nil {
return err
}
if opts.Browse.Value {
return CmdBrowse(globals, opts.Issue)
}
return nil
}
-54
View File
@@ -1,54 +0,0 @@
package jiracmd
import (
"fmt"
"github.com/coryb/figtree"
"github.com/coryb/oreo"
"github.com/go-jira/jira"
"github.com/go-jira/jira/jiracli"
"github.com/go-jira/jira/jiradata"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
type EpicAddOptions struct {
jiradata.EpicIssues `yaml:",inline" json:",inline" figtree:",inline"`
Epic string `yaml:"epic,omitempty" json:"epic,omitempty"`
}
func CmdEpicAddRegistry() *jiracli.CommandRegistryEntry {
opts := EpicAddOptions{}
return &jiracli.CommandRegistryEntry{
"Add issues to Epic",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
return CmdEpicAddUsage(cmd, &opts)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdEpicAdd(o, globals, &opts)
},
}
}
func CmdEpicAddUsage(cmd *kingpin.CmdClause, opts *EpicAddOptions) error {
cmd.Arg("EPIC", "Epic Key or ID to add issues to").Required().StringVar(&opts.Epic)
cmd.Arg("ISSUE", "Issues to add to epic").Required().StringsVar(&opts.Issues)
return nil
}
func CmdEpicAdd(o *oreo.Client, globals *jiracli.GlobalOptions, opts *EpicAddOptions) error {
if err := jira.EpicAddIssues(o, globals.Endpoint.Value, opts.Epic, &opts.EpicIssues); err != nil {
return err
}
if !globals.Quiet.Value {
fmt.Printf("OK %s %s\n", opts.Epic, jira.URLJoin(globals.Endpoint.Value, "browse", opts.Epic))
for _, issue := range opts.Issues {
fmt.Printf("OK %s %s\n", issue, jira.URLJoin(globals.Endpoint.Value, "browse", issue))
}
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More