├── _t ├── .gitignore ├── .jira.d │ ├── list.yml │ └── config.yml ├── dockerroot │ ├── root │ │ ├── .java │ │ │ └── .userPrefs │ │ │ │ ├── .user.lock.root │ │ │ │ ├── .userRootModFile.root │ │ │ │ └── com │ │ │ │ └── atlassian │ │ │ │ └── maven │ │ │ │ └── plugins │ │ │ │ └── amps │ │ │ │ ├── prefs.xml │ │ │ │ └── util │ │ │ │ └── prefs.xml │ │ └── .m2 │ │ │ └── settings.xml │ └── jiratestservice │ │ ├── LICENSE │ │ ├── src │ │ └── main │ │ │ └── resources │ │ │ └── atlassian-plugin.xml │ │ └── README ├── .password-store │ ├── .gpg-id │ └── GoJira │ │ ├── gojira.gpg │ │ ├── mothra.gpg │ │ ├── gojira@example.com.gpg │ │ ├── mothra@example.com.gpg │ │ ├── api-token__gojira@corybennett.org.gpg │ │ └── api-token__mothra@corybennett.org.gpg ├── .gnupg │ ├── pubring.gpg │ ├── secring.gpg │ └── trustdb.gpg ├── env.sh ├── Dockerfile ├── 010login.t ├── test_binaries.sh ├── README.md ├── 101default.t ├── 110basic-worklog.t ├── 120custom-commands.t └── 000setup.t ├── test ├── .jira.d │ ├── list.yml │ └── config.yml ├── apilogin.go └── checks.go ├── .github ├── CODEOWNERS └── workflows │ ├── main.yml │ └── prepare-release.yml ├── jiracmd ├── log.go ├── unassign.go ├── take.go ├── fields.go ├── browse.go ├── issuelinktypes.go ├── session.go ├── attachRemove.go ├── epicRemove.go ├── epicCreate.go ├── components.go ├── issuetypes.go ├── unexportTemplates.go ├── epicList.go ├── editmeta.go ├── epicAdd.go ├── createmeta.go ├── transitions.go ├── logout.go ├── worklogList.go ├── view.go ├── labelsSet.go ├── attachGet.go ├── attachList.go ├── exportTemplates.go ├── vote.go ├── labelsAdd.go ├── labelsRemove.go ├── componentAdd.go ├── rank.go ├── login.go ├── worklogAdd.go ├── comment.go ├── request.go ├── block.go ├── assign.go ├── issuelink.go └── list.go ├── jiradata ├── EpicIssues.go ├── ListOfAttachmentFuncs.go ├── RankRequest.go ├── ErrorCollectionFuncs.go ├── IssueType_test.go ├── Attachment_test.go ├── TransitionsFuncs.go ├── providers.go ├── FieldOperation.go ├── IncludedFields.go ├── AllowedValues.go ├── Fields.go ├── ClauseNames.go ├── ProjectKeys.go ├── Operations.go ├── ErrorMessages.go ├── WarningMessages.go ├── FieldOperations.go ├── intOrString.go ├── ServerInfo.go ├── Groups.go ├── FieldOperationsMap.go ├── Group.go ├── Properties.go ├── EntityProperty.go ├── SessionInfo.go ├── Visibility.go ├── Remotelinks.go ├── AuthParams.go ├── RemoteEntityLink.go ├── JSONTypeMap.go ├── IssueCreateResponse.go ├── IssueLinkTypes.go ├── IssueLinkType.go ├── ProjectCategory.go ├── Priority.go ├── LinkGroups.go ├── SimpleListWrapper.go ├── Items.go ├── JsonType.go ├── StatusCategory.go ├── Issuetypes.go ├── LoginInfo.go ├── ErrorCollection.go ├── HistoryMetadataParticipant.go ├── LinkGroup.go ├── Opsbar.go ├── CurrentUser.go ├── SimpleLink.go ├── ChangeItem.go ├── AuthSuccess.go ├── EditMeta.go ├── IssueType.go ├── SearchRequest.go ├── Status.go ├── FieldMetaMap.go ├── Field.go └── FieldMeta.go ├── jiracli ├── error.go ├── keyring_windows.go ├── keyring.go ├── socksproxy.go ├── log.go └── unixproxy.go ├── .gitignore ├── utils.go ├── httpClient.go ├── .jira.d └── config.yml ├── jira.go ├── serverinfo.go ├── .chglog ├── config.yml └── CHANGELOG.tpl.md ├── error.go ├── fields.go ├── component.go ├── attachment.go ├── cmd └── jira │ └── main.go ├── project.go ├── schemas └── fetch-schemas.go ├── users.go ├── session.go └── go.mod /_t/.gitignore: -------------------------------------------------------------------------------- 1 | !src/ -------------------------------------------------------------------------------- /_t/.jira.d/list.yml: -------------------------------------------------------------------------------- 1 | template: list 2 | -------------------------------------------------------------------------------- /test/.jira.d/list.yml: -------------------------------------------------------------------------------- 1 | template: list 2 | -------------------------------------------------------------------------------- /_t/dockerroot/root/.java/.userPrefs/.user.lock.root: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_t/dockerroot/root/.java/.userPrefs/.userRootModFile.root: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_t/.password-store/.gpg-id: -------------------------------------------------------------------------------- 1 | Go Jira 2 | -------------------------------------------------------------------------------- /_t/.gnupg/pubring.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-jira/jira/HEAD/_t/.gnupg/pubring.gpg -------------------------------------------------------------------------------- /_t/.gnupg/secring.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-jira/jira/HEAD/_t/.gnupg/secring.gpg -------------------------------------------------------------------------------- /_t/.gnupg/trustdb.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-jira/jira/HEAD/_t/.gnupg/trustdb.gpg -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # one of these users must approve the PR before merging 2 | * @coryb @mikepea @ldelossa @georgettica 3 | -------------------------------------------------------------------------------- /_t/.password-store/GoJira/gojira.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-jira/jira/HEAD/_t/.password-store/GoJira/gojira.gpg -------------------------------------------------------------------------------- /_t/.password-store/GoJira/mothra.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-jira/jira/HEAD/_t/.password-store/GoJira/mothra.gpg -------------------------------------------------------------------------------- /jiracmd/log.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import logging "gopkg.in/op/go-logging.v1" 4 | 5 | var log = logging.MustGetLogger("jiracmd") 6 | -------------------------------------------------------------------------------- /_t/.password-store/GoJira/gojira@example.com.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-jira/jira/HEAD/_t/.password-store/GoJira/gojira@example.com.gpg -------------------------------------------------------------------------------- /_t/.password-store/GoJira/mothra@example.com.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-jira/jira/HEAD/_t/.password-store/GoJira/mothra@example.com.gpg -------------------------------------------------------------------------------- /jiradata/EpicIssues.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | type EpicIssues struct { 4 | Issues []string `json:"issues,omitempty" yaml:"issues,omitempty"` 5 | } 6 | -------------------------------------------------------------------------------- /_t/.password-store/GoJira/api-token__gojira@corybennett.org.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-jira/jira/HEAD/_t/.password-store/GoJira/api-token__gojira@corybennett.org.gpg -------------------------------------------------------------------------------- /_t/.password-store/GoJira/api-token__mothra@corybennett.org.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-jira/jira/HEAD/_t/.password-store/GoJira/api-token__mothra@corybennett.org.gpg -------------------------------------------------------------------------------- /jiracli/error.go: -------------------------------------------------------------------------------- 1 | package jiracli 2 | 3 | import "github.com/pkg/errors" 4 | 5 | type Error struct { 6 | error 7 | } 8 | 9 | func CliError(cause error) error { 10 | return &Error{ 11 | errors.WithStack(cause), 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /_t/env.sh: -------------------------------------------------------------------------------- 1 | export COLUMNS=149 2 | export JIRA_LOG_FORMAT="%{level:-5s} %{message}" 3 | export ENDPOINT="https://go-jira.atlassian.net" 4 | export GNUPGHOME=$(pwd)/.gnupg 5 | export PASSWORD_STORE_DIR=$(pwd)/.password-store 6 | export JIRACLOUD=1 7 | 8 | -------------------------------------------------------------------------------- /_t/dockerroot/root/.java/.userPrefs/com/atlassian/maven/plugins/amps/prefs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | jira 2 | schemas/*.json 3 | /_t/.gnupg/random_seed 4 | /_t/issue.props 5 | /_t/attach.props 6 | /_t/garbage.bin 7 | /_t/attach1.txt 8 | /_t/binary.out 9 | /_t/foobar.bin 10 | /_t/.password-store/GoJira/api-token:gojira@corybennett.org.gpg 11 | /_t/.password-store/GoJira/api-token:mothra@corybennett.org.gpg 12 | dist 13 | -------------------------------------------------------------------------------- /jiradata/ListOfAttachmentFuncs.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | func (l *ListOfAttachment) Len() int { 4 | return len(*l) 5 | } 6 | 7 | func (l *ListOfAttachment) Less(i, j int) bool { 8 | return (*l)[i].ID < (*l)[j].ID 9 | } 10 | 11 | func (l *ListOfAttachment) Swap(i, j int) { 12 | (*l)[i], (*l)[j] = (*l)[j], (*l)[i] 13 | } 14 | -------------------------------------------------------------------------------- /jiradata/RankRequest.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | type RankRequest struct { 4 | Issues []string `json:"issues,omitempty" yaml:"issues,omitempty"` 5 | RankBeforeIssue string `json:"rankBeforeIssue,omitempty" yaml:"rankBeforeIssue,omitempty"` 6 | RankAfterIssue string `json:"rankAfterIssue,omitempty" yaml:"rankAfterIssue,omitempty"` 7 | } 8 | -------------------------------------------------------------------------------- /_t/dockerroot/jiratestservice/LICENSE: -------------------------------------------------------------------------------- 1 | To avoid future confusion, we recommend that you include a license with your plugin. 2 | This file is simply a reminder. 3 | 4 | For a template license you can have a look at: http://www.opensource.org/licenses/ 5 | 6 | Atlassian releases most of its modules under the Apache2 license: http://opensource.org/licenses/Apache-2.0 7 | -------------------------------------------------------------------------------- /_t/dockerroot/root/.java/.userPrefs/com/atlassian/maven/plugins/amps/util/prefs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /jiracli/keyring_windows.go: -------------------------------------------------------------------------------- 1 | package jiracli 2 | 3 | import "fmt" 4 | 5 | func keyringGet(user string) (string, error) { 6 | return "", fmt.Errorf("Keyring is not supported for Windows, see: https://github.com/tmc/keyring") 7 | } 8 | 9 | func keyringSet(user, passwd string) error { 10 | return fmt.Errorf("Keyring is not supported for Windows, see: https://github.com/tmc/keyring") 11 | } 12 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "path" 7 | ) 8 | 9 | func URLJoin(endpoint string, paths ...string) string { 10 | u, err := url.Parse(endpoint) 11 | if err != nil { 12 | panic(fmt.Errorf("unable to parse endpoint: %s", endpoint)) 13 | } 14 | paths = append([]string{u.Path}, paths...) 15 | u.Path = path.Join(paths...) 16 | return u.String() 17 | } 18 | -------------------------------------------------------------------------------- /httpClient.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | ) 7 | 8 | type HttpClient interface { 9 | Delete(url string) (*http.Response, error) 10 | Do(*http.Request) (*http.Response, error) 11 | GetJSON(url string) (*http.Response, error) 12 | Post(url, bodyType string, body io.Reader) (*http.Response, error) 13 | Put(url, bodyType string, body io.Reader) (*http.Response, error) 14 | } 15 | -------------------------------------------------------------------------------- /.jira.d/config.yml: -------------------------------------------------------------------------------- 1 | config: 2 | stop: true 3 | password-source: pass 4 | endpoint: https://go-jira.atlassian.net 5 | user: admin 6 | login: atlassian@corybennett.org 7 | 8 | queries: 9 | todo: | 10 | resolution = unresolved {{if .project}}AND project = '{{.project}}'{{end}} AND status = 'To Do' 11 | open: | 12 | resolution = unresolved {{if .project}}AND project = '{{.project}}'{{end}} AND status = 'Open' 13 | -------------------------------------------------------------------------------- /_t/dockerroot/jiratestservice/src/main/resources/atlassian-plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ${project.description} 4 | ${project.version} 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /jira.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "github.com/coryb/oreo" 5 | ) 6 | 7 | // replace by ldflags 8 | var VERSION = "development" 9 | 10 | type Jira struct { 11 | Endpoint string `json:"endpoint,omitempty" yaml:"endpoint,omitempty"` 12 | UA HttpClient `json:"-" yaml:"-"` 13 | } 14 | 15 | func NewJira(endpoint string) *Jira { 16 | return &Jira{ 17 | Endpoint: endpoint, 18 | UA: oreo.New(), 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /jiracli/keyring.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package jiracli 4 | 5 | import "github.com/tmc/keyring" 6 | 7 | func keyringGet(user string) (string, error) { 8 | password, err := keyring.Get("go-jira", user) 9 | if err != nil && err != keyring.ErrNotFound { 10 | return password, err 11 | } 12 | return password, nil 13 | } 14 | 15 | func keyringSet(user, passwd string) error { 16 | return keyring.Set("go-jira", user, passwd) 17 | } 18 | -------------------------------------------------------------------------------- /jiradata/ErrorCollectionFuncs.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | import "strings" 4 | 5 | // Error is needed to make ErrorCollection implement the error interface 6 | func (e ErrorCollection) Error() string { 7 | if len(e.ErrorMessages) > 0 { 8 | return strings.Join(e.ErrorMessages, ". ") 9 | } 10 | out := "" 11 | for k, v := range e.Errors { 12 | if len(out) > 0 { 13 | out += ". " 14 | } 15 | out += k + ": " + v 16 | } 17 | return out 18 | } 19 | -------------------------------------------------------------------------------- /jiradata/IssueType_test.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestIssueTypeFields(t *testing.T) { 10 | // this is because schema is wrong, missing the 'Fields' arguments, so we manually add it. 11 | // If the jiradata is regenerated we need to manually make the change again to include: 12 | // Fields FieldMetaMap `json:"fields,omitempty" yaml:"fields,omitempty"` 13 | assert.IsType(t, FieldMetaMap{}, IssueType{}.Fields) 14 | } 15 | -------------------------------------------------------------------------------- /jiradata/Attachment_test.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestAttachmentID(t *testing.T) { 10 | // this is because schema is wrong, defaults to type `int`, so we manually change it 11 | // to `string`. If the jiradata is regenerated we need to manually make the change 12 | // again to include: 13 | // ID IntOrString `json:"id,omitempty" yaml:"id,omitempty"` 14 | assert.IsType(t, IntOrString(0), Attachment{}.ID) 15 | } 16 | -------------------------------------------------------------------------------- /serverinfo.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/go-jira/jira/jiradata" 7 | ) 8 | 9 | func ServerInfo(ua HttpClient, endpoint string) (*jiradata.ServerInfo, error) { 10 | uri := URLJoin(endpoint, "rest/api/2/serverInfo") 11 | resp, err := ua.GetJSON(uri) 12 | if err != nil { 13 | return nil, err 14 | } 15 | defer resp.Body.Close() 16 | 17 | if resp.StatusCode == 200 { 18 | results := jiradata.ServerInfo{} 19 | return &results, json.NewDecoder(resp.Body).Decode(&results) 20 | } 21 | return nil, responseError(resp) 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 4 | on: [push, pull_request] 5 | 6 | jobs: 7 | tests: 8 | name: Tests 9 | runs-on: ubuntu-latest 10 | container: docker.io/library/golang:${{ matrix.go }} 11 | strategy: 12 | matrix: 13 | go: ['1.13', '1.14'] 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | - name: add gox 18 | run: go install github.com/mitchellh/gox 19 | - name: make binaries 20 | run: make all 21 | - name: perform tests 22 | run: go test -v ./... 23 | 24 | -------------------------------------------------------------------------------- /.chglog/config.yml: -------------------------------------------------------------------------------- 1 | style: github 2 | template: CHANGELOG.tpl.md 3 | info: 4 | title: CHANGELOG 5 | repository_url: https://github.com/go-jira/go-jira 6 | options: 7 | commits: 8 | sort_by: Scope 9 | commit_groups: 10 | group_by: Scope 11 | header: 12 | pattern: '^(.*):\s*(.*)$' 13 | pattern_maps: 14 | - Scope 15 | - Subject 16 | issues: 17 | prefix: 18 | - "#" 19 | 20 | refs: 21 | actions: 22 | - Closes 23 | - Fixes 24 | - PullRequest 25 | 26 | notes: 27 | keywords: 28 | - BREAKING CHANGE 29 | - NOTE 30 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/go-jira/jira/jiradata" 8 | ) 9 | 10 | func responseError(resp *http.Response) error { 11 | results := &jiradata.ErrorCollection{} 12 | if err := json.NewDecoder(resp.Body).Decode(results); err != nil { 13 | results.Status = resp.StatusCode 14 | results.ErrorMessages = append(results.ErrorMessages, err.Error()) 15 | } 16 | if len(results.ErrorMessages) == 0 && len(results.Errors) == 0 { 17 | results.Status = resp.StatusCode 18 | results.ErrorMessages = append(results.ErrorMessages, resp.Status) 19 | } 20 | return results 21 | } 22 | -------------------------------------------------------------------------------- /jiradata/TransitionsFuncs.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Find will search the transitions for one that matches 8 | // the given name. It will return a valid trantion that matches 9 | // or nil 10 | func (t Transitions) Find(name string) *Transition { 11 | name = strings.ToLower(name) 12 | matches := Transitions{} 13 | for _, trans := range t { 14 | if strings.ToLower(trans.Name) == name { 15 | return trans 16 | } 17 | if strings.Contains(strings.ToLower(trans.Name), name) { 18 | matches = append(matches, trans) 19 | } 20 | } 21 | if len(matches) > 0 { 22 | return matches[0] 23 | } 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /_t/dockerroot/jiratestservice/README: -------------------------------------------------------------------------------- 1 | You have successfully created an Atlassian Plugin! 2 | 3 | Here are the SDK commands you'll use immediately: 4 | 5 | * atlas-run -- installs this plugin into the product and starts it on localhost 6 | * atlas-debug -- same as atlas-run, but allows a debugger to attach at port 5005 7 | * atlas-cli -- after atlas-run or atlas-debug, opens a Maven command line window: 8 | - 'pi' reinstalls the plugin into the running product instance 9 | * atlas-help -- prints description for all commands in the SDK 10 | 11 | Full documentation is always available at: 12 | 13 | https://developer.atlassian.com/display/DOCS/Introduction+to+the+Atlassian+Plugin+SDK 14 | -------------------------------------------------------------------------------- /fields.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/go-jira/jira/jiradata" 7 | ) 8 | 9 | // https://docs.atlassian.com/jira/REST/cloud/#api/2/field-getFields 10 | func (j *Jira) GetFields() ([]jiradata.Field, error) { 11 | return GetFields(j.UA, j.Endpoint) 12 | } 13 | 14 | func GetFields(ua HttpClient, endpoint string) ([]jiradata.Field, error) { 15 | uri := URLJoin(endpoint, "rest/api/2/field") 16 | resp, err := ua.GetJSON(uri) 17 | if err != nil { 18 | return nil, err 19 | } 20 | defer resp.Body.Close() 21 | if resp.StatusCode == 200 { 22 | results := []jiradata.Field{} 23 | return results, json.NewDecoder(resp.Body).Decode(&results) 24 | } 25 | return nil, responseError(resp) 26 | } 27 | -------------------------------------------------------------------------------- /jiradata/providers.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | // these routines implement the various Provide interfaces that are required by the jira functions 4 | 5 | func (i *IssueUpdate) ProvideIssueUpdate() *IssueUpdate { 6 | return i 7 | } 8 | 9 | func (w *Worklog) ProvideWorklog() *Worklog { 10 | return w 11 | } 12 | 13 | func (l *LinkIssueRequest) ProvideLinkIssueRequest() *LinkIssueRequest { 14 | return l 15 | } 16 | 17 | func (r *RankRequest) ProvideRankRequest() *RankRequest { 18 | return r 19 | } 20 | 21 | func (c *Comment) ProvideComment() *Comment { 22 | return c 23 | } 24 | 25 | func (c *Component) ProvideComponent() *Component { 26 | return c 27 | } 28 | 29 | func (e *EpicIssues) ProvideEpicIssues() *EpicIssues { 30 | return e 31 | } 32 | -------------------------------------------------------------------------------- /jiradata/FieldOperation.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/IssueUpdate.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // FieldOperation defined from schema: 14 | // { 15 | // "title": "Field Operation", 16 | // "type": "object" 17 | // } 18 | type FieldOperation map[string]interface{} 19 | -------------------------------------------------------------------------------- /jiradata/IncludedFields.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // IncludedFields defined from schema: 14 | // { 15 | // "title": "Included Fields", 16 | // "type": "object" 17 | // } 18 | type IncludedFields map[string]interface{} 19 | -------------------------------------------------------------------------------- /jiradata/AllowedValues.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/TransitionsMeta.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // AllowedValues defined from schema: 14 | // { 15 | // "title": "allowedValues", 16 | // "type": "array", 17 | // "items": {} 18 | // } 19 | type AllowedValues []interface{} 20 | -------------------------------------------------------------------------------- /jiradata/Fields.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchRequest.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // Fields defined from schema: 14 | // { 15 | // "title": "fields", 16 | // "type": "array", 17 | // "items": { 18 | // "type": "string" 19 | // } 20 | // } 21 | type Fields []string 22 | -------------------------------------------------------------------------------- /jiradata/ClauseNames.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/Field.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // ClauseNames defined from schema: 14 | // { 15 | // "title": "clauseNames", 16 | // "type": "array", 17 | // "items": { 18 | // "type": "string" 19 | // } 20 | // } 21 | type ClauseNames []string 22 | -------------------------------------------------------------------------------- /jiradata/ProjectKeys.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/Project.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // ProjectKeys defined from schema: 14 | // { 15 | // "title": "projectKeys", 16 | // "type": "array", 17 | // "items": { 18 | // "type": "string" 19 | // } 20 | // } 21 | type ProjectKeys []string 22 | -------------------------------------------------------------------------------- /jiradata/Operations.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/TransitionsMeta.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // Operations defined from schema: 14 | // { 15 | // "title": "operations", 16 | // "type": "array", 17 | // "items": { 18 | // "type": "string" 19 | // } 20 | // } 21 | type Operations []string 22 | -------------------------------------------------------------------------------- /jiracli/socksproxy.go: -------------------------------------------------------------------------------- 1 | package jiracli 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "time" 7 | 8 | "golang.org/x/net/proxy" 9 | ) 10 | 11 | func socksProxy(address string) *http.Transport { 12 | return newSocksProxyTransport(address) 13 | } 14 | 15 | func newSocksProxyTransport(address string) *http.Transport { 16 | dialer, err := proxy.SOCKS5("tcp", address, nil, proxy.Direct) 17 | if err != nil { 18 | // TODO: whoops, return error? 19 | panic(err) 20 | } 21 | dial := func(network, addr string) (net.Conn, error) { 22 | return dialer.Dial(network, addr) 23 | } 24 | 25 | return &http.Transport{ 26 | Dial: dial, 27 | DisableKeepAlives: true, 28 | ResponseHeaderTimeout: 30 * time.Second, 29 | ExpectContinueTimeout: 10 * time.Second, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /jiradata/ErrorMessages.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/ErrorCollection.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // ErrorMessages defined from schema: 14 | // { 15 | // "title": "errorMessages", 16 | // "type": "array", 17 | // "items": { 18 | // "type": "string" 19 | // } 20 | // } 21 | type ErrorMessages []string 22 | -------------------------------------------------------------------------------- /jiradata/WarningMessages.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // WarningMessages defined from schema: 14 | // { 15 | // "title": "warningMessages", 16 | // "type": "array", 17 | // "items": { 18 | // "type": "string" 19 | // } 20 | // } 21 | type WarningMessages []string 22 | -------------------------------------------------------------------------------- /jiradata/FieldOperations.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/IssueUpdate.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // FieldOperations defined from schema: 14 | // { 15 | // "type": "array", 16 | // "items": { 17 | // "title": "Field Operation", 18 | // "type": "object" 19 | // } 20 | // } 21 | type FieldOperations []FieldOperation 22 | -------------------------------------------------------------------------------- /jiradata/intOrString.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | ) 7 | 8 | // this is for some bad schemas like Attachments.ID where in some api's it is an `int` and some it is a `string` 9 | type IntOrString int 10 | 11 | func (i *IntOrString) UnmarshalYAML(unmarshal func(interface{}) error) error { 12 | var tmp string 13 | if err := unmarshal(&tmp); err != nil { 14 | return unmarshal((*int)(i)) 15 | } 16 | tmpInt, err := strconv.Atoi(tmp) 17 | *i = IntOrString(tmpInt) 18 | return err 19 | } 20 | 21 | func (i *IntOrString) UnmarshalJSON(b []byte) error { 22 | var tmp string 23 | if err := json.Unmarshal(b, &tmp); err != nil { 24 | return json.Unmarshal(b, (*int)(i)) 25 | } 26 | tmpInt, err := strconv.Atoi(tmp) 27 | *i = IntOrString(tmpInt) 28 | return err 29 | } 30 | -------------------------------------------------------------------------------- /jiradata/ServerInfo.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | type ServerInfo struct { 4 | BaseURL string `json:"baseUrl,omitempty" yaml:"baseUrl,omitempty"` 5 | BuildDate string `json:"buildDate,omitempty" yaml:"buildDate,omitempty"` 6 | BuildNumber int `json:"buildNumber,omitempty" yaml:"buildNumber,omitempty"` 7 | DeploymentType string `json:"deploymentType,omitempty" yaml:"deploymentType,omitempty"` 8 | SCMInfo string `json:"scmInfo,omitempty" yaml:"scmInfo,omitempty"` 9 | ServerTime string `json:"serverTime,omitempty" yaml:"serverTime,omitempty"` 10 | ServerTitle string `json:"serverTitle,omitempty" yaml:"serverTitle,omitempty"` 11 | Version string `json:"version,omitempty" yaml:"version,omitempty"` 12 | VersionNumbers []int `json:"versionNumbers,omitempty" yaml:"versionNumbers,omitempty"` 13 | } 14 | -------------------------------------------------------------------------------- /jiradata/Groups.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/ListofAttachment.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // Groups defined from schema: 14 | // { 15 | // "type": "array", 16 | // "items": { 17 | // "title": "Group", 18 | // "type": "object", 19 | // "properties": { 20 | // "name": { 21 | // "type": "string" 22 | // }, 23 | // "self": { 24 | // "type": "string" 25 | // } 26 | // } 27 | // } 28 | // } 29 | type Groups []*Group 30 | -------------------------------------------------------------------------------- /jiracli/log.go: -------------------------------------------------------------------------------- 1 | package jiracli 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | 7 | logging "gopkg.in/op/go-logging.v1" 8 | ) 9 | 10 | var ( 11 | log = logging.MustGetLogger("jira") 12 | ) 13 | 14 | func IncreaseLogLevel(verbosity int) { 15 | logging.SetLevel(logging.GetLevel("")+logging.Level(verbosity), "") 16 | } 17 | 18 | func InitLogging() { 19 | logBackend := logging.NewLogBackend(os.Stderr, "", 0) 20 | format := os.Getenv("JIRA_LOG_FORMAT") 21 | if format == "" { 22 | format = "%{color}%{level:-5s}%{color:reset} %{message}" 23 | } 24 | logging.SetBackend( 25 | logging.NewBackendFormatter( 26 | logBackend, 27 | logging.MustStringFormatter(format), 28 | ), 29 | ) 30 | if os.Getenv("JIRA_DEBUG") == "" { 31 | logging.SetLevel(logging.NOTICE, "") 32 | } else { 33 | logging.SetLevel(logging.DEBUG, "") 34 | if verbosity, err := strconv.Atoi(os.Getenv("JIRA_DEBUG")); err == nil { 35 | IncreaseLogLevel(verbosity) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /jiracmd/unassign.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "github.com/coryb/figtree" 5 | "github.com/coryb/oreo" 6 | "github.com/go-jira/jira/jiracli" 7 | kingpin "gopkg.in/alecthomas/kingpin.v2" 8 | ) 9 | 10 | func CmdUnassignRegistry() *jiracli.CommandRegistryEntry { 11 | opts := AssignOptions{} 12 | 13 | return &jiracli.CommandRegistryEntry{ 14 | "Unassign an issue", 15 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 16 | jiracli.LoadConfigs(cmd, fig, &opts) 17 | return CmdAssignUsage(cmd, &opts) 18 | }, 19 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 20 | opts.Issue = jiracli.FormatIssue(opts.Issue, opts.Project) 21 | return CmdAssign(o, globals, &opts) 22 | }, 23 | } 24 | } 25 | 26 | func CmdUnassignUsage(cmd *kingpin.CmdClause, opts *AssignOptions) error { 27 | jiracli.BrowseUsage(cmd, &opts.CommonOptions) 28 | cmd.Arg("ISSUE", "issue to unassign").Required().StringVar(&opts.Issue) 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /jiradata/FieldOperationsMap.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/IssueUpdate.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // FieldOperationsMap defined from schema: 14 | // { 15 | // "title": "update", 16 | // "type": "object", 17 | // "patternProperties": { 18 | // ".+": { 19 | // "type": "array", 20 | // "items": { 21 | // "title": "Field Operation", 22 | // "type": "object" 23 | // } 24 | // } 25 | // } 26 | // } 27 | type FieldOperationsMap map[string]FieldOperations 28 | -------------------------------------------------------------------------------- /jiradata/Group.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/ListofAttachment.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // Group defined from schema: 14 | // { 15 | // "title": "Group", 16 | // "type": "object", 17 | // "properties": { 18 | // "name": { 19 | // "type": "string" 20 | // }, 21 | // "self": { 22 | // "type": "string" 23 | // } 24 | // } 25 | // } 26 | type Group struct { 27 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 28 | Self string `json:"self,omitempty" yaml:"self,omitempty"` 29 | } 30 | -------------------------------------------------------------------------------- /_t/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | RUN apk --update add openjdk8-jre curl screen && \ 3 | curl -s -L https://marketplace.atlassian.com/download/plugins/atlassian-plugin-sdk-tgz | tar xzf - && \ 4 | ln -s /atlassian* /atlassian 5 | 6 | ENV PATH=/bin:/usr/bin:/atlassian/bin 7 | 8 | # Copy in the serivce and also the root .m2 settings to force cache everything. 9 | # We also copy in /root/.java settings to prevent the dumb spam prompt from 10 | # the atlas-run command: 11 | # Would you like to subscribe to the Atlassian developer mailing list? (Y/y/N/n) Y: : 12 | COPY dockerroot / 13 | WORKDIR /jiratestservice 14 | 15 | EXPOSE 8080 16 | 17 | # we wrap the command with screen so that the dumb atlas-run has a tty to watch. Without screen 18 | # there is no tty so atlas-run will immediately read an EOF (aka CTRL-D) and interpret that to 19 | # mean we want the service to begin the "graceful shutdown" and exit 20 | CMD ["screen", "-DmL", "atlas-run", "--http-port", "8080", "--context-path", "ROOT", "--server", "localhost"] 21 | -------------------------------------------------------------------------------- /jiracli/unixproxy.go: -------------------------------------------------------------------------------- 1 | package jiracli 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | "os" 8 | "time" 9 | ) 10 | 11 | type transport struct { 12 | shadow *http.Transport 13 | } 14 | 15 | func newUnixProxyTransport(path string) *transport { 16 | dial := func(network, addr string) (net.Conn, error) { 17 | return net.Dial("unix", path) 18 | } 19 | 20 | shadow := &http.Transport{ 21 | Dial: dial, 22 | DialTLS: dial, 23 | DisableKeepAlives: true, 24 | ResponseHeaderTimeout: 30 * time.Second, 25 | ExpectContinueTimeout: 10 * time.Second, 26 | } 27 | 28 | return &transport{shadow} 29 | } 30 | 31 | func unixProxy(path string) *transport { 32 | return newUnixProxyTransport(os.ExpandEnv(path)) 33 | } 34 | 35 | func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) { 36 | req2 := *req 37 | url2 := *req.URL 38 | req2.URL = &url2 39 | req2.URL.Opaque = fmt.Sprintf("//%s%s", req.URL.Host, req.URL.EscapedPath()) 40 | return t.shadow.RoundTrip(&req2) 41 | } 42 | -------------------------------------------------------------------------------- /jiracmd/take.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "github.com/coryb/figtree" 5 | "github.com/coryb/oreo" 6 | "github.com/go-jira/jira/jiracli" 7 | kingpin "gopkg.in/alecthomas/kingpin.v2" 8 | ) 9 | 10 | func CmdTakeRegistry() *jiracli.CommandRegistryEntry { 11 | opts := AssignOptions{} 12 | 13 | return &jiracli.CommandRegistryEntry{ 14 | "Assign issue to yourself", 15 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 16 | jiracli.LoadConfigs(cmd, fig, &opts) 17 | return CmdAssignUsage(cmd, &opts) 18 | }, 19 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 20 | opts.Issue = jiracli.FormatIssue(opts.Issue, opts.Project) 21 | if opts.Assignee == "" { 22 | opts.Assignee = globals.Login.Value 23 | } 24 | return CmdAssign(o, globals, &opts) 25 | }, 26 | } 27 | } 28 | 29 | func CmdTakeUsage(cmd *kingpin.CmdClause, opts *AssignOptions) error { 30 | jiracli.BrowseUsage(cmd, &opts.CommonOptions) 31 | cmd.Arg("ISSUE", "issue to assign").Required().StringVar(&opts.Issue) 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /jiradata/Properties.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/WorklogWithPagination.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // Properties defined from schema: 14 | // { 15 | // "title": "properties", 16 | // "type": "array", 17 | // "items": { 18 | // "title": "Entity Property", 19 | // "type": "object", 20 | // "properties": { 21 | // "key": { 22 | // "title": "key", 23 | // "type": "string" 24 | // }, 25 | // "value": { 26 | // "title": "value" 27 | // } 28 | // } 29 | // } 30 | // } 31 | type Properties []*EntityProperty 32 | -------------------------------------------------------------------------------- /jiradata/EntityProperty.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/WorklogWithPagination.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // EntityProperty defined from schema: 14 | // { 15 | // "title": "Entity Property", 16 | // "type": "object", 17 | // "properties": { 18 | // "key": { 19 | // "title": "key", 20 | // "type": "string" 21 | // }, 22 | // "value": { 23 | // "title": "value" 24 | // } 25 | // } 26 | // } 27 | type EntityProperty struct { 28 | Key string `json:"key,omitempty" yaml:"key,omitempty"` 29 | Value interface{} `json:"value,omitempty" yaml:"value,omitempty"` 30 | } 31 | -------------------------------------------------------------------------------- /jiradata/SessionInfo.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/AuthSuccess.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // SessionInfo defined from schema: 14 | // { 15 | // "title": "Session Info", 16 | // "type": "object", 17 | // "properties": { 18 | // "name": { 19 | // "title": "name", 20 | // "type": "string" 21 | // }, 22 | // "value": { 23 | // "title": "value", 24 | // "type": "string" 25 | // } 26 | // } 27 | // } 28 | type SessionInfo struct { 29 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 30 | Value string `json:"value,omitempty" yaml:"value,omitempty"` 31 | } 32 | -------------------------------------------------------------------------------- /jiradata/Visibility.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/WorklogWithPagination.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // Visibility defined from schema: 14 | // { 15 | // "title": "Visibility", 16 | // "type": "object", 17 | // "properties": { 18 | // "type": { 19 | // "title": "type", 20 | // "type": "string" 21 | // }, 22 | // "value": { 23 | // "title": "value", 24 | // "type": "string" 25 | // } 26 | // } 27 | // } 28 | type Visibility struct { 29 | Type string `json:"type,omitempty" yaml:"type,omitempty"` 30 | Value string `json:"value,omitempty" yaml:"value,omitempty"` 31 | } 32 | -------------------------------------------------------------------------------- /component.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | 7 | "github.com/go-jira/jira/jiradata" 8 | ) 9 | 10 | type ComponentProvider interface { 11 | ProvideComponent() *jiradata.Component 12 | } 13 | 14 | // https://docs.atlassian.com/jira/REST/cloud/#api/2/component-createComponent 15 | func (j *Jira) CreateComponent(cp ComponentProvider) (*jiradata.Component, error) { 16 | return CreateComponent(j.UA, j.Endpoint, cp) 17 | } 18 | 19 | func CreateComponent(ua HttpClient, endpoint string, cp ComponentProvider) (*jiradata.Component, error) { 20 | req := cp.ProvideComponent() 21 | encoded, err := json.Marshal(req) 22 | if err != nil { 23 | return nil, err 24 | } 25 | uri := URLJoin(endpoint, "rest/api/2/component") 26 | resp, err := ua.Post(uri, "application/json", bytes.NewBuffer(encoded)) 27 | if err != nil { 28 | return nil, err 29 | } 30 | defer resp.Body.Close() 31 | 32 | if resp.StatusCode == 201 { 33 | results := &jiradata.Component{} 34 | return results, json.NewDecoder(resp.Body).Decode(results) 35 | } 36 | return nil, responseError(resp) 37 | } 38 | -------------------------------------------------------------------------------- /jiradata/Remotelinks.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/Project.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // Remotelinks defined from schema: 14 | // { 15 | // "title": "remotelinks", 16 | // "type": "array", 17 | // "items": { 18 | // "title": "Remote Entity Link", 19 | // "type": "object", 20 | // "properties": { 21 | // "link": { 22 | // "title": "link" 23 | // }, 24 | // "name": { 25 | // "title": "name", 26 | // "type": "string" 27 | // }, 28 | // "self": { 29 | // "title": "self", 30 | // "type": "string" 31 | // } 32 | // } 33 | // } 34 | // } 35 | type Remotelinks []*RemoteEntityLink 36 | -------------------------------------------------------------------------------- /_t/010login.t: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | eval "$(curl -q -s https://raw.githubusercontent.com/coryb/osht/master/osht.sh)" 3 | cd $(dirname $0) 4 | jira=../jira 5 | . env.sh 6 | 7 | SKIP test -n "$JIRACLOUD" # using Jira Cloud at go-jira.atlassian.net 8 | 9 | PLAN 7 10 | 11 | ############################################################################### 12 | ## Verify logout works, we expect when we call the session api 13 | ## that we will get a 401 and prompt user for password 14 | ################################################################################ 15 | RUNS $jira logout 16 | 17 | NRUNS $jira req /rest/auth/1/session &1` 16 | if ! [[ "$out" =~ "Mach-O 64-bit" ]]; then 17 | error "darwin/amd64 build not as expected: $out" 18 | fi 19 | 20 | out=`file ${DIST_DIR}/jira-linux-amd64 2>&1` 21 | if ! [[ "$out" =~ "ELF 64-bit LSB executable, x86-64" ]]; then 22 | error "linux/amd64 build not as expected: $out" 23 | fi 24 | 25 | out=`file ${DIST_DIR}/jira-linux-386 2>&1` 26 | if ! [[ "$out" =~ "ELF 32-bit LSB executable, Intel 80386" ]]; then 27 | error "linux/i386 build not as expected: $out" 28 | fi 29 | 30 | out=`file ${DIST_DIR}/jira-windows-amd64.exe 2>&1` 31 | if ! [[ "$out" =~ "PE32+ executable (console) x86-64" ]]; then 32 | error "windows/amd64 build not as expected: $out" 33 | fi 34 | 35 | out=`file ${DIST_DIR}/jira-windows-386.exe 2>&1` 36 | if ! [[ "$out" =~ "PE32 executable (console) Intel 80386" ]]; then 37 | error "windows/i386 build not as expected: $out" 38 | fi 39 | 40 | exit $EXIT_CODE 41 | -------------------------------------------------------------------------------- /jiradata/IssueCreateResponse.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/IssueCreateResponse.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // IssueCreateResponse defined from schema: 14 | // { 15 | // "title": "Issue Create Response", 16 | // "id": "https://docs.atlassian.com/jira/REST/schema/issue-create-response#", 17 | // "type": "object", 18 | // "properties": { 19 | // "id": { 20 | // "title": "id", 21 | // "type": "string" 22 | // }, 23 | // "key": { 24 | // "title": "key", 25 | // "type": "string" 26 | // }, 27 | // "self": { 28 | // "title": "self", 29 | // "type": "string" 30 | // } 31 | // } 32 | // } 33 | type IssueCreateResponse struct { 34 | ID string `json:"id,omitempty" yaml:"id,omitempty"` 35 | Key string `json:"key,omitempty" yaml:"key,omitempty"` 36 | Self string `json:"self,omitempty" yaml:"self,omitempty"` 37 | } 38 | -------------------------------------------------------------------------------- /attachment.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/go-jira/jira/jiradata" 7 | ) 8 | 9 | // https://docs.atlassian.com/jira/REST/cloud/#api/2/attachment-getAttachment 10 | func (j *Jira) GetAttachment(id string) (*jiradata.Attachment, error) { 11 | return GetAttachment(j.UA, j.Endpoint, id) 12 | } 13 | 14 | func GetAttachment(ua HttpClient, endpoint string, id string) (*jiradata.Attachment, error) { 15 | uri := URLJoin(endpoint, "rest/api/2/attachment", id) 16 | resp, err := ua.GetJSON(uri) 17 | if err != nil { 18 | return nil, err 19 | } 20 | defer resp.Body.Close() 21 | 22 | if resp.StatusCode == 200 { 23 | results := &jiradata.Attachment{} 24 | return results, json.NewDecoder(resp.Body).Decode(results) 25 | } 26 | return nil, responseError(resp) 27 | } 28 | 29 | // https://docs.atlassian.com/jira/REST/cloud/#api/2/attachment-removeAttachment 30 | func (j *Jira) RemoveAttachment(id string) error { 31 | return RemoveAttachment(j.UA, j.Endpoint, id) 32 | } 33 | 34 | func RemoveAttachment(ua HttpClient, endpoint string, id string) error { 35 | uri := URLJoin(endpoint, "rest/api/2/attachment", id) 36 | resp, err := ua.Delete(uri) 37 | if err != nil { 38 | return err 39 | } 40 | defer resp.Body.Close() 41 | 42 | if resp.StatusCode == 204 { 43 | return nil 44 | } 45 | return responseError(resp) 46 | } 47 | -------------------------------------------------------------------------------- /jiracmd/issuelinktypes.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "github.com/coryb/figtree" 5 | "github.com/coryb/oreo" 6 | "github.com/go-jira/jira" 7 | "github.com/go-jira/jira/jiracli" 8 | kingpin "gopkg.in/alecthomas/kingpin.v2" 9 | ) 10 | 11 | func CmdIssueLinkTypesRegistry() *jiracli.CommandRegistryEntry { 12 | opts := jiracli.CommonOptions{ 13 | Template: figtree.NewStringOption("issuelinktypes"), 14 | } 15 | 16 | return &jiracli.CommandRegistryEntry{ 17 | "Show the issue link types", 18 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 19 | jiracli.LoadConfigs(cmd, fig, &opts) 20 | return CmdIssueLinkTypesUsage(cmd, &opts) 21 | }, 22 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 23 | return CmdIssueLinkTypes(o, globals, &opts) 24 | }, 25 | } 26 | } 27 | 28 | func CmdIssueLinkTypesUsage(cmd *kingpin.CmdClause, opts *jiracli.CommonOptions) error { 29 | jiracli.TemplateUsage(cmd, opts) 30 | jiracli.GJsonQueryUsage(cmd, opts) 31 | return nil 32 | } 33 | 34 | // CmdIssueLinkTypes will get issue link type data and send to "issuelinktypes" template 35 | func CmdIssueLinkTypes(o *oreo.Client, globals *jiracli.GlobalOptions, opts *jiracli.CommonOptions) error { 36 | data, err := jira.GetIssueLinkTypes(o, globals.Endpoint.Value) 37 | if err != nil { 38 | return err 39 | } 40 | return opts.PrintTemplate(data) 41 | } 42 | -------------------------------------------------------------------------------- /cmd/jira/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "reflect" 7 | 8 | "github.com/coryb/figtree" 9 | "github.com/coryb/oreo" 10 | 11 | "github.com/go-jira/jira/jiracli" 12 | "github.com/go-jira/jira/jiracmd" 13 | "gopkg.in/coryb/yaml.v2" 14 | "gopkg.in/op/go-logging.v1" 15 | ) 16 | 17 | type oreoLogger struct { 18 | logger *logging.Logger 19 | } 20 | 21 | var log = logging.MustGetLogger("jira") 22 | 23 | func (ol *oreoLogger) Printf(format string, args ...interface{}) { 24 | ol.logger.Debugf(format, args...) 25 | } 26 | 27 | func main() { 28 | defer jiracli.HandleExit() 29 | 30 | jiracli.InitLogging() 31 | 32 | configDir := ".jira.d" 33 | 34 | yaml.UseMapType(reflect.TypeOf(map[string]interface{}{})) 35 | defer yaml.RestoreMapType() 36 | 37 | fig := figtree.NewFigTree( 38 | figtree.WithHome(jiracli.Homedir()), 39 | figtree.WithEnvPrefix("JIRA"), 40 | figtree.WithConfigDir(configDir), 41 | ) 42 | 43 | if err := os.MkdirAll(filepath.Join(jiracli.Homedir(), configDir), 0755); err != nil { 44 | log.Errorf("%s", err) 45 | panic(jiracli.Exit{Code: 1}) 46 | } 47 | 48 | o := oreo.New().WithCookieFile(filepath.Join(jiracli.Homedir(), configDir, "cookies.js")).WithLogger(&oreoLogger{log}) 49 | 50 | jiracmd.RegisterAllCommands() 51 | 52 | app := jiracli.CommandLine(fig, o) 53 | jiracli.ParseCommandLine(app, os.Args[1:]) 54 | } 55 | -------------------------------------------------------------------------------- /jiracmd/session.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/coryb/figtree" 7 | "github.com/coryb/oreo" 8 | "github.com/go-jira/jira" 9 | "github.com/go-jira/jira/jiracli" 10 | kingpin "gopkg.in/alecthomas/kingpin.v2" 11 | yaml "gopkg.in/coryb/yaml.v2" 12 | ) 13 | 14 | func CmdSessionRegistry() *jiracli.CommandRegistryEntry { 15 | opts := jiracli.CommonOptions{} 16 | return &jiracli.CommandRegistryEntry{ 17 | "Attempt to login into jira server", 18 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 19 | jiracli.LoadConfigs(cmd, fig, &opts) 20 | return nil 21 | }, 22 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 23 | return CmdSession(o, globals, &opts) 24 | }, 25 | } 26 | } 27 | 28 | // CmdSession will attempt to login into jira server 29 | func CmdSession(o *oreo.Client, globals *jiracli.GlobalOptions, opts *jiracli.CommonOptions) error { 30 | ua := o.WithoutRedirect().WithRetries(0).WithoutPostCallbacks() 31 | session, err := jira.GetSession(ua, globals.Endpoint.Value) 32 | var output []byte 33 | if err != nil { 34 | defer panic(jiracli.Exit{1}) 35 | output, err = yaml.Marshal(err) 36 | if err != nil { 37 | return err 38 | } 39 | } else { 40 | output, err = yaml.Marshal(session) 41 | if err != nil { 42 | return err 43 | } 44 | } 45 | fmt.Print(string(output)) 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /_t/README.md: -------------------------------------------------------------------------------- 1 | ## Tests 2 | 3 | The test are written using the `osht` bash testing framework. Please read the [documentation](https://github.com/coryb/osht/blob/master/README.md) for `osht`. 4 | 5 | ## Running Test: 6 | 7 | From the top level of the project you can run: 8 | ``` 9 | # this creates a local "jira" binary 10 | make 11 | 12 | # this runs the integration tests in the "_t" directory 13 | prove 14 | ``` 15 | 16 | ### Running individual tests 17 | To run a specific test you can run it directly like: 18 | ``` 19 | ./100basic.t 20 | ``` 21 | There is a useful `-v` option to make the test more verbose and an `-a` option to casue the test to abort after the first failure. 22 | 23 | The tests all require the jira service to be running from the docker container, so you will have to manually run the setup script: 24 | ``` 25 | ./000setup.t 26 | ``` 27 | 28 | After than you can run the other tests over and over. The jira service is just a test instance started for local development. It comes with 29 | a temporary license (I think it is 8 hours) so you will have to run the `./000setup.t` script at least once daily. 30 | 31 | ## API Documentation: 32 | https://docs.atlassian.com/jira/REST/cloud/ 33 | https://docs.atlassian.com/jira-software/REST/cloud 34 | 35 | ## projectTempalteKey missing documentation 36 | https://answers.atlassian.com/questions/36176301/jira-api-7.1.0-create-project 37 | 38 | -------------------------------------------------------------------------------- /jiradata/IssueLinkTypes.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/IssueLinkTypes.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // IssueLinkTypes defined from schema: 14 | // { 15 | // "title": "issueLinkTypes", 16 | // "type": "array", 17 | // "items": { 18 | // "title": "Issue Link Type", 19 | // "type": "object", 20 | // "properties": { 21 | // "id": { 22 | // "title": "id", 23 | // "type": "string" 24 | // }, 25 | // "inward": { 26 | // "title": "inward", 27 | // "type": "string" 28 | // }, 29 | // "name": { 30 | // "title": "name", 31 | // "type": "string" 32 | // }, 33 | // "outward": { 34 | // "title": "outward", 35 | // "type": "string" 36 | // }, 37 | // "self": { 38 | // "title": "self", 39 | // "type": "string" 40 | // } 41 | // } 42 | // } 43 | // } 44 | type IssueLinkTypes []*IssueLinkType 45 | -------------------------------------------------------------------------------- /_t/101default.t: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | eval "$(curl -q -s https://raw.githubusercontent.com/coryb/osht/master/osht.sh)" 3 | cd $(dirname $0) 4 | jira="../jira" 5 | . env.sh 6 | 7 | PLAN 6 8 | 9 | # reset login 10 | RUNS $jira logout 11 | RUNS $jira login 12 | 13 | # cleanup from previous failed test executions 14 | ($jira ls --project BASIC | awk -F: '{print $1}' | while read issue; do ../jira done $issue; done) | sed 's/^/# CLEANUP: /g' 15 | 16 | ############################################################################### 17 | ## Create an issue 18 | ############################################################################### 19 | RUNS $jira create --project BASIC -o summary=summary -o description=description --noedit --saveFile issue.props 20 | issue=$(awk '/issue/{print $2}' issue.props) 21 | 22 | DIFF <- 12 | resolution = unresolved {{if .project}}AND project = '{{.project}}'{{end}} AND status = 'To Do' 13 | 14 | custom-commands: 15 | - name: env 16 | help: print the JIRA environment variables available to custom commands 17 | script: |- 18 | env | sort | grep JIRA 19 | - name: print-project 20 | help: print the name of the configured project 21 | script: "echo $JIRA_PROJECT" 22 | - name: jira-path 23 | help: print the path the jira command that is running this alias 24 | script: |- 25 | echo {{jira}} 26 | - name: mine 27 | help: display issues assigned to me 28 | script: |- 29 | if [ -n "$JIRA_PROJECT" ]; then 30 | # if `project: ...` configured just list the issues for current project 31 | {{jira}} list --template table --query "resolution = unresolved and assignee=currentuser() and project = $JIRA_PROJECT ORDER BY priority asc, created" 32 | else 33 | # otherwise list issues for all project 34 | {{jira}} list --template table --query "resolution = unresolved and assignee=currentuser() ORDER BY priority asc, created" 35 | fi 36 | - name: argtest 37 | help: testing passing args 38 | script: |- 39 | echo {{args.ARG}} 40 | args: 41 | - name: ARG 42 | help: string to echo for testing 43 | - name: opttest 44 | help: testing passing option flags 45 | script: |- 46 | echo {{options.OPT}} 47 | options: 48 | - name: OPT 49 | help: string to echo for testing 50 | -------------------------------------------------------------------------------- /test/.jira.d/config.yml: -------------------------------------------------------------------------------- 1 | config: 2 | stop: true 3 | endpoint: https://go-jira.atlassian.net 4 | password-source: stdin 5 | user: gojira 6 | login: gojira@corybennett.org 7 | 8 | project: BASIC 9 | 10 | queries: 11 | todo: >- 12 | resolution = unresolved {{if .project}}AND project = '{{.project}}'{{end}} AND status = 'To Do' 13 | 14 | custom-commands: 15 | - name: env 16 | help: print the JIRA environment variables available to custom commands 17 | script: |- 18 | env | sort | grep JIRA 19 | - name: print-project 20 | help: print the name of the configured project 21 | script: "echo $JIRA_PROJECT" 22 | - name: jira-path 23 | help: print the path the jira command that is running this alias 24 | script: |- 25 | echo {{jira}} 26 | - name: mine 27 | help: display issues assigned to me 28 | script: |- 29 | if [ -n "$JIRA_PROJECT" ]; then 30 | # if `project: ...` configured just list the issues for current project 31 | {{jira}} list --template table --query "resolution = unresolved and assignee=currentuser() and project = $JIRA_PROJECT ORDER BY priority asc, created" 32 | else 33 | # otherwise list issues for all project 34 | {{jira}} list --template table --query "resolution = unresolved and assignee=currentuser() ORDER BY priority asc, created" 35 | fi 36 | - name: argtest 37 | help: testing passing args 38 | script: |- 39 | echo {{args.ARG}} 40 | args: 41 | - name: ARG 42 | help: string to echo for testing 43 | - name: opttest 44 | help: testing passing option flags 45 | script: |- 46 | echo {{options.OPT}} 47 | options: 48 | - name: OPT 49 | help: string to echo for testing 50 | -------------------------------------------------------------------------------- /jiracmd/epicRemove.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/coryb/figtree" 7 | "github.com/coryb/oreo" 8 | 9 | "github.com/go-jira/jira" 10 | "github.com/go-jira/jira/jiracli" 11 | "github.com/go-jira/jira/jiradata" 12 | kingpin "gopkg.in/alecthomas/kingpin.v2" 13 | ) 14 | 15 | type EpicRemoveOptions struct { 16 | jiradata.EpicIssues `yaml:",inline" json:",inline" figtree:",inline"` 17 | Project string `yaml:"project,omitempty" json:"project,omitempty"` 18 | } 19 | 20 | func CmdEpicRemoveRegistry() *jiracli.CommandRegistryEntry { 21 | opts := EpicRemoveOptions{} 22 | 23 | return &jiracli.CommandRegistryEntry{ 24 | "Remove issues from Epic", 25 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 26 | jiracli.LoadConfigs(cmd, fig, &opts) 27 | return CmdEpicRemoveUsage(cmd, &opts) 28 | }, 29 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 30 | for i := range opts.Issues { 31 | opts.Issues[i] = jiracli.FormatIssue(opts.Issues[i], opts.Project) 32 | } 33 | return CmdEpicRemove(o, globals, &opts) 34 | }, 35 | } 36 | } 37 | 38 | func CmdEpicRemoveUsage(cmd *kingpin.CmdClause, opts *EpicRemoveOptions) error { 39 | cmd.Arg("ISSUE", "Issues to remove from any epic").Required().StringsVar(&opts.Issues) 40 | return nil 41 | } 42 | 43 | func CmdEpicRemove(o *oreo.Client, globals *jiracli.GlobalOptions, opts *EpicRemoveOptions) error { 44 | if err := jira.EpicRemoveIssues(o, globals.Endpoint.Value, &opts.EpicIssues); err != nil { 45 | return err 46 | } 47 | 48 | if !globals.Quiet.Value { 49 | for _, issue := range opts.Issues { 50 | fmt.Printf("OK %s %s\n", issue, jira.URLJoin(globals.Endpoint.Value, "browse", issue)) 51 | } 52 | } 53 | 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /jiradata/LinkGroup.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // LinkGroup defined from schema: 14 | // { 15 | // "title": "Link Group", 16 | // "type": "object", 17 | // "properties": { 18 | // "groups": { 19 | // "type": "array", 20 | // "items": { 21 | // "$ref": "#/definitions/link-group" 22 | // } 23 | // }, 24 | // "header": { 25 | // "$ref": "#/definitions/simple-link" 26 | // }, 27 | // "id": { 28 | // "type": "string" 29 | // }, 30 | // "links": { 31 | // "type": "array", 32 | // "items": { 33 | // "$ref": "#/definitions/simple-link" 34 | // } 35 | // }, 36 | // "styleClass": { 37 | // "type": "string" 38 | // }, 39 | // "weight": { 40 | // "type": "integer" 41 | // } 42 | // } 43 | // } 44 | type LinkGroup struct { 45 | Groups []interface{} `json:"groups,omitempty" yaml:"groups,omitempty"` 46 | Header interface{} `json:"header,omitempty" yaml:"header,omitempty"` 47 | ID string `json:"id,omitempty" yaml:"id,omitempty"` 48 | Links []interface{} `json:"links,omitempty" yaml:"links,omitempty"` 49 | StyleClass string `json:"styleClass,omitempty" yaml:"styleClass,omitempty"` 50 | Weight int `json:"weight,omitempty" yaml:"weight,omitempty"` 51 | } 52 | -------------------------------------------------------------------------------- /jiradata/Opsbar.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // Opsbar defined from schema: 14 | // { 15 | // "title": "Opsbar", 16 | // "type": "object", 17 | // "properties": { 18 | // "linkGroups": { 19 | // "title": "linkGroups", 20 | // "type": "array", 21 | // "items": { 22 | // "title": "Link Group", 23 | // "type": "object", 24 | // "properties": { 25 | // "groups": { 26 | // "type": "array", 27 | // "items": { 28 | // "$ref": "#/definitions/link-group" 29 | // } 30 | // }, 31 | // "header": { 32 | // "$ref": "#/definitions/simple-link" 33 | // }, 34 | // "id": { 35 | // "type": "string" 36 | // }, 37 | // "links": { 38 | // "type": "array", 39 | // "items": { 40 | // "$ref": "#/definitions/simple-link" 41 | // } 42 | // }, 43 | // "styleClass": { 44 | // "type": "string" 45 | // }, 46 | // "weight": { 47 | // "type": "integer" 48 | // } 49 | // } 50 | // } 51 | // } 52 | // } 53 | // } 54 | type Opsbar struct { 55 | LinkGroups LinkGroups `json:"linkGroups,omitempty" yaml:"linkGroups,omitempty"` 56 | } 57 | -------------------------------------------------------------------------------- /jiracmd/epicCreate.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "github.com/coryb/figtree" 5 | "github.com/coryb/oreo" 6 | 7 | "github.com/go-jira/jira/jiracli" 8 | kingpin "gopkg.in/alecthomas/kingpin.v2" 9 | ) 10 | 11 | func CmdEpicCreateRegistry() *jiracli.CommandRegistryEntry { 12 | opts := CreateOptions{ 13 | CommonOptions: jiracli.CommonOptions{ 14 | Template: figtree.NewStringOption("epic-create"), 15 | }, 16 | Overrides: map[string]string{}, 17 | } 18 | 19 | return &jiracli.CommandRegistryEntry{ 20 | "Create Epic", 21 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 22 | jiracli.LoadConfigs(cmd, fig, &opts) 23 | return CmdEpicCreateUsage(cmd, &opts) 24 | }, 25 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 26 | return CmdCreate(o, globals, &opts) 27 | }, 28 | } 29 | } 30 | 31 | func CmdEpicCreateUsage(cmd *kingpin.CmdClause, opts *CreateOptions) error { 32 | jiracli.BrowseUsage(cmd, &opts.CommonOptions) 33 | jiracli.EditorUsage(cmd, &opts.CommonOptions) 34 | jiracli.TemplateUsage(cmd, &opts.CommonOptions) 35 | cmd.Flag("noedit", "Disable opening the editor").SetValue(&opts.SkipEditing) 36 | cmd.Flag("project", "project to create epic in").Short('p').StringVar(&opts.Project) 37 | cmd.Flag("epic-name", "Epic Name").Short('n').PreAction(func(ctx *kingpin.ParseContext) error { 38 | opts.Overrides["epic-name"] = jiracli.FlagValue(ctx, "epic-name") 39 | return nil 40 | }).String() 41 | cmd.Flag("comment", "Comment message for epic").Short('m').PreAction(func(ctx *kingpin.ParseContext) error { 42 | opts.Overrides["comment"] = jiracli.FlagValue(ctx, "comment") 43 | return nil 44 | }).String() 45 | cmd.Flag("override", "Set epic property").Short('o').StringMapVar(&opts.Overrides) 46 | cmd.Flag("saveFile", "Write epic as yaml to file").StringVar(&opts.SaveFile) 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /jiracmd/components.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/coryb/figtree" 7 | "github.com/coryb/oreo" 8 | 9 | "github.com/go-jira/jira" 10 | "github.com/go-jira/jira/jiracli" 11 | kingpin "gopkg.in/alecthomas/kingpin.v2" 12 | ) 13 | 14 | type ComponentsOptions struct { 15 | jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` 16 | Project string `yaml:"project,omitempty" json:"project,omitempty"` 17 | } 18 | 19 | func CmdComponentsRegistry() *jiracli.CommandRegistryEntry { 20 | opts := ComponentsOptions{ 21 | CommonOptions: jiracli.CommonOptions{ 22 | Template: figtree.NewStringOption("components"), 23 | }, 24 | } 25 | 26 | return &jiracli.CommandRegistryEntry{ 27 | "Show components for a project", 28 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 29 | jiracli.LoadConfigs(cmd, fig, &opts) 30 | return CmdComponentsUsage(cmd, &opts) 31 | }, 32 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 33 | return CmdComponents(o, globals, &opts) 34 | }, 35 | } 36 | } 37 | 38 | func CmdComponentsUsage(cmd *kingpin.CmdClause, opts *ComponentsOptions) error { 39 | jiracli.TemplateUsage(cmd, &opts.CommonOptions) 40 | jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions) 41 | cmd.Flag("project", "project to list components").Short('p').StringVar(&opts.Project) 42 | 43 | return nil 44 | } 45 | 46 | // CmdComponents will get available components for project and send to the "components" template 47 | func CmdComponents(o *oreo.Client, globals *jiracli.GlobalOptions, opts *ComponentsOptions) error { 48 | if opts.Project == "" { 49 | return fmt.Errorf("Project Required.") 50 | } 51 | data, err := jira.GetProjectComponents(o, globals.Endpoint.Value, opts.Project) 52 | if err != nil { 53 | return err 54 | } 55 | return opts.PrintTemplate(data) 56 | } 57 | -------------------------------------------------------------------------------- /jiracmd/issuetypes.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/coryb/figtree" 7 | "github.com/coryb/oreo" 8 | 9 | "github.com/go-jira/jira" 10 | "github.com/go-jira/jira/jiracli" 11 | kingpin "gopkg.in/alecthomas/kingpin.v2" 12 | ) 13 | 14 | type IssueTypesOptions struct { 15 | jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` 16 | Project string `yaml:"project,omitempty" json:"project,omitempty"` 17 | } 18 | 19 | func CmdIssueTypesRegistry() *jiracli.CommandRegistryEntry { 20 | opts := IssueTypesOptions{ 21 | CommonOptions: jiracli.CommonOptions{ 22 | Template: figtree.NewStringOption("issuetypes"), 23 | }, 24 | } 25 | 26 | return &jiracli.CommandRegistryEntry{ 27 | "Show issue types for a project", 28 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 29 | jiracli.LoadConfigs(cmd, fig, &opts) 30 | return CmdIssueTypesUsage(cmd, &opts) 31 | }, 32 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 33 | return CmdIssueTypes(o, globals, &opts) 34 | }, 35 | } 36 | } 37 | 38 | func CmdIssueTypesUsage(cmd *kingpin.CmdClause, opts *IssueTypesOptions) error { 39 | jiracli.TemplateUsage(cmd, &opts.CommonOptions) 40 | jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions) 41 | cmd.Flag("project", "project to list issueTypes").Short('p').StringVar(&opts.Project) 42 | 43 | return nil 44 | } 45 | 46 | // CmdIssueTypes will get available issueTypes for project and send to the "issueTypes" template 47 | func CmdIssueTypes(o *oreo.Client, globals *jiracli.GlobalOptions, opts *IssueTypesOptions) error { 48 | if opts.Project == "" { 49 | return fmt.Errorf("Project Required.") 50 | } 51 | data, err := jira.GetIssueCreateMetaProject(o, globals.Endpoint.Value, opts.Project) 52 | if err != nil { 53 | return err 54 | } 55 | return opts.PrintTemplate(data) 56 | } 57 | -------------------------------------------------------------------------------- /users.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/url" 7 | "strings" 8 | 9 | "github.com/go-jira/jira/jiradata" 10 | ) 11 | 12 | type UserSearchOptions struct { 13 | Query string `yaml:"query,omitempty" json:"query,omitempty"` 14 | Username string `yaml:"username,omitempty" json:"username,omitempty"` 15 | AccountID string `yaml:"accountId,omitempty" json:"accountId,omitempty"` 16 | StartAt int `yaml:"startAt,omitempty" json:"startAt,omitempty"` 17 | MaxResults int `yaml:"max-results,omitempty" json:"max-results,omitempty"` 18 | Property string `yaml:"property,omitempty" json:"property,omitempty"` 19 | } 20 | 21 | // https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-search-get 22 | 23 | func UserSearch(ua HttpClient, endpoint string, opts *UserSearchOptions) ([]*jiradata.User, error) { 24 | uri := URLJoin(endpoint, "rest/api/2/user/search") 25 | params := []string{} 26 | if opts.Query != "" { 27 | params = append(params, "query="+url.QueryEscape(opts.Query)) 28 | } 29 | if opts.AccountID != "" { 30 | params = append(params, "accountId="+url.QueryEscape(opts.AccountID)) 31 | } 32 | if opts.StartAt != 0 { 33 | params = append(params, fmt.Sprintf("startAt=%d", opts.StartAt)) 34 | } 35 | if opts.MaxResults != 0 { 36 | params = append(params, fmt.Sprintf("maxResults=%d", opts.MaxResults)) 37 | } 38 | if opts.Property != "" { 39 | params = append(params, "property="+url.QueryEscape(opts.Property)) 40 | } 41 | if len(params) > 0 { 42 | uri += "?" + strings.Join(params, "&") 43 | } 44 | resp, err := ua.GetJSON(uri) 45 | if err != nil { 46 | return nil, err 47 | } 48 | defer resp.Body.Close() 49 | 50 | if resp.StatusCode == 200 { 51 | results := []*jiradata.User{} 52 | return results, json.NewDecoder(resp.Body).Decode(&results) 53 | } 54 | return nil, responseError(resp) 55 | } 56 | -------------------------------------------------------------------------------- /.chglog/CHANGELOG.tpl.md: -------------------------------------------------------------------------------- 1 | {{ if .Versions -}} 2 | 3 | ## [Unreleased] 4 | 5 | {{ if .Unreleased.CommitGroups -}} 6 | {{ range .Unreleased.CommitGroups -}} 7 | ### {{ .Title }} 8 | {{ range .Commits -}} 9 | - [{{.Hash.Short}}]({{ $.Info.RepositoryURL }}/commit/{{ .Hash.Long }}): {{ .Subject }} 10 | {{ if .Refs -}}{{ range .Refs }} -{{if .Action}}{{ .Action }} {{ end }} [#{{ .Ref }}]({{ $.Info.RepositoryURL }}/issues/{{ .Ref }}){{ end -}} 11 | {{ end -}} 12 | {{ end -}} 13 | {{ end -}} 14 | {{ end -}} 15 | 16 | {{ range .Versions }} 17 | 18 | ## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }} 19 | {{ range .CommitGroups -}} 20 | ### {{ .Title }} 21 | {{ range .Commits -}} 22 | - [{{.Hash.Short}}]({{ $.Info.RepositoryURL }}/commit/{{ .Hash.Long }}): {{ .Subject }} 23 | {{ if .Refs -}}{{ range .Refs }} - {{if .Action}}{{ .Action }}{{ end }} [#{{ .Ref }}]({{ $.Info.RepositoryURL }}/issues/{{ .Ref }}){{ end -}} 24 | {{ end -}} 25 | {{ end -}} 26 | {{ end -}} 27 | 28 | {{- if .RevertCommits -}} 29 | ### Reverts 30 | {{ range .RevertCommits -}} 31 | - {{ .Revert.Header }} 32 | {{ end }} 33 | {{ end -}} 34 | 35 | {{- if .MergeCommits -}} 36 | ### Pull Requests 37 | {{ range .MergeCommits -}} 38 | - {{ .Header }} 39 | {{ end }} 40 | {{ end -}} 41 | 42 | {{- if .NoteGroups -}} 43 | {{ range .NoteGroups -}} 44 | ### {{ .Title }} 45 | {{ range .Notes }} 46 | {{ .Body }} 47 | {{ end }} 48 | {{ end -}} 49 | {{ end -}} 50 | {{ end -}} 51 | 52 | {{- if .Versions }} 53 | [Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD 54 | {{ range .Versions -}} 55 | {{ if .Tag.Previous -}} 56 | [{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }} 57 | {{ end -}} 58 | {{ end -}} 59 | {{ end -}} 60 | {{ end -}} 61 | -------------------------------------------------------------------------------- /jiracmd/unexportTemplates.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path" 9 | 10 | "github.com/coryb/figtree" 11 | "github.com/coryb/oreo" 12 | "github.com/go-jira/jira/jiracli" 13 | kingpin "gopkg.in/alecthomas/kingpin.v2" 14 | ) 15 | 16 | func CmdUnexportTemplatesRegistry() *jiracli.CommandRegistryEntry { 17 | opts := ExportTemplatesOptions{} 18 | 19 | return &jiracli.CommandRegistryEntry{ 20 | "Remove unmodified exported templates", 21 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 22 | jiracli.LoadConfigs(cmd, fig, &opts) 23 | return CmdExportTemplatesUsage(cmd, &opts) 24 | }, 25 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 26 | if opts.Dir == "" { 27 | opts.Dir = fmt.Sprintf("%s/.jira.d/templates", jiracli.Homedir()) 28 | } 29 | return CmdUnexportTemplates(globals, &opts) 30 | }, 31 | } 32 | } 33 | 34 | // CmdUnexportTemplates will remove unmodified templates from export directory 35 | func CmdUnexportTemplates(globals *jiracli.GlobalOptions, opts *ExportTemplatesOptions) error { 36 | for name, template := range jiracli.AllTemplates { 37 | if opts.Template != "" && opts.Template != name { 38 | continue 39 | } 40 | templateFile := path.Join(opts.Dir, name) 41 | if _, err := os.Stat(templateFile); err != nil { 42 | log.Warning("Skipping %s, not found", templateFile) 43 | continue 44 | } 45 | // open, read, compare 46 | contents, err := ioutil.ReadFile(templateFile) 47 | if err != nil { 48 | return err 49 | } 50 | if bytes.Equal([]byte(template), contents) { 51 | if !globals.Quiet.Value { 52 | log.Notice("Removing %s, template identical to default", templateFile) 53 | } 54 | os.Remove(templateFile) 55 | } else if !globals.Quiet.Value { 56 | log.Notice("Skipping %s, found customizations to template", templateFile) 57 | } 58 | } 59 | return nil 60 | } 61 | -------------------------------------------------------------------------------- /jiradata/CurrentUser.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/CurrentUser.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // CurrentUser defined from schema: 14 | // { 15 | // "title": "Current User", 16 | // "id": "https://docs.atlassian.com/jira/REST/schema/current-user#", 17 | // "type": "object", 18 | // "properties": { 19 | // "loginInfo": { 20 | // "title": "Login Info", 21 | // "type": "object", 22 | // "properties": { 23 | // "failedLoginCount": { 24 | // "title": "failedLoginCount", 25 | // "type": "integer" 26 | // }, 27 | // "lastFailedLoginTime": { 28 | // "title": "lastFailedLoginTime", 29 | // "type": "string" 30 | // }, 31 | // "loginCount": { 32 | // "title": "loginCount", 33 | // "type": "integer" 34 | // }, 35 | // "previousLoginTime": { 36 | // "title": "previousLoginTime", 37 | // "type": "string" 38 | // } 39 | // } 40 | // }, 41 | // "name": { 42 | // "title": "name", 43 | // "type": "string" 44 | // }, 45 | // "self": { 46 | // "title": "self", 47 | // "type": "string" 48 | // } 49 | // } 50 | // } 51 | type CurrentUser struct { 52 | LoginInfo *LoginInfo `json:"loginInfo,omitempty" yaml:"loginInfo,omitempty"` 53 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 54 | Self string `json:"self,omitempty" yaml:"self,omitempty"` 55 | } 56 | -------------------------------------------------------------------------------- /jiradata/SimpleLink.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/Project.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // SimpleLink defined from schema: 14 | // { 15 | // "title": "Simple Link", 16 | // "type": "object", 17 | // "properties": { 18 | // "href": { 19 | // "title": "href", 20 | // "type": "string" 21 | // }, 22 | // "iconClass": { 23 | // "title": "iconClass", 24 | // "type": "string" 25 | // }, 26 | // "id": { 27 | // "title": "id", 28 | // "type": "string" 29 | // }, 30 | // "label": { 31 | // "title": "label", 32 | // "type": "string" 33 | // }, 34 | // "styleClass": { 35 | // "title": "styleClass", 36 | // "type": "string" 37 | // }, 38 | // "title": { 39 | // "title": "title", 40 | // "type": "string" 41 | // }, 42 | // "weight": { 43 | // "title": "weight", 44 | // "type": "integer" 45 | // } 46 | // } 47 | // } 48 | type SimpleLink struct { 49 | Href string `json:"href,omitempty" yaml:"href,omitempty"` 50 | IconClass string `json:"iconClass,omitempty" yaml:"iconClass,omitempty"` 51 | ID string `json:"id,omitempty" yaml:"id,omitempty"` 52 | Label string `json:"label,omitempty" yaml:"label,omitempty"` 53 | StyleClass string `json:"styleClass,omitempty" yaml:"styleClass,omitempty"` 54 | Title string `json:"title,omitempty" yaml:"title,omitempty"` 55 | Weight int `json:"weight,omitempty" yaml:"weight,omitempty"` 56 | } 57 | -------------------------------------------------------------------------------- /jiracmd/epicList.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "github.com/coryb/figtree" 5 | "github.com/coryb/oreo" 6 | "github.com/go-jira/jira" 7 | "github.com/go-jira/jira/jiracli" 8 | kingpin "gopkg.in/alecthomas/kingpin.v2" 9 | ) 10 | 11 | type EpicListOptions struct { 12 | ListOptions `yaml:",inline" json:",inline" figtree:",inline"` 13 | Epic string `yaml:"epic,omitempty" json:"epic,omitempty"` 14 | } 15 | 16 | func CmdEpicListRegistry() *jiracli.CommandRegistryEntry { 17 | opts := EpicListOptions{ 18 | ListOptions: ListOptions{ 19 | CommonOptions: jiracli.CommonOptions{ 20 | Template: figtree.NewStringOption("epic-list"), 21 | }, 22 | }, 23 | } 24 | 25 | return &jiracli.CommandRegistryEntry{ 26 | "Prints list of issues for an epic with optional search criteria", 27 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 28 | jiracli.LoadConfigs(cmd, fig, &opts) 29 | return CmdEpicListUsage(cmd, &opts, fig) 30 | }, 31 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 32 | opts.Epic = jiracli.FormatIssue(opts.Epic, opts.Project) 33 | if opts.MaxResults == 0 { 34 | opts.MaxResults = 500 35 | } 36 | if opts.QueryFields == "" { 37 | opts.QueryFields = "assignee,created,priority,reporter,status,summary,updated,issuetype" 38 | } 39 | if opts.Sort == "" { 40 | opts.Sort = "priority asc, key" 41 | } 42 | return CmdEpicList(o, globals, &opts) 43 | }, 44 | } 45 | } 46 | 47 | func CmdEpicListUsage(cmd *kingpin.CmdClause, opts *EpicListOptions, fig *figtree.FigTree) error { 48 | CmdListUsage(cmd, &opts.ListOptions, fig) 49 | cmd.Arg("EPIC", "Epic Key or ID to list").Required().StringVar(&opts.Epic) 50 | return nil 51 | } 52 | 53 | func CmdEpicList(o *oreo.Client, globals *jiracli.GlobalOptions, opts *EpicListOptions) error { 54 | data, err := jira.EpicSearch(o, globals.Endpoint.Value, opts.Epic, opts) 55 | if err != nil { 56 | return err 57 | } 58 | return opts.PrintTemplate(data) 59 | } 60 | -------------------------------------------------------------------------------- /jiradata/ChangeItem.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // ChangeItem defined from schema: 14 | // { 15 | // "title": "Change Item", 16 | // "type": "object", 17 | // "properties": { 18 | // "field": { 19 | // "title": "field", 20 | // "type": "string" 21 | // }, 22 | // "fieldId": { 23 | // "title": "fieldId", 24 | // "type": "string" 25 | // }, 26 | // "fieldtype": { 27 | // "title": "fieldtype", 28 | // "type": "string" 29 | // }, 30 | // "from": { 31 | // "title": "from", 32 | // "type": "string" 33 | // }, 34 | // "fromString": { 35 | // "title": "fromString", 36 | // "type": "string" 37 | // }, 38 | // "to": { 39 | // "title": "to", 40 | // "type": "string" 41 | // }, 42 | // "toString": { 43 | // "title": "toString", 44 | // "type": "string" 45 | // } 46 | // } 47 | // } 48 | type ChangeItem struct { 49 | Field string `json:"field,omitempty" yaml:"field,omitempty"` 50 | FieldID string `json:"fieldId,omitempty" yaml:"fieldId,omitempty"` 51 | FieldType string `json:"fieldtype,omitempty" yaml:"fieldtype,omitempty"` 52 | From string `json:"from,omitempty" yaml:"from,omitempty"` 53 | FromString string `json:"fromString,omitempty" yaml:"fromString,omitempty"` 54 | To string `json:"to,omitempty" yaml:"to,omitempty"` 55 | ToString string `json:"toString,omitempty" yaml:"toString,omitempty"` 56 | } 57 | -------------------------------------------------------------------------------- /jiracmd/editmeta.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "github.com/coryb/figtree" 5 | "github.com/coryb/oreo" 6 | "github.com/go-jira/jira" 7 | "github.com/go-jira/jira/jiracli" 8 | kingpin "gopkg.in/alecthomas/kingpin.v2" 9 | ) 10 | 11 | type EditMetaOptions struct { 12 | jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` 13 | Project string `yaml:"project,omitempty" json:"project,omitempty"` 14 | Issue string `yaml:"issue,omitempty" json:"issue,omitempty"` 15 | } 16 | 17 | func CmdEditMetaRegistry() *jiracli.CommandRegistryEntry { 18 | 19 | opts := EditMetaOptions{ 20 | CommonOptions: jiracli.CommonOptions{ 21 | Template: figtree.NewStringOption("editmeta"), 22 | }, 23 | } 24 | 25 | return &jiracli.CommandRegistryEntry{ 26 | "View 'edit' metadata", 27 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 28 | jiracli.LoadConfigs(cmd, fig, &opts) 29 | return CmdEditMetaUsage(cmd, &opts) 30 | }, 31 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 32 | opts.Issue = jiracli.FormatIssue(opts.Issue, opts.Project) 33 | return CmdEditMeta(o, globals, &opts) 34 | }, 35 | } 36 | } 37 | 38 | func CmdEditMetaUsage(cmd *kingpin.CmdClause, opts *EditMetaOptions) error { 39 | jiracli.BrowseUsage(cmd, &opts.CommonOptions) 40 | jiracli.TemplateUsage(cmd, &opts.CommonOptions) 41 | jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions) 42 | cmd.Arg("ISSUE", "edit metadata for issue id").Required().StringVar(&opts.Issue) 43 | return nil 44 | } 45 | 46 | // CmdEditMeta will get issue edit metadata and send to "editmeta" template 47 | func CmdEditMeta(o *oreo.Client, globals *jiracli.GlobalOptions, opts *EditMetaOptions) error { 48 | editMeta, err := jira.GetIssueEditMeta(o, globals.Endpoint.Value, opts.Issue) 49 | if err != nil { 50 | return err 51 | } 52 | if err := opts.PrintTemplate(editMeta); err != nil { 53 | return err 54 | } 55 | if opts.Browse.Value { 56 | return CmdBrowse(globals, opts.Issue) 57 | } 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /jiracmd/epicAdd.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/coryb/figtree" 7 | "github.com/coryb/oreo" 8 | 9 | "github.com/go-jira/jira" 10 | "github.com/go-jira/jira/jiracli" 11 | "github.com/go-jira/jira/jiradata" 12 | kingpin "gopkg.in/alecthomas/kingpin.v2" 13 | ) 14 | 15 | type EpicAddOptions struct { 16 | jiradata.EpicIssues `yaml:",inline" json:",inline" figtree:",inline"` 17 | Project string `yaml:"project,omitempty" json:"project,omitempty"` 18 | Epic string `yaml:"epic,omitempty" json:"epic,omitempty"` 19 | } 20 | 21 | func CmdEpicAddRegistry() *jiracli.CommandRegistryEntry { 22 | opts := EpicAddOptions{} 23 | 24 | return &jiracli.CommandRegistryEntry{ 25 | "Add issues to Epic", 26 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 27 | jiracli.LoadConfigs(cmd, fig, &opts) 28 | return CmdEpicAddUsage(cmd, &opts) 29 | }, 30 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 31 | opts.Epic = jiracli.FormatIssue(opts.Epic, opts.Project) 32 | for i := range opts.Issues { 33 | opts.Issues[i] = jiracli.FormatIssue(opts.Issues[i], opts.Project) 34 | } 35 | return CmdEpicAdd(o, globals, &opts) 36 | }, 37 | } 38 | } 39 | 40 | func CmdEpicAddUsage(cmd *kingpin.CmdClause, opts *EpicAddOptions) error { 41 | cmd.Arg("EPIC", "Epic Key or ID to add issues to").Required().StringVar(&opts.Epic) 42 | cmd.Arg("ISSUE", "Issues to add to epic").Required().StringsVar(&opts.Issues) 43 | return nil 44 | } 45 | 46 | func CmdEpicAdd(o *oreo.Client, globals *jiracli.GlobalOptions, opts *EpicAddOptions) error { 47 | if err := jira.EpicAddIssues(o, globals.Endpoint.Value, opts.Epic, &opts.EpicIssues); err != nil { 48 | return err 49 | } 50 | 51 | if !globals.Quiet.Value { 52 | fmt.Printf("OK %s %s\n", opts.Epic, jira.URLJoin(globals.Endpoint.Value, "browse", opts.Epic)) 53 | for _, issue := range opts.Issues { 54 | fmt.Printf("OK %s %s\n", issue, jira.URLJoin(globals.Endpoint.Value, "browse", issue)) 55 | } 56 | } 57 | 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /jiradata/AuthSuccess.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/AuthSuccess.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // AuthSuccess defined from schema: 14 | // { 15 | // "title": "Auth Success", 16 | // "id": "https://docs.atlassian.com/jira/REST/schema/auth-success#", 17 | // "type": "object", 18 | // "properties": { 19 | // "loginInfo": { 20 | // "title": "Login Info", 21 | // "type": "object", 22 | // "properties": { 23 | // "failedLoginCount": { 24 | // "title": "failedLoginCount", 25 | // "type": "integer" 26 | // }, 27 | // "lastFailedLoginTime": { 28 | // "title": "lastFailedLoginTime", 29 | // "type": "string" 30 | // }, 31 | // "loginCount": { 32 | // "title": "loginCount", 33 | // "type": "integer" 34 | // }, 35 | // "previousLoginTime": { 36 | // "title": "previousLoginTime", 37 | // "type": "string" 38 | // } 39 | // } 40 | // }, 41 | // "session": { 42 | // "title": "Session Info", 43 | // "type": "object", 44 | // "properties": { 45 | // "name": { 46 | // "title": "name", 47 | // "type": "string" 48 | // }, 49 | // "value": { 50 | // "title": "value", 51 | // "type": "string" 52 | // } 53 | // } 54 | // } 55 | // } 56 | // } 57 | type AuthSuccess struct { 58 | LoginInfo *LoginInfo `json:"loginInfo,omitempty" yaml:"loginInfo,omitempty"` 59 | Session *SessionInfo `json:"session,omitempty" yaml:"session,omitempty"` 60 | } 61 | -------------------------------------------------------------------------------- /jiracmd/createmeta.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "github.com/coryb/figtree" 5 | "github.com/coryb/oreo" 6 | "github.com/go-jira/jira" 7 | "github.com/go-jira/jira/jiracli" 8 | kingpin "gopkg.in/alecthomas/kingpin.v2" 9 | ) 10 | 11 | type CreateMetaOptions struct { 12 | jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` 13 | Project string `yaml:"project,omitempty" json:"project,omitempty"` 14 | IssueType string `yaml:"issuetype,omitempty" json:"issuetype,omitempty"` 15 | } 16 | 17 | func CmdCreateMetaRegistry() *jiracli.CommandRegistryEntry { 18 | opts := CreateMetaOptions{ 19 | CommonOptions: jiracli.CommonOptions{ 20 | Template: figtree.NewStringOption("createmeta"), 21 | }, 22 | } 23 | 24 | return &jiracli.CommandRegistryEntry{ 25 | "View 'create' metadata", 26 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 27 | jiracli.LoadConfigs(cmd, fig, &opts) 28 | return CmdCreateMetaUsage(cmd, &opts) 29 | }, 30 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 31 | return CmdCreateMeta(o, globals, &opts) 32 | }, 33 | } 34 | } 35 | 36 | func CmdCreateMetaUsage(cmd *kingpin.CmdClause, opts *CreateMetaOptions) error { 37 | jiracli.TemplateUsage(cmd, &opts.CommonOptions) 38 | jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions) 39 | cmd.Flag("project", "project to fetch create metadata").Short('p').StringVar(&opts.Project) 40 | cmd.Flag("issuetype", "issuetype in project to fetch create metadata").Short('i').StringVar(&opts.IssueType) 41 | return nil 42 | } 43 | 44 | // Create will get issue create metadata and send to "createmeta" template 45 | func CmdCreateMeta(o *oreo.Client, globals *jiracli.GlobalOptions, opts *CreateMetaOptions) error { 46 | if err := defaultIssueType(o, globals.Endpoint.Value, &opts.Project, &opts.IssueType); err != nil { 47 | return err 48 | } 49 | createMeta, err := jira.GetIssueCreateMetaIssueType(o, globals.Endpoint.Value, opts.Project, opts.IssueType) 50 | if err != nil { 51 | return err 52 | } 53 | return opts.PrintTemplate(createMeta) 54 | } 55 | -------------------------------------------------------------------------------- /jiradata/EditMeta.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchResults.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // EditMeta defined from schema: 14 | // { 15 | // "title": "Edit Meta", 16 | // "type": "object", 17 | // "properties": { 18 | // "fields": { 19 | // "title": "fields", 20 | // "type": "object", 21 | // "patternProperties": { 22 | // ".+": { 23 | // "title": "Field Meta", 24 | // "type": "object", 25 | // "properties": { 26 | // "allowedValues": { 27 | // "type": "array", 28 | // "items": {} 29 | // }, 30 | // "autoCompleteUrl": { 31 | // "type": "string" 32 | // }, 33 | // "defaultValue": {}, 34 | // "hasDefaultValue": { 35 | // "type": "boolean" 36 | // }, 37 | // "key": { 38 | // "type": "string" 39 | // }, 40 | // "name": { 41 | // "type": "string" 42 | // }, 43 | // "operations": { 44 | // "type": "array", 45 | // "items": { 46 | // "type": "string" 47 | // } 48 | // }, 49 | // "required": { 50 | // "type": "boolean" 51 | // }, 52 | // "schema": { 53 | // "$ref": "#/definitions/json-type" 54 | // } 55 | // } 56 | // } 57 | // } 58 | // } 59 | // } 60 | // } 61 | type EditMeta struct { 62 | Fields FieldMetaMap `json:"fields,omitempty" yaml:"fields,omitempty"` 63 | } 64 | -------------------------------------------------------------------------------- /jiradata/IssueType.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/Project.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // IssueType defined from schema: 14 | // { 15 | // "title": "Issue Type", 16 | // "type": "object", 17 | // "properties": { 18 | // "avatarId": { 19 | // "title": "avatarId", 20 | // "type": "integer" 21 | // }, 22 | // "description": { 23 | // "title": "description", 24 | // "type": "string" 25 | // }, 26 | // "iconUrl": { 27 | // "title": "iconUrl", 28 | // "type": "string" 29 | // }, 30 | // "id": { 31 | // "title": "id", 32 | // "type": "string" 33 | // }, 34 | // "name": { 35 | // "title": "name", 36 | // "type": "string" 37 | // }, 38 | // "self": { 39 | // "title": "self", 40 | // "type": "string" 41 | // }, 42 | // "subtask": { 43 | // "title": "subtask", 44 | // "type": "boolean" 45 | // } 46 | // } 47 | // } 48 | type IssueType struct { 49 | AvatarID int `json:"avatarId,omitempty" yaml:"avatarId,omitempty"` 50 | Description string `json:"description,omitempty" yaml:"description,omitempty"` 51 | Fields FieldMetaMap `json:"fields,omitempty" yaml:"fields,omitempty"` 52 | IconURL string `json:"iconUrl,omitempty" yaml:"iconUrl,omitempty"` 53 | ID string `json:"id,omitempty" yaml:"id,omitempty"` 54 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 55 | Self string `json:"self,omitempty" yaml:"self,omitempty"` 56 | Subtask bool `json:"subtask,omitempty" yaml:"subtask,omitempty"` 57 | } 58 | -------------------------------------------------------------------------------- /jiracmd/transitions.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "github.com/coryb/figtree" 5 | "github.com/coryb/oreo" 6 | "github.com/go-jira/jira" 7 | "github.com/go-jira/jira/jiracli" 8 | kingpin "gopkg.in/alecthomas/kingpin.v2" 9 | ) 10 | 11 | type TransitionsOptions struct { 12 | jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` 13 | Project string `yaml:"project,omitempty" json:"project,omitempty"` 14 | Issue string `yaml:"issue,omitempty" json:"issue,omitempty"` 15 | } 16 | 17 | func CmdTransitionsRegistry(defaultTemplate string) *jiracli.CommandRegistryEntry { 18 | opts := TransitionsOptions{ 19 | CommonOptions: jiracli.CommonOptions{ 20 | Template: figtree.NewStringOption(defaultTemplate), 21 | }, 22 | } 23 | 24 | return &jiracli.CommandRegistryEntry{ 25 | "List valid issue transitions", 26 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 27 | jiracli.LoadConfigs(cmd, fig, &opts) 28 | return CmdTransitionsUsage(cmd, &opts) 29 | }, 30 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 31 | opts.Issue = jiracli.FormatIssue(opts.Issue, opts.Project) 32 | return CmdTransitions(o, globals, &opts) 33 | }, 34 | } 35 | } 36 | 37 | func CmdTransitionsUsage(cmd *kingpin.CmdClause, opts *TransitionsOptions) error { 38 | jiracli.BrowseUsage(cmd, &opts.CommonOptions) 39 | jiracli.TemplateUsage(cmd, &opts.CommonOptions) 40 | jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions) 41 | cmd.Arg("ISSUE", "issue to list valid transitions").Required().StringVar(&opts.Issue) 42 | return nil 43 | } 44 | 45 | // Transitions will get issue edit metadata and send to "editmeta" template 46 | func CmdTransitions(o *oreo.Client, globals *jiracli.GlobalOptions, opts *TransitionsOptions) error { 47 | editMeta, err := jira.GetIssueTransitions(o, globals.Endpoint.Value, opts.Issue) 48 | if err != nil { 49 | return err 50 | } 51 | if err := opts.PrintTemplate(editMeta); err != nil { 52 | return err 53 | } 54 | if opts.Browse.Value { 55 | return CmdBrowse(globals, opts.Issue) 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /jiracmd/logout.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/coryb/figtree" 8 | "github.com/coryb/oreo" 9 | "github.com/go-jira/jira" 10 | "github.com/go-jira/jira/jiracli" 11 | "github.com/mgutz/ansi" 12 | "golang.org/x/crypto/ssh/terminal" 13 | survey "gopkg.in/AlecAivazis/survey.v1" 14 | kingpin "gopkg.in/alecthomas/kingpin.v2" 15 | ) 16 | 17 | func CmdLogoutRegistry() *jiracli.CommandRegistryEntry { 18 | opts := jiracli.CommonOptions{} 19 | return &jiracli.CommandRegistryEntry{ 20 | "Deactivate session with Jira server", 21 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 22 | jiracli.LoadConfigs(cmd, fig, &opts) 23 | return nil 24 | }, 25 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 26 | return CmdLogout(o, globals, &opts) 27 | }, 28 | } 29 | } 30 | 31 | // CmdLogout will attempt to terminate an active Jira session 32 | func CmdLogout(o *oreo.Client, globals *jiracli.GlobalOptions, opts *jiracli.CommonOptions) error { 33 | if globals.AuthMethodIsToken() { 34 | log.Noticef("No need to logout when using api-token or bearer-token authentication method") 35 | if globals.GetPass() != "" && terminal.IsTerminal(int(os.Stdin.Fd())) && terminal.IsTerminal(int(os.Stdout.Fd())) { 36 | delete := false 37 | err := survey.AskOne( 38 | &survey.Confirm{ 39 | Message: fmt.Sprintf("Delete token from password provider [%s]: ", globals.PasswordSource), 40 | Default: false, 41 | }, 42 | &delete, 43 | nil, 44 | ) 45 | if err != nil { 46 | log.Errorf("%s", err) 47 | panic(jiracli.Exit{Code: 1}) 48 | } 49 | if delete { 50 | globals.SetPass("") 51 | } 52 | } 53 | return nil 54 | } 55 | ua := o.WithoutRedirect().WithRetries(0).WithoutCallbacks() 56 | err := jira.DeleteSession(ua, globals.Endpoint.Value) 57 | if err == nil { 58 | if !globals.Quiet.Value { 59 | fmt.Println(ansi.Color("OK", "green"), "Terminated session for", globals.User) 60 | } 61 | } else { 62 | fmt.Printf("%s Failed to terminate session for %s: %s", ansi.Color("ERROR", "red"), globals.User, err) 63 | } 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /jiracmd/worklogList.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "github.com/coryb/figtree" 5 | "github.com/coryb/oreo" 6 | "github.com/go-jira/jira" 7 | "github.com/go-jira/jira/jiracli" 8 | "github.com/go-jira/jira/jiradata" 9 | kingpin "gopkg.in/alecthomas/kingpin.v2" 10 | ) 11 | 12 | type WorklogListOptions struct { 13 | jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` 14 | Project string `yaml:"project,omitempty" json:"project,omitempty"` 15 | Issue string `yaml:"issue,omitempty" json:"issue,omitempty"` 16 | } 17 | 18 | func CmdWorklogListRegistry() *jiracli.CommandRegistryEntry { 19 | opts := WorklogListOptions{ 20 | CommonOptions: jiracli.CommonOptions{ 21 | Template: figtree.NewStringOption("worklogs"), 22 | }, 23 | } 24 | return &jiracli.CommandRegistryEntry{ 25 | "Prints the worklog data for given issue", 26 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 27 | jiracli.LoadConfigs(cmd, fig, &opts) 28 | return CmdWorklogListUsage(cmd, &opts) 29 | }, 30 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 31 | opts.Issue = jiracli.FormatIssue(opts.Issue, opts.Project) 32 | return CmdWorklogList(o, globals, &opts) 33 | }, 34 | } 35 | } 36 | 37 | func CmdWorklogListUsage(cmd *kingpin.CmdClause, opts *WorklogListOptions) error { 38 | jiracli.BrowseUsage(cmd, &opts.CommonOptions) 39 | jiracli.TemplateUsage(cmd, &opts.CommonOptions) 40 | jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions) 41 | cmd.Arg("ISSUE", "issue id to fetch worklogs").Required().StringVar(&opts.Issue) 42 | return nil 43 | } 44 | 45 | // CmdWorklogList will get worklog data for given issue and sent to the "worklogs" template 46 | func CmdWorklogList(o *oreo.Client, globals *jiracli.GlobalOptions, opts *WorklogListOptions) error { 47 | data, err := jira.GetIssueWorklog(o, globals.Endpoint.Value, opts.Issue) 48 | if err != nil { 49 | return err 50 | } 51 | if err := opts.PrintTemplate(struct { 52 | Worklogs *jiradata.Worklogs `json:"worklogs,omitempty" yaml:"worklogs,omitempty"` 53 | }{data}); err != nil { 54 | return err 55 | } 56 | if opts.Browse.Value { 57 | return CmdBrowse(globals, opts.Issue) 58 | } 59 | return nil 60 | } 61 | -------------------------------------------------------------------------------- /jiracmd/view.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "github.com/coryb/figtree" 5 | "github.com/coryb/oreo" 6 | "github.com/go-jira/jira" 7 | "github.com/go-jira/jira/jiracli" 8 | kingpin "gopkg.in/alecthomas/kingpin.v2" 9 | ) 10 | 11 | type ViewOptions struct { 12 | jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` 13 | jira.IssueOptions `yaml:",inline" json:",inline" figtree:",inline"` 14 | Project string `yaml:"project,omitempty" json:"project,omitempty"` 15 | Issue string `yaml:"issue,omitempty" json:"issue,omitempty"` 16 | } 17 | 18 | func CmdViewRegistry() *jiracli.CommandRegistryEntry { 19 | opts := ViewOptions{ 20 | CommonOptions: jiracli.CommonOptions{ 21 | Template: figtree.NewStringOption("view"), 22 | }, 23 | } 24 | 25 | return &jiracli.CommandRegistryEntry{ 26 | "Prints issue details", 27 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 28 | jiracli.LoadConfigs(cmd, fig, &opts) 29 | return CmdViewUsage(cmd, &opts) 30 | }, 31 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 32 | opts.Issue = jiracli.FormatIssue(opts.Issue, opts.Project) 33 | return CmdView(o, globals, &opts) 34 | }, 35 | } 36 | } 37 | 38 | func CmdViewUsage(cmd *kingpin.CmdClause, opts *ViewOptions) error { 39 | jiracli.BrowseUsage(cmd, &opts.CommonOptions) 40 | jiracli.TemplateUsage(cmd, &opts.CommonOptions) 41 | jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions) 42 | cmd.Flag("expand", "field to expand for the issue").StringsVar(&opts.Expand) 43 | cmd.Flag("field", "field to return for the issue").StringsVar(&opts.Fields) 44 | cmd.Flag("property", "property to return for issue").StringsVar(&opts.Properties) 45 | cmd.Arg("ISSUE", "issue id to view").Required().StringVar(&opts.Issue) 46 | return nil 47 | } 48 | 49 | // View will get issue data and send to "view" template 50 | func CmdView(o *oreo.Client, globals *jiracli.GlobalOptions, opts *ViewOptions) error { 51 | data, err := jira.GetIssue(o, globals.Endpoint.Value, opts.Issue, opts) 52 | if err != nil { 53 | return err 54 | } 55 | if err := opts.PrintTemplate(data); err != nil { 56 | return err 57 | } 58 | if opts.Browse.Value { 59 | return CmdBrowse(globals, opts.Issue) 60 | } 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /jiracmd/labelsSet.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/coryb/figtree" 7 | "github.com/coryb/oreo" 8 | 9 | "github.com/go-jira/jira" 10 | "github.com/go-jira/jira/jiracli" 11 | "github.com/go-jira/jira/jiradata" 12 | kingpin "gopkg.in/alecthomas/kingpin.v2" 13 | ) 14 | 15 | type LabelsSetOptions struct { 16 | jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` 17 | Project string `yaml:"project,omitempty" json:"project,omitempty"` 18 | Issue string `yaml:"issue,omitempty" json:"issue,omitempty"` 19 | Labels []string `yaml:"labels,omitempty" json:"labels,omitempty"` 20 | } 21 | 22 | func CmdLabelsSetRegistry() *jiracli.CommandRegistryEntry { 23 | opts := LabelsSetOptions{} 24 | return &jiracli.CommandRegistryEntry{ 25 | "Set labels on an issue", 26 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 27 | jiracli.LoadConfigs(cmd, fig, &opts) 28 | return CmdLabelsSetUsage(cmd, &opts) 29 | }, 30 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 31 | opts.Issue = jiracli.FormatIssue(opts.Issue, opts.Project) 32 | return CmdLabelsSet(o, globals, &opts) 33 | }, 34 | } 35 | } 36 | 37 | func CmdLabelsSetUsage(cmd *kingpin.CmdClause, opts *LabelsSetOptions) error { 38 | jiracli.BrowseUsage(cmd, &opts.CommonOptions) 39 | cmd.Arg("ISSUE", "issue id to modify labels").Required().StringVar(&opts.Issue) 40 | cmd.Arg("LABEL", "label to set on issue").Required().StringsVar(&opts.Labels) 41 | return nil 42 | } 43 | 44 | // CmdLabelsSet will set labels on a given issue 45 | func CmdLabelsSet(o *oreo.Client, globals *jiracli.GlobalOptions, opts *LabelsSetOptions) error { 46 | issueUpdate := jiradata.IssueUpdate{ 47 | Update: jiradata.FieldOperationsMap{ 48 | "labels": jiradata.FieldOperations{ 49 | jiradata.FieldOperation{ 50 | "set": opts.Labels, 51 | }, 52 | }, 53 | }, 54 | } 55 | 56 | if err := jira.EditIssue(o, globals.Endpoint.Value, opts.Issue, &issueUpdate); err != nil { 57 | return err 58 | } 59 | if !globals.Quiet.Value { 60 | fmt.Printf("OK %s %s\n", opts.Issue, jira.URLJoin(globals.Endpoint.Value, "browse", opts.Issue)) 61 | } 62 | if opts.Browse.Value { 63 | return CmdBrowse(globals, opts.Issue) 64 | } 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /jiracmd/attachGet.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | 8 | "github.com/coryb/figtree" 9 | "github.com/coryb/oreo" 10 | jira "github.com/go-jira/jira" 11 | "github.com/go-jira/jira/jiracli" 12 | kingpin "gopkg.in/alecthomas/kingpin.v2" 13 | ) 14 | 15 | type AttachGetOptions struct { 16 | AttachmentID string `yaml:"attachment-id,omitempty" json:"attachment-id,omitempty"` 17 | OutputFile string `yaml:"output-file,omitempty" json:"output-file,omitempty"` 18 | } 19 | 20 | func CmdAttachGetRegistry() *jiracli.CommandRegistryEntry { 21 | opts := AttachGetOptions{} 22 | 23 | return &jiracli.CommandRegistryEntry{ 24 | "Fetch attachment", 25 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 26 | jiracli.LoadConfigs(cmd, fig, &opts) 27 | return CmdAttachGetUsage(cmd, &opts) 28 | }, 29 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 30 | return CmdAttachGet(o, globals, &opts) 31 | }, 32 | } 33 | } 34 | 35 | func CmdAttachGetUsage(cmd *kingpin.CmdClause, opts *AttachGetOptions) error { 36 | cmd.Flag("output", "Write attachment to specified file name, '-' for stdout").Short('o').StringVar(&opts.OutputFile) 37 | cmd.Arg("ATTACHMENT-ID", "Attachment id to fetch").StringVar(&opts.AttachmentID) 38 | return nil 39 | } 40 | 41 | func CmdAttachGet(o *oreo.Client, globals *jiracli.GlobalOptions, opts *AttachGetOptions) error { 42 | attachment, err := jira.GetAttachment(o, globals.Endpoint.Value, opts.AttachmentID) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | resp, err := o.Get(attachment.Content) 48 | if err != nil { 49 | return err 50 | } 51 | defer resp.Body.Close() 52 | 53 | var output *os.File 54 | if opts.OutputFile == "-" { 55 | output = os.Stdout 56 | } else if opts.OutputFile != "" { 57 | output, err = os.Create(opts.OutputFile) 58 | if err != nil { 59 | return err 60 | } 61 | defer output.Close() 62 | } else { 63 | output, err = os.Create(attachment.Filename) 64 | if err != nil { 65 | return err 66 | } 67 | defer output.Close() 68 | } 69 | 70 | _, err = io.Copy(output, resp.Body) 71 | if err != nil { 72 | return err 73 | } 74 | output.Close() 75 | if opts.OutputFile != "-" && !globals.Quiet.Value { 76 | fmt.Printf("OK Wrote %s\n", output.Name()) 77 | } 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /jiracmd/attachList.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/coryb/figtree" 7 | "github.com/coryb/oreo" 8 | "github.com/go-jira/jira" 9 | "github.com/go-jira/jira/jiracli" 10 | "github.com/go-jira/jira/jiradata" 11 | kingpin "gopkg.in/alecthomas/kingpin.v2" 12 | ) 13 | 14 | type AttachListOptions struct { 15 | jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` 16 | Project string `yaml:"project,omitempty" json:"project,omitempty"` 17 | Issue string `yaml:"issue,omitempty" json:"issue,omitempty"` 18 | } 19 | 20 | func CmdAttachListRegistry() *jiracli.CommandRegistryEntry { 21 | opts := AttachListOptions{ 22 | CommonOptions: jiracli.CommonOptions{ 23 | Template: figtree.NewStringOption("attach-list"), 24 | }, 25 | } 26 | 27 | return &jiracli.CommandRegistryEntry{ 28 | "Prints attachment details for issue", 29 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 30 | jiracli.LoadConfigs(cmd, fig, &opts) 31 | return CmdAttachListUsage(cmd, &opts) 32 | }, 33 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 34 | opts.Issue = jiracli.FormatIssue(opts.Issue, opts.Project) 35 | return CmdAttachList(o, globals, &opts) 36 | }, 37 | } 38 | } 39 | 40 | func CmdAttachListUsage(cmd *kingpin.CmdClause, opts *AttachListOptions) error { 41 | jiracli.BrowseUsage(cmd, &opts.CommonOptions) 42 | jiracli.TemplateUsage(cmd, &opts.CommonOptions) 43 | cmd.Arg("ISSUE", "Issue id to lookup attachments").Required().StringVar(&opts.Issue) 44 | return nil 45 | } 46 | 47 | func CmdAttachList(o *oreo.Client, globals *jiracli.GlobalOptions, opts *AttachListOptions) error { 48 | data, err := jira.GetIssue(o, globals.Endpoint.Value, opts.Issue, nil) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | // need to conver the interface{} "attachment" field to an actual 54 | // ListOfAttachment object so we can sort it 55 | var attachments jiradata.ListOfAttachment 56 | err = jiracli.ConvertType(data.Fields["attachment"], &attachments) 57 | if err != nil { 58 | return err 59 | } 60 | sort.Sort(&attachments) 61 | 62 | if err := opts.PrintTemplate(attachments); err != nil { 63 | return err 64 | } 65 | if opts.Browse.Value { 66 | return CmdBrowse(globals, opts.Issue) 67 | } 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /jiracmd/exportTemplates.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | 8 | "github.com/coryb/figtree" 9 | "github.com/coryb/oreo" 10 | "github.com/go-jira/jira/jiracli" 11 | kingpin "gopkg.in/alecthomas/kingpin.v2" 12 | ) 13 | 14 | type ExportTemplatesOptions struct { 15 | Template string `yaml:"template,omitempty" json:"template,omitempty"` 16 | Dir string `yaml:"dir,omitempty" json:"dir,omitempty"` 17 | } 18 | 19 | func CmdExportTemplatesRegistry() *jiracli.CommandRegistryEntry { 20 | opts := ExportTemplatesOptions{} 21 | 22 | return &jiracli.CommandRegistryEntry{ 23 | "Export templates for customizations", 24 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 25 | jiracli.LoadConfigs(cmd, fig, &opts) 26 | return CmdExportTemplatesUsage(cmd, &opts) 27 | }, 28 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 29 | if opts.Dir == "" { 30 | opts.Dir = fmt.Sprintf("%s/.jira.d/templates", jiracli.Homedir()) 31 | } 32 | return CmdExportTemplates(globals, &opts) 33 | }, 34 | } 35 | } 36 | 37 | func CmdExportTemplatesUsage(cmd *kingpin.CmdClause, opts *ExportTemplatesOptions) error { 38 | cmd.Flag("template", "Template to export").Short('t').StringVar(&opts.Template) 39 | cmd.Flag("dir", "directory to write tempates to").Short('d').StringVar(&opts.Dir) 40 | 41 | return nil 42 | } 43 | 44 | // CmdExportTemplates will export templates to directory 45 | func CmdExportTemplates(globals *jiracli.GlobalOptions, opts *ExportTemplatesOptions) error { 46 | if err := os.MkdirAll(opts.Dir, 0755); err != nil { 47 | return err 48 | } 49 | 50 | for name, template := range jiracli.AllTemplates { 51 | if opts.Template != "" && opts.Template != name { 52 | continue 53 | } 54 | templateFile := path.Join(opts.Dir, name) 55 | if _, err := os.Stat(templateFile); err == nil { 56 | log.Warning("Skipping %s, already exists", templateFile) 57 | continue 58 | } 59 | fh, err := os.OpenFile(templateFile, os.O_WRONLY|os.O_CREATE, 0644) 60 | if err != nil { 61 | log.Errorf("Failed to open %s for writing: %s", templateFile, err) 62 | return err 63 | } 64 | defer fh.Close() 65 | if !globals.Quiet.Value { 66 | log.Noticef("Creating %s", templateFile) 67 | } 68 | fh.Write([]byte(template)) 69 | } 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /jiradata/SearchRequest.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/SearchRequest.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // SearchRequest defined from schema: 14 | // { 15 | // "title": "Search Request", 16 | // "id": "https://docs.atlassian.com/jira/REST/schema/search-request#", 17 | // "type": "object", 18 | // "properties": { 19 | // "fields": { 20 | // "title": "fields", 21 | // "type": "array", 22 | // "items": { 23 | // "type": "string" 24 | // } 25 | // }, 26 | // "fieldsByKeys": { 27 | // "title": "fieldsByKeys", 28 | // "type": "boolean" 29 | // }, 30 | // "jql": { 31 | // "title": "jql", 32 | // "type": "string" 33 | // }, 34 | // "maxResults": { 35 | // "title": "maxResults", 36 | // "type": "integer" 37 | // }, 38 | // "properties": { 39 | // "title": "properties", 40 | // "type": "array", 41 | // "items": { 42 | // "type": "string" 43 | // } 44 | // }, 45 | // "startAt": { 46 | // "title": "startAt", 47 | // "type": "integer" 48 | // }, 49 | // "validateQuery": { 50 | // "title": "validateQuery", 51 | // "type": "string" 52 | // } 53 | // } 54 | // } 55 | type SearchRequest struct { 56 | Fields Fields `json:"fields,omitempty" yaml:"fields,omitempty"` 57 | FieldsByKeys bool `json:"fieldsByKeys,omitempty" yaml:"fieldsByKeys,omitempty"` 58 | JQL string `json:"jql,omitempty" yaml:"jql,omitempty"` 59 | MaxResults int `json:"maxResults,omitempty" yaml:"maxResults,omitempty"` 60 | Properties Properties `json:"properties,omitempty" yaml:"properties,omitempty"` 61 | StartAt int `json:"startAt,omitempty" yaml:"startAt,omitempty"` 62 | ValidateQuery string `json:"validateQuery,omitempty" yaml:"validateQuery,omitempty"` 63 | } 64 | -------------------------------------------------------------------------------- /jiracmd/vote.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/coryb/figtree" 7 | "github.com/coryb/oreo" 8 | 9 | "github.com/go-jira/jira" 10 | "github.com/go-jira/jira/jiracli" 11 | kingpin "gopkg.in/alecthomas/kingpin.v2" 12 | ) 13 | 14 | type VoteAction int 15 | 16 | const ( 17 | VoteUP VoteAction = iota 18 | VoteDown 19 | ) 20 | 21 | type VoteOptions struct { 22 | jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` 23 | Project string `yaml:"project,omitempty" json:"project,omitempty"` 24 | Issue string `yaml:"issue,omitempty" json:"issue,omitempty"` 25 | Action VoteAction `yaml:"-" json:"-"` 26 | } 27 | 28 | func CmdVoteRegistry() *jiracli.CommandRegistryEntry { 29 | opts := VoteOptions{ 30 | CommonOptions: jiracli.CommonOptions{}, 31 | Action: VoteUP, 32 | } 33 | 34 | return &jiracli.CommandRegistryEntry{ 35 | "Vote up/down an issue", 36 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 37 | jiracli.LoadConfigs(cmd, fig, &opts) 38 | return CmdVoteUsage(cmd, &opts) 39 | }, 40 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 41 | opts.Issue = jiracli.FormatIssue(opts.Issue, opts.Project) 42 | return CmdVote(o, globals, &opts) 43 | }, 44 | } 45 | } 46 | 47 | func CmdVoteUsage(cmd *kingpin.CmdClause, opts *VoteOptions) error { 48 | jiracli.BrowseUsage(cmd, &opts.CommonOptions) 49 | cmd.Flag("down", "downvote the issue").Short('d').PreAction(func(ctx *kingpin.ParseContext) error { 50 | opts.Action = VoteDown 51 | return nil 52 | }).Bool() 53 | cmd.Arg("ISSUE", "issue id to vote").StringVar(&opts.Issue) 54 | return nil 55 | } 56 | 57 | // Vote will up/down vote an issue 58 | func CmdVote(o *oreo.Client, globals *jiracli.GlobalOptions, opts *VoteOptions) error { 59 | if opts.Action == VoteUP { 60 | if err := jira.IssueAddVote(o, globals.Endpoint.Value, opts.Issue); err != nil { 61 | return err 62 | } 63 | } else { 64 | if err := jira.IssueRemoveVote(o, globals.Endpoint.Value, opts.Issue); err != nil { 65 | return err 66 | } 67 | } 68 | if !globals.Quiet.Value { 69 | fmt.Printf("OK %s %s\n", opts.Issue, jira.URLJoin(globals.Endpoint.Value, "browse", opts.Issue)) 70 | } 71 | if opts.Browse.Value { 72 | return CmdBrowse(globals, opts.Issue) 73 | } 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /jiracmd/labelsAdd.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/coryb/figtree" 7 | "github.com/coryb/oreo" 8 | 9 | "github.com/go-jira/jira" 10 | "github.com/go-jira/jira/jiracli" 11 | "github.com/go-jira/jira/jiradata" 12 | kingpin "gopkg.in/alecthomas/kingpin.v2" 13 | ) 14 | 15 | type LabelsAddOptions struct { 16 | jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` 17 | Project string `yaml:"project,omitempty" json:"project,omitempty"` 18 | Issue string `yaml:"issue,omitempty" json:"issue,omitempty"` 19 | Labels []string `yaml:"labels,omitempty" json:"labels,omitempty"` 20 | } 21 | 22 | func CmdLabelsAddRegistry() *jiracli.CommandRegistryEntry { 23 | opts := LabelsAddOptions{} 24 | return &jiracli.CommandRegistryEntry{ 25 | "Add labels to an issue", 26 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 27 | jiracli.LoadConfigs(cmd, fig, &opts) 28 | return CmdLabelsAddUsage(cmd, &opts) 29 | }, 30 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 31 | opts.Issue = jiracli.FormatIssue(opts.Issue, opts.Project) 32 | return CmdLabelsAdd(o, globals, &opts) 33 | }, 34 | } 35 | } 36 | 37 | func CmdLabelsAddUsage(cmd *kingpin.CmdClause, opts *LabelsAddOptions) error { 38 | jiracli.BrowseUsage(cmd, &opts.CommonOptions) 39 | cmd.Arg("ISSUE", "issue id to modify labels").Required().StringVar(&opts.Issue) 40 | cmd.Arg("LABEL", "label to add to issue").Required().StringsVar(&opts.Labels) 41 | return nil 42 | } 43 | 44 | // CmdLabelsAdd will add labels on a given issue 45 | func CmdLabelsAdd(o *oreo.Client, globals *jiracli.GlobalOptions, opts *LabelsAddOptions) error { 46 | ops := jiradata.FieldOperations{} 47 | for _, label := range opts.Labels { 48 | ops = append(ops, jiradata.FieldOperation{ 49 | "add": label, 50 | }) 51 | } 52 | issueUpdate := jiradata.IssueUpdate{ 53 | Update: jiradata.FieldOperationsMap{ 54 | "labels": ops, 55 | }, 56 | } 57 | 58 | if err := jira.EditIssue(o, globals.Endpoint.Value, opts.Issue, &issueUpdate); err != nil { 59 | return err 60 | } 61 | if !globals.Quiet.Value { 62 | fmt.Printf("OK %s %s\n", opts.Issue, jira.URLJoin(globals.Endpoint.Value, "browse", opts.Issue)) 63 | } 64 | if opts.Browse.Value { 65 | return CmdBrowse(globals, opts.Issue) 66 | } 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /jiracmd/labelsRemove.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/coryb/figtree" 7 | "github.com/coryb/oreo" 8 | 9 | "github.com/go-jira/jira" 10 | "github.com/go-jira/jira/jiracli" 11 | "github.com/go-jira/jira/jiradata" 12 | kingpin "gopkg.in/alecthomas/kingpin.v2" 13 | ) 14 | 15 | type LabelsRemoveOptions struct { 16 | jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` 17 | Project string `yaml:"project,omitempty" json:"project,omitempty"` 18 | Issue string `yaml:"issue,omitempty" json:"issue,omitempty"` 19 | Labels []string `yaml:"labels,omitempty" json:"labels,omitempty"` 20 | } 21 | 22 | func CmdLabelsRemoveRegistry() *jiracli.CommandRegistryEntry { 23 | opts := LabelsRemoveOptions{} 24 | return &jiracli.CommandRegistryEntry{ 25 | "Remove labels from an issue", 26 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 27 | jiracli.LoadConfigs(cmd, fig, &opts) 28 | return CmdLabelsRemoveUsage(cmd, &opts) 29 | }, 30 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 31 | opts.Issue = jiracli.FormatIssue(opts.Issue, opts.Project) 32 | return CmdLabelsRemove(o, globals, &opts) 33 | }, 34 | } 35 | } 36 | 37 | func CmdLabelsRemoveUsage(cmd *kingpin.CmdClause, opts *LabelsRemoveOptions) error { 38 | jiracli.BrowseUsage(cmd, &opts.CommonOptions) 39 | cmd.Arg("ISSUE", "issue id to modify labels").Required().StringVar(&opts.Issue) 40 | cmd.Arg("LABEL", "label to remove from issue").Required().StringsVar(&opts.Labels) 41 | return nil 42 | } 43 | 44 | // CmdLabelsRemove will remove labels on a given issue 45 | func CmdLabelsRemove(o *oreo.Client, globals *jiracli.GlobalOptions, opts *LabelsRemoveOptions) error { 46 | ops := jiradata.FieldOperations{} 47 | for _, label := range opts.Labels { 48 | ops = append(ops, jiradata.FieldOperation{ 49 | "remove": label, 50 | }) 51 | } 52 | issueUpdate := jiradata.IssueUpdate{ 53 | Update: jiradata.FieldOperationsMap{ 54 | "labels": ops, 55 | }, 56 | } 57 | 58 | err := jira.EditIssue(o, globals.Endpoint.Value, opts.Issue, &issueUpdate) 59 | if err != nil { 60 | return err 61 | } 62 | if !globals.Quiet.Value { 63 | fmt.Printf("OK %s %s\n", opts.Issue, jira.URLJoin(globals.Endpoint.Value, "browse", opts.Issue)) 64 | } 65 | if opts.Browse.Value { 66 | return CmdBrowse(globals, opts.Issue) 67 | } 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /session.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | 7 | "github.com/go-jira/jira/jiradata" 8 | ) 9 | 10 | type AuthProvider interface { 11 | ProvideAuthParams() *jiradata.AuthParams 12 | } 13 | 14 | type AuthOptions struct { 15 | Username string 16 | Password string 17 | } 18 | 19 | func (a *AuthOptions) ProvideAuthParams() *jiradata.AuthParams { 20 | return &jiradata.AuthParams{ 21 | Username: a.Username, 22 | Password: a.Password, 23 | } 24 | } 25 | 26 | // https://docs.atlassian.com/jira/REST/cloud/#auth/1/session-login 27 | func (j *Jira) NewSession(ap AuthProvider) (*jiradata.AuthSuccess, error) { 28 | return NewSession(j.UA, j.Endpoint, ap) 29 | } 30 | 31 | func NewSession(ua HttpClient, endpoint string, ap AuthProvider) (*jiradata.AuthSuccess, error) { 32 | req := ap.ProvideAuthParams() 33 | encoded, err := json.Marshal(req) 34 | if err != nil { 35 | return nil, err 36 | } 37 | uri := URLJoin(endpoint, "rest/auth/1/session") 38 | resp, err := ua.Post(uri, "application/json", bytes.NewBuffer(encoded)) 39 | if err != nil { 40 | return nil, err 41 | } 42 | defer resp.Body.Close() 43 | 44 | if resp.StatusCode == 200 { 45 | results := &jiradata.AuthSuccess{} 46 | return results, json.NewDecoder(resp.Body).Decode(results) 47 | } 48 | return nil, responseError(resp) 49 | } 50 | 51 | // https://docs.atlassian.com/jira/REST/cloud/#auth/1/session-currentUser 52 | func (j *Jira) GetSession() (*jiradata.CurrentUser, error) { 53 | return GetSession(j.UA, j.Endpoint) 54 | } 55 | 56 | func GetSession(ua HttpClient, endpoint string) (*jiradata.CurrentUser, error) { 57 | uri := URLJoin(endpoint, "rest/auth/1/session") 58 | resp, err := ua.GetJSON(uri) 59 | if err != nil { 60 | return nil, err 61 | } 62 | defer resp.Body.Close() 63 | 64 | if resp.StatusCode == 200 { 65 | results := &jiradata.CurrentUser{} 66 | return results, json.NewDecoder(resp.Body).Decode(results) 67 | } 68 | return nil, responseError(resp) 69 | } 70 | 71 | // https://docs.atlassian.com/jira/REST/cloud/#auth/1/session-logout 72 | func (j *Jira) DeleteSession() error { 73 | return DeleteSession(j.UA, j.Endpoint) 74 | } 75 | 76 | func DeleteSession(ua HttpClient, endpoint string) error { 77 | uri := URLJoin(endpoint, "rest/auth/1/session") 78 | resp, err := ua.Delete(uri) 79 | if err != nil { 80 | return err 81 | } 82 | defer resp.Body.Close() 83 | if resp.StatusCode == 204 { 84 | return nil 85 | } 86 | return responseError(resp) 87 | } 88 | -------------------------------------------------------------------------------- /jiracmd/componentAdd.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/coryb/figtree" 7 | "github.com/coryb/oreo" 8 | 9 | "github.com/go-jira/jira" 10 | "github.com/go-jira/jira/jiracli" 11 | "github.com/go-jira/jira/jiradata" 12 | kingpin "gopkg.in/alecthomas/kingpin.v2" 13 | ) 14 | 15 | type ComponentAddOptions struct { 16 | jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` 17 | jiradata.Component `yaml:",inline" json:",inline" figtree:",inline"` 18 | } 19 | 20 | func CmdComponentAddRegistry() *jiracli.CommandRegistryEntry { 21 | opts := ComponentAddOptions{ 22 | CommonOptions: jiracli.CommonOptions{ 23 | Template: figtree.NewStringOption("component-add"), 24 | }, 25 | } 26 | 27 | return &jiracli.CommandRegistryEntry{ 28 | "Add component", 29 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 30 | jiracli.LoadConfigs(cmd, fig, &opts) 31 | return CmdComponentAddUsage(cmd, &opts) 32 | }, 33 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 34 | return CmdComponentAdd(o, globals, &opts) 35 | }, 36 | } 37 | } 38 | 39 | func CmdComponentAddUsage(cmd *kingpin.CmdClause, opts *ComponentAddOptions) error { 40 | jiracli.EditorUsage(cmd, &opts.CommonOptions) 41 | jiracli.TemplateUsage(cmd, &opts.CommonOptions) 42 | cmd.Flag("noedit", "Disable opening the editor").SetValue(&opts.SkipEditing) 43 | cmd.Flag("project", "project to create component in").Short('p').StringVar(&opts.Project) 44 | cmd.Flag("name", "name of component").Short('n').StringVar(&opts.Name) 45 | cmd.Flag("description", "description of component").Short('d').StringVar(&opts.Description) 46 | cmd.Flag("lead", "person that acts as lead for component").Short('l').StringVar(&opts.LeadUserName) 47 | return nil 48 | } 49 | 50 | // CmdComponentAdd sends the provided overrides to the "component-add" template for editing, then 51 | // will parse the edited document as YAML and submit the document to jira. 52 | func CmdComponentAdd(o *oreo.Client, globals *jiracli.GlobalOptions, opts *ComponentAddOptions) error { 53 | var err error 54 | component := &jiradata.Component{} 55 | err = jiracli.EditLoop(&opts.CommonOptions, &opts.Component, component, func() error { 56 | _, err = jira.CreateComponent(o, globals.Endpoint.Value, component) 57 | return err 58 | }) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | if !globals.Quiet.Value { 64 | fmt.Printf("OK %s %s\n", component.Project, component.Name) 65 | } 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-jira/jira 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/Masterminds/goutils v1.1.0 // indirect 7 | github.com/Masterminds/semver v1.5.0 // indirect 8 | github.com/Masterminds/sprig v2.21.0+incompatible 9 | github.com/Netflix/go-expect v0.0.0-20180928190340-9d1f4485533b // indirect 10 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect 11 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect 12 | github.com/cheekybits/genny v1.0.0 // indirect 13 | github.com/coryb/figtree v1.0.1-0.20190907170512-58176d03ef0d 14 | github.com/coryb/kingpeon v0.0.0-20180107011214-9a669f143f2e 15 | github.com/coryb/oreo v0.0.0-20180804211640-3e1b88fc08f1 16 | github.com/davecgh/go-spew v1.1.0 // indirect 17 | github.com/fatih/camelcase v1.0.0 // indirect 18 | github.com/google/go-cmp v0.5.2 19 | github.com/google/uuid v1.1.1 // indirect 20 | github.com/guelfey/go.dbus v0.0.0-20131113121618-f6a3a2366cc3 // indirect 21 | github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c // indirect 22 | github.com/huandu/xstrings v1.2.0 // indirect 23 | github.com/imdario/mergo v0.3.7 // indirect 24 | github.com/jinzhu/copier v0.0.0-20180308034124-7e38e58719c3 25 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 26 | github.com/kr/pretty v0.1.0 // indirect 27 | github.com/kr/pty v1.1.4 // indirect 28 | github.com/mattn/go-colorable v0.0.9 // indirect 29 | github.com/mattn/go-isatty v0.0.3 // indirect 30 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b 31 | github.com/mitchellh/go-wordwrap v1.0.1 32 | github.com/olekukonko/tablewriter v0.0.3 33 | github.com/pkg/browser v0.0.0-20170505125900-c90ca0c84f15 34 | github.com/pkg/errors v0.8.0 35 | github.com/pmezard/go-difflib v1.0.0 // indirect 36 | github.com/stretchr/testify v1.2.2 37 | github.com/theckman/go-flock v0.4.0 // indirect 38 | github.com/tidwall/gjson v0.0.0-20180711011033-ba784d767ac7 39 | github.com/tidwall/match v1.0.0 // indirect 40 | github.com/tmc/keyring v0.0.0-20171121202319-839169085ae1 41 | golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb 42 | golang.org/x/net v0.0.0-20171102191033-01c190206fbd 43 | golang.org/x/sys v0.0.0-20180727230415-bd9dbc187b6e // indirect 44 | gopkg.in/AlecAivazis/survey.v1 v1.6.1 45 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 46 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 47 | gopkg.in/coryb/yaml.v2 v2.0.0-20180616071044-0e40e46f7153 48 | gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 49 | gopkg.in/yaml.v2 v2.2.2 // indirect 50 | ) 51 | -------------------------------------------------------------------------------- /jiracmd/rank.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/coryb/figtree" 7 | "github.com/coryb/oreo" 8 | 9 | "github.com/go-jira/jira" 10 | "github.com/go-jira/jira/jiracli" 11 | "github.com/go-jira/jira/jiradata" 12 | kingpin "gopkg.in/alecthomas/kingpin.v2" 13 | ) 14 | 15 | type RankOptions struct { 16 | jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` 17 | Project string `yaml:"project,omitempty" json:"project,omitempty"` 18 | First string `yaml:"first,omitempty" json:"first,omitempty"` 19 | Second string `yaml:"second,omitempty" json:"second,omitempty"` 20 | Order string `yaml:"order,omitempty" json:"order,omitempty"` 21 | } 22 | 23 | func CmdRankRegistry() *jiracli.CommandRegistryEntry { 24 | opts := RankOptions{} 25 | 26 | return &jiracli.CommandRegistryEntry{ 27 | "Change ranking of issue", 28 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 29 | jiracli.LoadConfigs(cmd, fig, &opts) 30 | return CmdRankUsage(cmd, &opts) 31 | }, 32 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 33 | opts.First = jiracli.FormatIssue(opts.First, opts.Project) 34 | opts.Second = jiracli.FormatIssue(opts.Second, opts.Project) 35 | return CmdRank(o, globals, &opts) 36 | }, 37 | } 38 | } 39 | 40 | func CmdRankUsage(cmd *kingpin.CmdClause, opts *RankOptions) error { 41 | jiracli.BrowseUsage(cmd, &opts.CommonOptions) 42 | cmd.Arg("FIRST-ISSUE", "first issue").Required().StringVar(&opts.First) 43 | cmd.Arg("after|before", "rank ordering").Required().HintOptions("after", "before").EnumVar(&opts.Order, "after", "before") 44 | cmd.Arg("SECOND-ISSUE", "second issue").Required().StringVar(&opts.Second) 45 | return nil 46 | } 47 | 48 | // CmdRank order two issue 49 | func CmdRank(o *oreo.Client, globals *jiracli.GlobalOptions, opts *RankOptions) error { 50 | req := &jiradata.RankRequest{ 51 | Issues: []string{opts.First}, 52 | } 53 | 54 | if opts.Order == "after" { 55 | req.RankAfterIssue = opts.Second 56 | } else { 57 | req.RankBeforeIssue = opts.Second 58 | } 59 | 60 | if err := jira.RankIssues(o, globals.Endpoint.Value, req); err != nil { 61 | return err 62 | } 63 | 64 | if !globals.Quiet.Value { 65 | fmt.Printf("OK %s %s\n", opts.First, jira.URLJoin(globals.Endpoint.Value, "browse", opts.First)) 66 | fmt.Printf("OK %s %s\n", opts.Second, jira.URLJoin(globals.Endpoint.Value, "browse", opts.Second)) 67 | } 68 | 69 | if opts.Browse.Value { 70 | if err := CmdBrowse(globals, opts.First); err != nil { 71 | return CmdBrowse(globals, opts.Second) 72 | } 73 | } 74 | 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /jiradata/Status.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/TransitionsMeta.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // Status defined from schema: 14 | // { 15 | // "title": "Status", 16 | // "type": "object", 17 | // "properties": { 18 | // "description": { 19 | // "title": "description", 20 | // "type": "string" 21 | // }, 22 | // "iconUrl": { 23 | // "title": "iconUrl", 24 | // "type": "string" 25 | // }, 26 | // "id": { 27 | // "title": "id", 28 | // "type": "string" 29 | // }, 30 | // "name": { 31 | // "title": "name", 32 | // "type": "string" 33 | // }, 34 | // "self": { 35 | // "title": "self", 36 | // "type": "string" 37 | // }, 38 | // "statusCategory": { 39 | // "title": "Status Category", 40 | // "type": "object", 41 | // "properties": { 42 | // "colorName": { 43 | // "title": "colorName", 44 | // "type": "string" 45 | // }, 46 | // "id": { 47 | // "title": "id", 48 | // "type": "integer" 49 | // }, 50 | // "key": { 51 | // "title": "key", 52 | // "type": "string" 53 | // }, 54 | // "name": { 55 | // "title": "name", 56 | // "type": "string" 57 | // }, 58 | // "self": { 59 | // "title": "self", 60 | // "type": "string" 61 | // } 62 | // } 63 | // }, 64 | // "statusColor": { 65 | // "title": "statusColor", 66 | // "type": "string" 67 | // } 68 | // } 69 | // } 70 | type Status struct { 71 | Description string `json:"description,omitempty" yaml:"description,omitempty"` 72 | IconURL string `json:"iconUrl,omitempty" yaml:"iconUrl,omitempty"` 73 | ID string `json:"id,omitempty" yaml:"id,omitempty"` 74 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 75 | Self string `json:"self,omitempty" yaml:"self,omitempty"` 76 | StatusCategory *StatusCategory `json:"statusCategory,omitempty" yaml:"statusCategory,omitempty"` 77 | StatusColor string `json:"statusColor,omitempty" yaml:"statusColor,omitempty"` 78 | } 79 | -------------------------------------------------------------------------------- /jiracmd/login.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/coryb/figtree" 8 | "github.com/coryb/oreo" 9 | "github.com/go-jira/jira" 10 | "github.com/go-jira/jira/jiracli" 11 | "github.com/mgutz/ansi" 12 | kingpin "gopkg.in/alecthomas/kingpin.v2" 13 | ) 14 | 15 | func CmdLoginRegistry() *jiracli.CommandRegistryEntry { 16 | opts := jiracli.CommonOptions{} 17 | return &jiracli.CommandRegistryEntry{ 18 | "Attempt to login into jira server", 19 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 20 | jiracli.LoadConfigs(cmd, fig, &opts) 21 | return nil 22 | }, 23 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 24 | return CmdLogin(o, globals, &opts) 25 | }, 26 | } 27 | } 28 | 29 | func authCallback(req *http.Request, resp *http.Response) (*http.Response, error) { 30 | if resp.StatusCode == 403 { 31 | defer resp.Body.Close() 32 | // X-Authentication-Denied-Reason: CAPTCHA_CHALLENGE; login-url=https://jira/login.jsp 33 | if reason := resp.Header.Get("X-Authentication-Denied-Reason"); reason != "" { 34 | return resp, fmt.Errorf("Authenticaion Failed: " + reason) 35 | } 36 | return resp, fmt.Errorf("Authenticaion Failed: Unkown Reason") 37 | } else if resp.StatusCode == 200 { 38 | if reason := resp.Header.Get("X-Seraph-Loginreason"); reason == "AUTHENTICATION_DENIED" { 39 | defer resp.Body.Close() 40 | return resp, fmt.Errorf("Authentication Failed: " + reason) 41 | } 42 | } 43 | 44 | return resp, nil 45 | } 46 | 47 | // CmdLogin will attempt to login into jira server 48 | func CmdLogin(o *oreo.Client, globals *jiracli.GlobalOptions, opts *jiracli.CommonOptions) error { 49 | if globals.AuthMethod() == "api-token" { 50 | log.Noticef("No need to login when using api-token authentication method") 51 | return nil 52 | } 53 | if globals.AuthMethod() == "bearer-token" { 54 | log.Noticef("No need to login when using bearer-token authentication method") 55 | return nil 56 | } 57 | 58 | ua := o.WithoutRedirect().WithRetries(0).WithoutCallbacks().WithPostCallback(authCallback) 59 | 60 | if session, err := jira.GetSession(o, globals.Endpoint.Value); err != nil { 61 | // No active session so try to create a new one 62 | _, err := jira.NewSession(ua, globals.Endpoint.Value, globals) 63 | if err != nil { 64 | // reset password on failed session 65 | globals.SetPass("") 66 | log.Errorf("%s", err) 67 | } else if !globals.Quiet.Value { 68 | fmt.Println(ansi.Color("OK", "green"), "New session for", globals.User) 69 | } 70 | } else { 71 | if !globals.Quiet.Value { 72 | fmt.Println(ansi.Color("OK", "green"), "Found session for", session.Name) 73 | } 74 | } 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /test/checks.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/google/go-cmp/cmp" 10 | ) 11 | 12 | func checkDiff(t *testing.T, buf bytes.Buffer, expect string, formats ...interface{}) { 13 | expect = fmt.Sprintf(expect, formats...) 14 | if !cmp.Equal(expect, buf.String()) { 15 | t.Fatal( 16 | cmp.Diff( 17 | buf.String(), 18 | expect, 19 | ), 20 | ) 21 | } 22 | } 23 | 24 | func checkCreateIssue(t *testing.T, buf bytes.Buffer, endpoint string) string { 25 | out := strings.Split(buf.String(), " ") 26 | if len(out) < 3 { 27 | t.Fatalf("unexpected split count on create output: %v", buf.String()) 28 | } 29 | issue := out[1] 30 | expect := fmt.Sprintf("OK %s %s/browse/%s\n", issue, endpoint, issue) 31 | if !cmp.Equal(expect, buf.String()) { 32 | t.Fatal( 33 | cmp.Diff( 34 | buf.String(), 35 | expect, 36 | ), 37 | ) 38 | } 39 | return issue 40 | } 41 | 42 | func checkEditIssue(t *testing.T, buf bytes.Buffer, issue, endpoint string) { 43 | out := strings.Split(buf.String(), " ") 44 | if len(out) < 3 { 45 | t.Fatalf("unexpected split count on create output: %v", buf.String()) 46 | } 47 | editedIssue := out[1] 48 | expect := fmt.Sprintf("OK %s %s/browse/%s\n", issue, endpoint, issue) 49 | if !cmp.Equal(expect, buf.String()) { 50 | t.Fatal( 51 | cmp.Diff( 52 | buf.String(), 53 | expect, 54 | ), 55 | ) 56 | } 57 | if !cmp.Equal(editedIssue, issue) { 58 | t.Fatal( 59 | cmp.Diff( 60 | editedIssue, 61 | issue, 62 | ), 63 | ) 64 | } 65 | } 66 | 67 | func checkIssueInOutput(t *testing.T, buf bytes.Buffer, issue string) { 68 | if !strings.Contains(buf.String(), issue) { 69 | t.Fatalf("issue %s not located in stdout: %s", issue, buf.String()) 70 | } 71 | } 72 | 73 | func checkIssueNotInOutput(t *testing.T, buf bytes.Buffer, issue string) { 74 | if strings.Contains(buf.String(), issue) { 75 | t.Fatalf("issue %s not located in stdout: %s", issue, buf.String()) 76 | } 77 | } 78 | 79 | func checkBlockIssue(t *testing.T, buf bytes.Buffer, issue, blocker, endpoint string) { 80 | checkDualIssues(t, buf, blocker, issue, endpoint) 81 | } 82 | 83 | func checkDupIssue(t *testing.T, buf bytes.Buffer, issue, duplicate, endpoint string) { 84 | checkDualIssues(t, buf, issue, duplicate, endpoint) 85 | } 86 | 87 | func checkDualIssues(t *testing.T, buf bytes.Buffer, first, second, endpoint string) { 88 | lines := strings.Split(buf.String(), "\n") 89 | if len(lines) < 2 { 90 | t.Fatalf("unexpected split count on create output: %v", buf.String()) 91 | } 92 | 93 | testBuf := bytes.NewBuffer([]byte(lines[0] + "\n")) 94 | checkEditIssue(t, *testBuf, first, endpoint) 95 | 96 | testBuf = bytes.NewBuffer([]byte(lines[1] + "\n")) 97 | checkEditIssue(t, *testBuf, second, endpoint) 98 | } 99 | -------------------------------------------------------------------------------- /jiradata/FieldMetaMap.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/TransitionsMeta.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // FieldMetaMap defined from schema: 14 | // { 15 | // "title": "fields", 16 | // "type": "object", 17 | // "patternProperties": { 18 | // ".+": { 19 | // "title": "Field Meta", 20 | // "type": "object", 21 | // "properties": { 22 | // "allowedValues": { 23 | // "title": "allowedValues", 24 | // "type": "array", 25 | // "items": {} 26 | // }, 27 | // "autoCompleteUrl": { 28 | // "title": "autoCompleteUrl", 29 | // "type": "string" 30 | // }, 31 | // "defaultValue": { 32 | // "title": "defaultValue" 33 | // }, 34 | // "hasDefaultValue": { 35 | // "title": "hasDefaultValue", 36 | // "type": "boolean" 37 | // }, 38 | // "key": { 39 | // "title": "key", 40 | // "type": "string" 41 | // }, 42 | // "name": { 43 | // "title": "name", 44 | // "type": "string" 45 | // }, 46 | // "operations": { 47 | // "title": "operations", 48 | // "type": "array", 49 | // "items": { 50 | // "type": "string" 51 | // } 52 | // }, 53 | // "required": { 54 | // "title": "required", 55 | // "type": "boolean" 56 | // }, 57 | // "schema": { 58 | // "title": "Json Type", 59 | // "type": "object", 60 | // "properties": { 61 | // "custom": { 62 | // "title": "custom", 63 | // "type": "string" 64 | // }, 65 | // "customId": { 66 | // "title": "customId", 67 | // "type": "integer" 68 | // }, 69 | // "items": { 70 | // "title": "items", 71 | // "type": "string" 72 | // }, 73 | // "system": { 74 | // "title": "system", 75 | // "type": "string" 76 | // }, 77 | // "type": { 78 | // "title": "type", 79 | // "type": "string" 80 | // } 81 | // } 82 | // } 83 | // } 84 | // } 85 | // } 86 | // } 87 | type FieldMetaMap map[string]*FieldMeta 88 | -------------------------------------------------------------------------------- /jiracmd/worklogAdd.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/coryb/figtree" 7 | "github.com/coryb/oreo" 8 | "github.com/go-jira/jira" 9 | "github.com/go-jira/jira/jiracli" 10 | "github.com/go-jira/jira/jiradata" 11 | kingpin "gopkg.in/alecthomas/kingpin.v2" 12 | ) 13 | 14 | type WorklogAddOptions struct { 15 | jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` 16 | jiradata.Worklog `yaml:",inline" json:",inline" figtree:",inline"` 17 | Project string `yaml:"project,omitempty" json:"project,omitempty"` 18 | Issue string `yaml:"issue,omitempty" json:"issue,omitempty"` 19 | } 20 | 21 | func CmdWorklogAddRegistry() *jiracli.CommandRegistryEntry { 22 | opts := WorklogAddOptions{ 23 | CommonOptions: jiracli.CommonOptions{ 24 | Template: figtree.NewStringOption("worklog"), 25 | }, 26 | } 27 | return &jiracli.CommandRegistryEntry{ 28 | "Add a worklog to an issue", 29 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 30 | jiracli.LoadConfigs(cmd, fig, &opts) 31 | return CmdWorklogAddUsage(cmd, &opts) 32 | }, 33 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 34 | opts.Issue = jiracli.FormatIssue(opts.Issue, opts.Project) 35 | return CmdWorklogAdd(o, globals, &opts) 36 | }, 37 | } 38 | } 39 | 40 | func CmdWorklogAddUsage(cmd *kingpin.CmdClause, opts *WorklogAddOptions) error { 41 | jiracli.BrowseUsage(cmd, &opts.CommonOptions) 42 | jiracli.EditorUsage(cmd, &opts.CommonOptions) 43 | jiracli.TemplateUsage(cmd, &opts.CommonOptions) 44 | cmd.Flag("noedit", "Disable opening the editor").SetValue(&opts.SkipEditing) 45 | cmd.Flag("comment", "Comment message for worklog").Short('m').StringVar(&opts.Comment) 46 | cmd.Flag("time-spent", "Time spent working on issue").Short('T').StringVar(&opts.TimeSpent) 47 | cmd.Flag("started", "Time you started work").Short('S').StringVar(&opts.Started) 48 | cmd.Arg("ISSUE", "issue id to fetch worklogs").Required().StringVar(&opts.Issue) 49 | return nil 50 | } 51 | 52 | // CmdWorklogAdd will attempt to add (action=add) a worklog to the given issue. 53 | // It will spawn the editor (unless --noedit isused) and post edited YAML 54 | // content as JSON to the worklog endpoint 55 | func CmdWorklogAdd(o *oreo.Client, globals *jiracli.GlobalOptions, opts *WorklogAddOptions) error { 56 | err := jiracli.EditLoop(&opts.CommonOptions, &opts.Worklog, &opts.Worklog, func() error { 57 | _, err := jira.AddIssueWorklog(o, globals.Endpoint.Value, opts.Issue, opts) 58 | return err 59 | }) 60 | if err != nil { 61 | return err 62 | } 63 | if !globals.Quiet.Value { 64 | fmt.Printf("OK %s %s\n", opts.Issue, jira.URLJoin(globals.Endpoint.Value, "browse", opts.Issue)) 65 | } 66 | if opts.Browse.Value { 67 | return CmdBrowse(globals, opts.Issue) 68 | } 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /jiracmd/comment.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/coryb/figtree" 7 | "github.com/coryb/oreo" 8 | 9 | "github.com/go-jira/jira" 10 | "github.com/go-jira/jira/jiracli" 11 | "github.com/go-jira/jira/jiradata" 12 | kingpin "gopkg.in/alecthomas/kingpin.v2" 13 | ) 14 | 15 | type CommentOptions struct { 16 | jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` 17 | Project string `yaml:"project,omitempty" json:"project,omitempty"` 18 | Overrides map[string]string `yaml:"overrides,omitempty" json:"overrides,omitempty"` 19 | Issue string `yaml:"issue,omitempty" json:"issue,omitempty"` 20 | } 21 | 22 | func CmdCommentRegistry() *jiracli.CommandRegistryEntry { 23 | opts := CommentOptions{ 24 | CommonOptions: jiracli.CommonOptions{ 25 | Template: figtree.NewStringOption("comment"), 26 | }, 27 | Overrides: map[string]string{}, 28 | } 29 | 30 | return &jiracli.CommandRegistryEntry{ 31 | "Add comment to issue", 32 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 33 | jiracli.LoadConfigs(cmd, fig, &opts) 34 | return CmdCommentUsage(cmd, &opts) 35 | }, 36 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 37 | opts.Issue = jiracli.FormatIssue(opts.Issue, opts.Project) 38 | return CmdComment(o, globals, &opts) 39 | }, 40 | } 41 | } 42 | 43 | func CmdCommentUsage(cmd *kingpin.CmdClause, opts *CommentOptions) error { 44 | jiracli.BrowseUsage(cmd, &opts.CommonOptions) 45 | jiracli.EditorUsage(cmd, &opts.CommonOptions) 46 | jiracli.TemplateUsage(cmd, &opts.CommonOptions) 47 | cmd.Flag("noedit", "Disable opening the editor").SetValue(&opts.SkipEditing) 48 | cmd.Flag("comment", "Comment message for issue").Short('m').PreAction(func(ctx *kingpin.ParseContext) error { 49 | opts.Overrides["comment"] = jiracli.FlagValue(ctx, "comment") 50 | return nil 51 | }).String() 52 | cmd.Arg("ISSUE", "issue id to update").StringVar(&opts.Issue) 53 | return nil 54 | } 55 | 56 | // CmdComment will update issue with comment 57 | func CmdComment(o *oreo.Client, globals *jiracli.GlobalOptions, opts *CommentOptions) error { 58 | comment := jiradata.Comment{} 59 | input := struct { 60 | Overrides map[string]string `yaml:"overrides,omitempty" json:"overrides,omitempty"` 61 | }{ 62 | opts.Overrides, 63 | } 64 | err := jiracli.EditLoop(&opts.CommonOptions, &input, &comment, func() error { 65 | _, err := jira.IssueAddComment(o, globals.Endpoint.Value, opts.Issue, &comment) 66 | return err 67 | }) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | if !globals.Quiet.Value { 73 | fmt.Printf("OK %s %s\n", opts.Issue, jira.URLJoin(globals.Endpoint.Value, "browse", opts.Issue)) 74 | } 75 | 76 | if opts.Browse.Value { 77 | return CmdBrowse(globals, opts.Issue) 78 | } 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /_t/dockerroot/root/.m2/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | defaultProfile 9 | 10 | true 11 | 12 | 13 | 14 | 15 | atlassian-public 16 | https://maven.atlassian.com/repository/public 17 | 18 | true 19 | never 20 | warn 21 | 22 | 23 | true 24 | warn 25 | never 26 | 27 | 28 | 29 | atlassian-plugin-sdk 30 | file://${env.ATLAS_HOME}/repository 31 | 32 | true 33 | never 34 | 35 | 36 | true 37 | warn 38 | never 39 | 40 | 41 | 42 | 43 | 44 | 45 | atlassian-public 46 | https://maven.atlassian.com/repository/public 47 | 48 | true 49 | warn 50 | never 51 | 52 | 53 | never 54 | warn 55 | 56 | 57 | 58 | atlassian-plugin-sdk 59 | file://${env.ATLAS_HOME}/repository 60 | 61 | true 62 | warn 63 | never 64 | 65 | 66 | true 67 | never 68 | 69 | 70 | 71 | 72 | true 73 | true 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /jiracmd/request.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/url" 8 | "strings" 9 | 10 | "github.com/coryb/figtree" 11 | "github.com/coryb/oreo" 12 | "github.com/go-jira/jira/jiracli" 13 | kingpin "gopkg.in/alecthomas/kingpin.v2" 14 | ) 15 | 16 | type RequestOptions struct { 17 | jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` 18 | Method string `yaml:"method,omitempty" json:"method,omitempty"` 19 | URI string `yaml:"uri,omitempty" json:"uri,omitempty"` 20 | Data string `yaml:"data,omitempty" json:"data,omitempty"` 21 | } 22 | 23 | func CmdRequestRegistry() *jiracli.CommandRegistryEntry { 24 | opts := RequestOptions{ 25 | CommonOptions: jiracli.CommonOptions{ 26 | Template: figtree.NewStringOption("request"), 27 | }, 28 | } 29 | 30 | return &jiracli.CommandRegistryEntry{ 31 | "Open issue in requestr", 32 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 33 | jiracli.LoadConfigs(cmd, fig, &opts) 34 | jiracli.TemplateUsage(cmd, &opts.CommonOptions) 35 | jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions) 36 | return CmdRequestUsage(cmd, &opts) 37 | }, 38 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 39 | if opts.Method == "" { 40 | opts.Method = "GET" 41 | } 42 | return CmdRequest(o, globals, &opts) 43 | }, 44 | } 45 | } 46 | 47 | func CmdRequestUsage(cmd *kingpin.CmdClause, opts *RequestOptions) error { 48 | cmd.Flag("method", "HTTP request method to use").Short('M').EnumVar(&opts.Method, "GET", "PUT", "POST", "DELETE") 49 | cmd.Arg("API", "Path to Jira API (ie: /rest/api/2/issue)").Required().StringVar(&opts.URI) 50 | cmd.Arg("JSON", "JSON Content to send to API").StringVar(&opts.Data) 51 | 52 | return nil 53 | } 54 | 55 | // CmdRequest open the default system requestr to the provided issue 56 | func CmdRequest(o *oreo.Client, globals *jiracli.GlobalOptions, opts *RequestOptions) error { 57 | uri := opts.URI 58 | if !strings.HasPrefix(uri, "http") { 59 | uri = globals.Endpoint.Value + uri 60 | } 61 | 62 | parsedURI, err := url.Parse(uri) 63 | if err != nil { 64 | return err 65 | } 66 | builder := oreo.RequestBuilder(parsedURI).WithMethod(opts.Method) 67 | if opts.Data != "" { 68 | builder = builder.WithJSON(opts.Data) 69 | } 70 | 71 | resp, err := o.Do(builder.Build()) 72 | if err != nil { 73 | return err 74 | } 75 | if resp.Body == nil { 76 | return fmt.Errorf("Empty Response Body") 77 | } 78 | defer resp.Body.Close() 79 | 80 | bodyBytes, err := ioutil.ReadAll(resp.Body) 81 | if err != nil { 82 | return fmt.Errorf("Response Body read Error: %v", err) 83 | } 84 | if len(bodyBytes) == 0 { 85 | log.Info("Empty response for status %d", resp.StatusCode) 86 | return nil 87 | } 88 | 89 | var data interface{} 90 | if err := json.Unmarshal(bodyBytes, &data); err != nil { 91 | return fmt.Errorf("JSON Parse Error: %v", err) 92 | } 93 | return opts.PrintTemplate(&data) 94 | } 95 | -------------------------------------------------------------------------------- /_t/120custom-commands.t: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | eval "$(curl -q -s https://raw.githubusercontent.com/coryb/osht/master/osht.sh)" 3 | cd $(dirname $0) 4 | jira="../jira" 5 | . env.sh 6 | 7 | PLAN 16 8 | 9 | # reset login 10 | RUNS $jira logout 11 | RUNS $jira login 12 | 13 | # cleanup from previous failed test executions 14 | ($jira ls --project BASIC | awk -F: '{print $1}' | while read issue; do ../jira done $issue; done) | sed 's/^/# CLEANUP: /g' 15 | 16 | ############################################################################### 17 | ## Create an issue 18 | ############################################################################### 19 | RUNS $jira create --project BASIC -o summary=summary -o description=description --noedit --saveFile issue.props 20 | issue=$(awk '/issue/{print $2}' issue.props) 21 | 22 | DIFF < 1 { 70 | return fmt.Errorf("Found %d accounts for users with username %q", len(users), opts.Assignee) 71 | } else if len(users) == 1 { 72 | opts.Assignee = users[0].AccountID 73 | } 74 | } 75 | assignFunc = jira.IssueAssignAccountID 76 | } 77 | 78 | err := assignFunc(o, globals.Endpoint.Value, opts.Issue, opts.Assignee) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | if !globals.Quiet.Value { 84 | fmt.Printf("OK %s %s\n", opts.Issue, jira.URLJoin(globals.Endpoint.Value, "browse", opts.Issue)) 85 | } 86 | 87 | if opts.Browse.Value { 88 | return CmdBrowse(globals, opts.Issue) 89 | } 90 | 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /jiracmd/issuelink.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/coryb/figtree" 7 | "github.com/coryb/oreo" 8 | 9 | "github.com/go-jira/jira" 10 | "github.com/go-jira/jira/jiracli" 11 | "github.com/go-jira/jira/jiradata" 12 | kingpin "gopkg.in/alecthomas/kingpin.v2" 13 | ) 14 | 15 | type IssueLinkOptions struct { 16 | jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` 17 | jiradata.LinkIssueRequest `yaml:",inline" json:",inline" figtree:",inline"` 18 | LinkType string `yaml:"linktype,omitempty" json:"linktype,omitempty"` 19 | Project string `yaml:"project,omitempty" json:"project,omitempty"` 20 | } 21 | 22 | func CmdIssueLinkRegistry() *jiracli.CommandRegistryEntry { 23 | opts := IssueLinkOptions{ 24 | LinkIssueRequest: jiradata.LinkIssueRequest{ 25 | Type: &jiradata.IssueLinkType{}, 26 | InwardIssue: &jiradata.IssueRef{}, 27 | OutwardIssue: &jiradata.IssueRef{}, 28 | }, 29 | } 30 | return &jiracli.CommandRegistryEntry{ 31 | "Link two issues", 32 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 33 | jiracli.LoadConfigs(cmd, fig, &opts) 34 | return CmdIssueLinkUsage(cmd, &opts) 35 | }, 36 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 37 | opts.OutwardIssue.Key = jiracli.FormatIssue(opts.OutwardIssue.Key, opts.Project) 38 | opts.InwardIssue.Key = jiracli.FormatIssue(opts.InwardIssue.Key, opts.Project) 39 | return CmdIssueLink(o, globals, &opts) 40 | }, 41 | } 42 | } 43 | 44 | func CmdIssueLinkUsage(cmd *kingpin.CmdClause, opts *IssueLinkOptions) error { 45 | jiracli.BrowseUsage(cmd, &opts.CommonOptions) 46 | jiracli.EditorUsage(cmd, &opts.CommonOptions) 47 | jiracli.TemplateUsage(cmd, &opts.CommonOptions) 48 | cmd.Flag("comment", "Comment message when linking issue").Short('m').PreAction(func(ctx *kingpin.ParseContext) error { 49 | opts.Comment = &jiradata.Comment{ 50 | Body: jiracli.FlagValue(ctx, "comment"), 51 | } 52 | return nil 53 | }).String() 54 | cmd.Arg("OUTWARDISSUE", "outward issue").Required().StringVar(&opts.OutwardIssue.Key) 55 | cmd.Arg("ISSUELINKTYPE", "issue link type").Required().StringVar(&opts.Type.Name) 56 | cmd.Arg("INWARDISSUE", "inward issue").Required().StringVar(&opts.InwardIssue.Key) 57 | return nil 58 | } 59 | 60 | // CmdIssueLink will update the given issue as being a duplicate by the given dup issue 61 | // and will attempt to resolve the dup issue 62 | func CmdIssueLink(o *oreo.Client, globals *jiracli.GlobalOptions, opts *IssueLinkOptions) error { 63 | if err := jira.LinkIssues(o, globals.Endpoint.Value, &opts.LinkIssueRequest); err != nil { 64 | return err 65 | } 66 | 67 | if !globals.Quiet.Value { 68 | fmt.Printf("OK %s %s\n", opts.InwardIssue.Key, jira.URLJoin(globals.Endpoint.Value, "browse", opts.InwardIssue.Key)) 69 | fmt.Printf("OK %s %s\n", opts.OutwardIssue.Key, jira.URLJoin(globals.Endpoint.Value, "browse", opts.OutwardIssue.Key)) 70 | } 71 | 72 | if opts.Browse.Value { 73 | if err := CmdBrowse(globals, opts.OutwardIssue.Key); err != nil { 74 | return CmdBrowse(globals, opts.InwardIssue.Key) 75 | } 76 | } 77 | 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /_t/000setup.t: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | eval "$(curl -q -s https://raw.githubusercontent.com/coryb/osht/master/osht.sh)" 3 | cd $(dirname $0) 4 | jira="../jira --user admin" 5 | 6 | . env.sh 7 | 8 | SKIP test -n "$JIRACLOUD" # using Jira Cloud at go-jira.atlassian.net 9 | PLAN 15 10 | 11 | # clean out any old containers 12 | docker rm -f go-jira-test 13 | 14 | RUNS docker build . -t go-jira-test 15 | 16 | mkdir -p $(pwd)/.maven-cache 17 | 18 | # start newt jira service, cache the users m2 directory to make startup faster 19 | RUNS docker run --detach -v $(pwd)/.maven-cache:/root/.m2/repository --name go-jira-test --publish 8080:8080 go-jira-test:latest 20 | 21 | # wait for docker service to get started 22 | RUNS sleep 5 23 | 24 | echo "# Waiting for jira service to be listening on port 8080" 25 | docker exec -i go-jira-test tail -f screenlog.0 | grep -m 1 'jira started successfully' | sed 's/^/# /' 26 | 27 | # wait for healthchecks to pass, curl will retry 900 times over 15 min waiting 28 | RUNS curl -q -L --retry 900 --retry-delay 1 -f -s "http://localhost:8080/rest/api/2/serverInfo?doHealthCheck=1" 29 | 30 | # login to jira as admin user 31 | RUNS $jira login 32 | 33 | # create gojira user 34 | RUNS $jira req -M POST /rest/api/2/user '{"name":"gojira","password":"gojira123","emailAddress":"gojira@example.com","displayName":"GoJira"}' 35 | 36 | # create mothra user (need secondary user for voting) 37 | RUNS $jira req -M POST /rest/api/2/user '{"name":"mothra","password":"mothra123","emailAddress":"mothra@example.com","displayName":"Mothra"}' 38 | 39 | # create SCRUM softwareproject 40 | RUNS $jira req -M POST /rest/api/2/project '{"key":"SCRUM","name":"Scrum","projectTypeKey":"software","projectTemplateKey":"com.pyxis.greenhopper.jira:gh-scrum-template","lead":"gojira"}' 41 | 42 | # create KANBAN software project 43 | RUNS $jira req -M POST /rest/api/2/project '{"key":"KANBAN","name":"Kanban","projectTypeKey":"software","projectTemplateKey":"com.pyxis.greenhopper.jira:gh-kanban-template","lead":"gojira"}' 44 | 45 | # create BAISC software project 46 | RUNS $jira req -M POST /rest/api/2/project '{"key":"BASIC","name":"Basic","projectTypeKey":"software","projectTemplateKey":"com.pyxis.greenhopper.jira:basic-software-development-template","lead":"gojira"}' 47 | 48 | # create PROJECT business project 49 | RUNS $jira req -M POST /rest/api/2/project '{"key":"PROJECT","name":"Project","projectTypeKey":"business","projectTemplateKey":"com.atlassian.jira-core-project-templates:jira-core-project-management","lead":"gojira"}' 50 | 51 | # create PROCESS business project 52 | RUNS $jira req -M POST /rest/api/2/project '{"key":"PROCESS","name":"Process","projectTypeKey":"business","projectTemplateKey":"com.atlassian.jira-core-project-templates:jira-core-process-management","lead":"gojira"}' 53 | 54 | # create TASK business project 55 | RUNS $jira req -M POST /rest/api/2/project '{"key":"TASK","name":"Task","projectTypeKey":"business","projectTemplateKey":"com.atlassian.jira-core-project-templates:jira-core-task-management","lead":"gojira"}' 56 | 57 | RUNS $jira logout 58 | 59 | # export new templates so we are always using whatever is latest 60 | # and not whatever is in the test-runners homedir 61 | RUNS $jira export-templates -d .jira.d/templates 62 | -------------------------------------------------------------------------------- /jiradata/FieldMeta.go: -------------------------------------------------------------------------------- 1 | package jiradata 2 | 3 | ///////////////////////////////////////////////////////////////////////// 4 | // This Code is Generated by SlipScheme Project: 5 | // https://github.com/coryb/slipscheme 6 | // 7 | // Generated with command: 8 | // slipscheme -dir jiradata -pkg jiradata -overwrite schemas/TransitionsMeta.json 9 | ///////////////////////////////////////////////////////////////////////// 10 | // DO NOT EDIT // 11 | ///////////////////////////////////////////////////////////////////////// 12 | 13 | // FieldMeta defined from schema: 14 | // { 15 | // "title": "Field Meta", 16 | // "type": "object", 17 | // "properties": { 18 | // "allowedValues": { 19 | // "title": "allowedValues", 20 | // "type": "array", 21 | // "items": {} 22 | // }, 23 | // "autoCompleteUrl": { 24 | // "title": "autoCompleteUrl", 25 | // "type": "string" 26 | // }, 27 | // "defaultValue": { 28 | // "title": "defaultValue" 29 | // }, 30 | // "hasDefaultValue": { 31 | // "title": "hasDefaultValue", 32 | // "type": "boolean" 33 | // }, 34 | // "key": { 35 | // "title": "key", 36 | // "type": "string" 37 | // }, 38 | // "name": { 39 | // "title": "name", 40 | // "type": "string" 41 | // }, 42 | // "operations": { 43 | // "title": "operations", 44 | // "type": "array", 45 | // "items": { 46 | // "type": "string" 47 | // } 48 | // }, 49 | // "required": { 50 | // "title": "required", 51 | // "type": "boolean" 52 | // }, 53 | // "schema": { 54 | // "title": "Json Type", 55 | // "type": "object", 56 | // "properties": { 57 | // "custom": { 58 | // "title": "custom", 59 | // "type": "string" 60 | // }, 61 | // "customId": { 62 | // "title": "customId", 63 | // "type": "integer" 64 | // }, 65 | // "items": { 66 | // "title": "items", 67 | // "type": "string" 68 | // }, 69 | // "system": { 70 | // "title": "system", 71 | // "type": "string" 72 | // }, 73 | // "type": { 74 | // "title": "type", 75 | // "type": "string" 76 | // } 77 | // } 78 | // } 79 | // } 80 | // } 81 | type FieldMeta struct { 82 | AllowedValues AllowedValues `json:"allowedValues,omitempty" yaml:"allowedValues,omitempty"` 83 | AutoCompleteURL string `json:"autoCompleteUrl,omitempty" yaml:"autoCompleteUrl,omitempty"` 84 | DefaultValue interface{} `json:"defaultValue,omitempty" yaml:"defaultValue,omitempty"` 85 | HasDefaultValue bool `json:"hasDefaultValue,omitempty" yaml:"hasDefaultValue,omitempty"` 86 | Key string `json:"key,omitempty" yaml:"key,omitempty"` 87 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 88 | Operations Operations `json:"operations,omitempty" yaml:"operations,omitempty"` 89 | Required bool `json:"required,omitempty" yaml:"required,omitempty"` 90 | Schema *JSONType `json:"schema,omitempty" yaml:"schema,omitempty"` 91 | } 92 | -------------------------------------------------------------------------------- /jiracmd/list.go: -------------------------------------------------------------------------------- 1 | package jiracmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/coryb/figtree" 7 | "github.com/coryb/oreo" 8 | "github.com/go-jira/jira" 9 | "github.com/go-jira/jira/jiracli" 10 | kingpin "gopkg.in/alecthomas/kingpin.v2" 11 | ) 12 | 13 | type ListOptions struct { 14 | jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"` 15 | jira.SearchOptions `yaml:",inline" json:",inline" figtree:",inline"` 16 | Queries map[string]string `yaml:"queries,omitempty" json:"queries,omitempty"` 17 | } 18 | 19 | func CmdListRegistry() *jiracli.CommandRegistryEntry { 20 | opts := ListOptions{ 21 | CommonOptions: jiracli.CommonOptions{ 22 | Template: figtree.NewStringOption("list"), 23 | }, 24 | } 25 | 26 | return &jiracli.CommandRegistryEntry{ 27 | "Prints list of issues for given search criteria", 28 | func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { 29 | jiracli.LoadConfigs(cmd, fig, &opts) 30 | return CmdListUsage(cmd, &opts, fig) 31 | }, 32 | func(o *oreo.Client, globals *jiracli.GlobalOptions) error { 33 | if opts.QueryFields == "" { 34 | opts.QueryFields = "assignee,created,priority,reporter,status,summary,updated,issuetype" 35 | } 36 | if opts.Sort == "" { 37 | opts.Sort = "priority asc, key" 38 | } 39 | return CmdList(o, globals, &opts) 40 | }, 41 | } 42 | } 43 | 44 | func CmdListUsage(cmd *kingpin.CmdClause, opts *ListOptions, fig *figtree.FigTree) error { 45 | jiracli.TemplateUsage(cmd, &opts.CommonOptions) 46 | jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions) 47 | cmd.Flag("assignee", "User assigned the issue").Short('a').StringVar(&opts.Assignee) 48 | cmd.Flag("component", "Component to search for").Short('c').StringVar(&opts.Component) 49 | cmd.Flag("issuetype", "Issue type to search for").Short('i').StringVar(&opts.IssueType) 50 | cmd.Flag("limit", "Maximum number of results to return in search").Short('l').IntVar(&opts.MaxResults) 51 | cmd.Flag("project", "Project to search for").Short('p').StringVar(&opts.Project) 52 | cmd.Flag("named-query", "The name of a query in the `queries` configuration").Short('n').PreAction(func(ctx *kingpin.ParseContext) error { 53 | name := jiracli.FlagValue(ctx, "named-query") 54 | if query, ok := opts.Queries[name]; ok && query != "" { 55 | var err error 56 | opts.Query, err = jiracli.ConfigTemplate(fig, query, cmd.FullCommand(), opts) 57 | return err 58 | } 59 | return fmt.Errorf("A valid named-query %q not found in `queries` configuration", name) 60 | }).String() 61 | cmd.Flag("query", "Jira Query Language (JQL) expression for the search").Short('q').StringVar(&opts.Query) 62 | cmd.Flag("queryfields", "Fields that are used in \"list\" template").Short('f').StringVar(&opts.QueryFields) 63 | cmd.Flag("reporter", "Reporter to search for").Short('r').StringVar(&opts.Reporter) 64 | cmd.Flag("status", "Filter on issue status").Short('S').StringVar(&opts.Status) 65 | cmd.Flag("sort", "Sort order to return").Short('s').StringVar(&opts.Sort) 66 | cmd.Flag("watcher", "Watcher to search for").Short('w').StringVar(&opts.Watcher) 67 | return nil 68 | } 69 | 70 | // List will query jira and send data to "list" template 71 | func CmdList(o *oreo.Client, globals *jiracli.GlobalOptions, opts *ListOptions) error { 72 | data, err := jira.Search(o, globals.Endpoint.Value, opts, jira.WithAutoPagination()) 73 | if err != nil { 74 | return err 75 | } 76 | return opts.PrintTemplate(data) 77 | } 78 | --------------------------------------------------------------------------------